diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp
index aadafc29..3f731658 100644
--- a/src/core/FileLoader.cpp
+++ b/src/core/FileLoader.cpp
@@ -332,6 +332,16 @@ CFileLoader::FindRelatedModelInfoCB(RpAtomic *atomic, void *data)
 	return atomic;
 }
 
+#ifdef LIBRW
+void
+InitClump(RpClump *clump)
+{
+	RpClumpForAllAtomics(clump, ConvertPlatformAtomic, nil);
+}
+#else
+#define InitClump(clump)
+#endif
+
 void
 CFileLoader::LoadModelFile(const char *filename)
 {
@@ -343,6 +353,7 @@ CFileLoader::LoadModelFile(const char *filename)
 	if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)){
 		clump = RpClumpStreamRead(stream);
 		if(clump){
+			InitClump(clump);
 			RpClumpForAllAtomics(clump, FindRelatedModelInfoCB, clump);
 			RpClumpDestroy(clump);
 		}
@@ -368,6 +379,7 @@ CFileLoader::LoadClumpFile(const char *filename)
 			GetNameAndLOD(nodename, name, &n);
 			mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(name, nil);
 			if(mi){
+				InitClump(clump);
 				assert(mi->IsClump());
 				mi->SetClump(clump);
 			}else
@@ -393,6 +405,7 @@ CFileLoader::LoadClumpFile(RwStream *stream, uint32 id)
 	if (mi->GetModelType() == MITYPE_PED && id != 0 && RwStreamFindChunk(stream, rwID_CLUMP, nil, nil)) {
 		// Read LOD ped
 		clump = RpClumpStreamRead(stream);
+		InitClump(clump);
 		if(clump){
 			((CPedModelInfo*)mi)->SetLowDetailClump(clump);
 			RpClumpDestroy(clump);
@@ -423,6 +436,7 @@ CFileLoader::FinishLoadClumpFile(RwStream *stream, uint32 id)
 	clump = RpClumpGtaStreamRead2(stream);
 
 	if(clump){
+		InitClump(clump);
 		mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id);
 		mi->SetClump(clump);
 		return true;
@@ -443,6 +457,7 @@ CFileLoader::LoadAtomicFile(RwStream *stream, uint32 id)
 		clump = RpClumpStreamRead(stream);
 		if(clump == nil)
 			return false;
+		InitClump(clump);
 		gpRelatedModelInfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id);
 		RpClumpForAllAtomics(clump, SetRelatedModelInfoCB, clump);
 		RpClumpDestroy(clump);
@@ -806,6 +821,8 @@ CFileLoader::LoadAtomicFile2Return(const char *filename)
 	stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename);
 	if(RwStreamFindChunk(stream, rwID_CLUMP, nil, nil))
 		clump = RpClumpStreamRead(stream);
+	if(clump)
+		InitClump(clump);
 	RwStreamClose(stream, nil);
 	return clump;
 }
diff --git a/src/core/Streaming.cpp b/src/core/Streaming.cpp
index 3c5689fd..4f721f17 100644
--- a/src/core/Streaming.cpp
+++ b/src/core/Streaming.cpp
@@ -390,6 +390,7 @@ CStreaming::LoadCdDirectory(const char *dirname, int n)
 	assert(sizeof(direntry) == 32);
 	while(CFileMgr::Read(fd, (char*)&direntry, sizeof(direntry))){
 		dot = strchr(direntry.name, '.');
+		assert(dot);
 		if(dot) *dot = '\0';
 		if(direntry.size > (uint32)ms_streamingBufferSize)
 			ms_streamingBufferSize = direntry.size;
diff --git a/src/core/main.cpp b/src/core/main.cpp
index 2e4b839a..cd234588 100644
--- a/src/core/main.cpp
+++ b/src/core/main.cpp
@@ -89,7 +89,11 @@ RwRGBA gColourTop;
 bool gameAlreadyInitialised;
 
 float NumberOfChunksLoaded;
+#ifdef GTA_PS2
+#define TOTALNUMCHUNKS 48.0f
+#else
 #define TOTALNUMCHUNKS 73.0f
+#endif
 
 bool g_SlowMode = false;
 char version_name[64];
diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp
index 58b3277a..64e59375 100644
--- a/src/fakerw/fake.cpp
+++ b/src/fakerw/fake.cpp
@@ -294,32 +294,12 @@ RwTextureAddressMode RwTextureGetAddressingV(const RwTexture *texture);
 // TODO
 void _rwD3D8TexDictionaryEnableRasterFormatConversion(bool enable) { }
 
-static rw::Raster*
-ConvertTexRaster(rw::Raster *ras)
-{
-	using namespace rw;
-
-	if(ras->platform == rw::platform)
-		return ras;
-	// compatible platforms
-	if(ras->platform == PLATFORM_D3D8 && rw::platform == PLATFORM_D3D9 ||
-	   ras->platform == PLATFORM_D3D9 && rw::platform == PLATFORM_D3D8)
-		return ras;
-
-	Image *img = ras->toImage();
-	ras->destroy();
-	img->unpalettize();
-	ras = Raster::createFromImage(img);
-	img->destroy();
-	return ras;
-}
-
 // hack for reading native textures
 RwBool rwNativeTextureHackRead(RwStream *stream, RwTexture **tex, RwInt32 size)
 {
 	*tex = Texture::streamReadNative(stream);
 #ifdef LIBRW
-	(*tex)->raster = ConvertTexRaster((*tex)->raster);
+	(*tex)->raster = rw::Raster::convertTexToCurrentPlatform((*tex)->raster);
 #endif
 	return *tex != nil;
 }
@@ -796,6 +776,9 @@ RwBool       RpWorldPluginAttach(void) {
 	registerNativeDataPlugin();
 	registerAtomicRightsPlugin();
 	registerMaterialRightsPlugin();
+
+	// not sure if this goes here
+	rw::xbox::registerVertexFormatPlugin();
 	return true;
 }
 
diff --git a/src/objects/CutsceneHead.cpp b/src/objects/CutsceneHead.cpp
index 55e75807..15611c29 100644
--- a/src/objects/CutsceneHead.cpp
+++ b/src/objects/CutsceneHead.cpp
@@ -12,6 +12,10 @@
 #include "CutsceneHead.h"
 #include "CdStream.h"
 
+#ifdef GTA_PS2_STUFF
+// this is a total hack to switch between PC and PS2 code
+static bool lastLoadedSKA;
+#endif
 
 CCutsceneHead::CCutsceneHead(CObject *obj)
 {
@@ -87,6 +91,10 @@ CCutsceneHead::ProcessControl(void)
 	assert(RwObjectGetType(m_rwObject) == rpCLUMP);
 	atm = GetFirstAtomic((RpClump*)m_rwObject);
 	hier = RpSkinAtomicGetHAnimHierarchy(atm);
+#ifdef GTA_PS2_STUFF
+	// PS2 only plays anims in cutscene, PC always plays anims
+	if(!lastLoadedSKA || CCutsceneMgr::IsRunning())
+#endif
 	RpHAnimHierarchyAddAnimTime(hier, CTimer::GetTimeStepNonClipped()/50.0f);
 }
 
@@ -168,6 +176,10 @@ CCutsceneHead::PlayAnimation(const char *animName)
 	uint32 offset, size;
 	RwStream *stream;
 
+#ifdef GTA_PS2_STUFF
+	lastLoadedSKA = false;
+#endif
+
 	assert(RwObjectGetType(m_rwObject) == rpCLUMP);
 	atm = GetFirstAtomic((RpClump*)m_rwObject);
 	hier = RpSkinAtomicGetHAnimHierarchy(atm);
@@ -191,4 +203,29 @@ CCutsceneHead::PlayAnimation(const char *animName)
 
 		RwStreamClose(stream, nil);
 	}
+#ifdef GTA_PS2_STUFF
+#ifdef LIBRW
+	else{
+		sprintf(gString, "%s.ska", animName);
+
+		if(CCutsceneMgr::ms_pCutsceneDir->FindItem(gString, offset, size)){
+			stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG");
+			assert(stream);
+
+			CStreaming::MakeSpaceFor(size * CDSTREAM_SECTOR_SIZE);
+			CStreaming::ImGonnaUseStreamingMemory();
+
+			RwStreamSkip(stream, offset*2048);
+			anim = rw::Animation::streamReadLegacy(stream);
+			RpHAnimHierarchySetCurrentAnim(hier, anim);
+
+			CStreaming::IHaveUsedStreamingMemory();
+
+			RwStreamClose(stream, nil);
+
+			lastLoadedSKA = true;
+		}
+	}
+#endif
+#endif
 }
diff --git a/src/rw/RwHelper.cpp b/src/rw/RwHelper.cpp
index 35af1ebd..0069934f 100644
--- a/src/rw/RwHelper.cpp
+++ b/src/rw/RwHelper.cpp
@@ -649,6 +649,83 @@ WRAPPER void _TexturePoolsInitialise() { EAXJMP(0x598B10); }
 WRAPPER void _TexturePoolsShutdown() { EAXJMP(0x598B30); }
 #endif
 
+#ifdef LIBRW
+#include <rpmatfx.h>
+#include "VehicleModelInfo.h"
+
+int32
+findPlatform(rw::Atomic *a)
+{
+	rw::Geometry *g = a->geometry;
+	if(g->instData)
+		return g->instData->platform;
+	return 0;
+}
+
+// in CVehicleModelInfo in VC
+static RpMaterial*
+GetMatFXEffectMaterialCB(RpMaterial *material, void *data)
+{
+	if(RpMatFXMaterialGetEffects(material) == rpMATFXEFFECTNULL)
+		return material;
+	*(int*)data = RpMatFXMaterialGetEffects(material);
+	return nil;
+}
+
+// Game doesn't read atomic extensions so we never get any other than the default pipe,
+// but we need it for uninstancing
+void
+attachPipe(rw::Atomic *atomic)
+{
+	if(RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)))
+		atomic->pipeline = rw::skinGlobals.pipelines[rw::platform];
+	else{
+		int fx = rpMATFXEFFECTNULL;
+		RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), GetMatFXEffectMaterialCB, &fx);
+		if(fx != rpMATFXEFFECTNULL)
+			RpMatFXAtomicEnableEffects(atomic);
+	}
+}
+
+// Attach pipes for the platform we have native data for so we can uninstance
+void
+switchPipes(rw::Atomic *a, int32 platform)
+{
+	if(a->pipeline && a->pipeline->platform != platform){
+		uint32 plgid = a->pipeline->pluginID;
+		switch(plgid){
+		// assume default pipe won't be attached explicitly
+		case rw::ID_SKIN:
+			a->pipeline = rw::skinGlobals.pipelines[platform];
+			break;
+		case rw::ID_MATFX:
+			a->pipeline = rw::matFXGlobals.pipelines[platform];
+			break;
+		}
+	}
+}
+
+RpAtomic*
+ConvertPlatformAtomic(RpAtomic *atomic, void *data)
+{
+	int32 driver = rw::platform;
+	int32 platform = findPlatform(atomic);
+	if(platform != 0 && platform != driver){
+		attachPipe(atomic);	// kludge
+		rw::ObjPipeline *origPipe = atomic->pipeline;
+		rw::platform = platform;
+		switchPipes(atomic, rw::platform);
+		if(atomic->geometry->flags & rw::Geometry::NATIVE)
+			atomic->uninstance();
+		// no ADC in this game
+		//rw::ps2::unconvertADC(atomic->geometry);
+		rw::platform = driver;
+		atomic->pipeline = origPipe;
+	}
+	return atomic;
+}
+#endif
+
 #if defined(FIX_BUGS) && defined(GTA_PC)
 RwUInt32 saved_alphafunc, saved_alpharef;
 
diff --git a/src/rw/RwHelper.h b/src/rw/RwHelper.h
index 130eb636..523a7732 100644
--- a/src/rw/RwHelper.h
+++ b/src/rw/RwHelper.h
@@ -56,6 +56,8 @@ RwCamera *CameraCreate(RwInt32 width,
 void _TexturePoolsInitialise();
 void _TexturePoolsShutdown();
 
+RpAtomic *ConvertPlatformAtomic(RpAtomic *atomic, void *data);
+
 #if defined(FIX_BUGS) && defined (GTA_PC)
 void SetAlphaTest(RwUInt32 alpharef);
 void RestoreAlphaTest();
diff --git a/vendor/librw b/vendor/librw
index 8c00f787..7e80d45c 160000
--- a/vendor/librw
+++ b/vendor/librw
@@ -1 +1 @@
-Subproject commit 8c00f787cb8f53781c4335ecbc9d28fb9c664ba7
+Subproject commit 7e80d45cdb6663f97d89ed443198ce6e37c4435e