code:
#include "common.h"

#include "RwHelper.h"
#include "templates.h"
#include "main.h"
#include "Entity.h"
#include "ModelInfo.h"
#include "Lights.h"
#include "Renderer.h"
#include "Camera.h"
#include "VisibilityPlugins.h"
#include "World.h"
#include "custompipes.h"
#include "MemoryHeap.h"

CLinkList<CVisibilityPlugins::AlphaObjectInfo> CVisibilityPlugins::m_alphaList;
CLinkList<CVisibilityPlugins::AlphaObjectInfo> CVisibilityPlugins::m_alphaEntityList;
#ifdef NEW_RENDERER
CLinkList<CVisibilityPlugins::AlphaObjectInfo> CVisibilityPlugins::m_alphaBuildingList;
#endif

int32 CVisibilityPlugins::ms_atomicPluginOffset = -1;
int32 CVisibilityPlugins::ms_framePluginOffset = -1;
int32 CVisibilityPlugins::ms_clumpPluginOffset = -1;

RwCamera *CVisibilityPlugins::ms_pCamera;
RwV3d *CVisibilityPlugins::ms_pCameraPosn;
float CVisibilityPlugins::ms_cullCompsDist;
float CVisibilityPlugins::ms_vehicleLod0Dist;
float CVisibilityPlugins::ms_vehicleLod1Dist;
float CVisibilityPlugins::ms_vehicleFadeDist;
float CVisibilityPlugins::ms_bigVehicleLod0Dist;
float CVisibilityPlugins::ms_bigVehicleLod1Dist;
float CVisibilityPlugins::ms_pedLod0Dist;
float CVisibilityPlugins::ms_pedLod1Dist;
float CVisibilityPlugins::ms_pedFadeDist;

#ifdef GTA_PS2	// maybe something else?
// if wanted, delete the original geometry data after rendering
// and only keep the instanced data
bool
rpDefaultGeometryInstance(RpGeometry *geo, void *atomic, int del)
{
#if THIS_IS_COMPATIBLE_WITH_GTA3_RW31
	if(RpGeometryGetNumMorphTargets(geo) != 1)
		return false;

	// this needs R*'s modification that geometry data is
	// allocated separately from the geometry itself
	geo->instanceFlags = rpGEOMETRYINSTANCE;
	AtomicDefaultRenderCallBack((RpAtomic*)atomic);

	if(!del)
		return true;

	// New mesh without indices
	RpMeshHeader *newheader = _rpMeshHeaderCreate(sizeof(RpMesh)*geo->mesh->numMeshes + sizeof(RpMeshHeader));
	newheader->numMeshes = geo->mesh->numMeshes;
	newheader->serialNum = 1;
	newheader->totalIndicesInMesh = 0;
	newheader->firstMeshOffset = 0;
	RpMesh *oldmesh = (RpMesh*)(geo->mesh+1);
	RpMesh *newmesh = (RpMesh*)(newheader+1);
	for(int i = 0; i < geo->mesh->numMeshes; i++){
		newmesh[i].indices = nil;
		newmesh[i].numIndices = 0;
		newmesh[i].material = oldmesh[i].material;
	}

	geo->refCount++;
	RpGeometryLock(geo, rpGEOMETRYLOCKPOLYGONS | rpGEOMETRYLOCKVERTICES |
		rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPRELIGHT |
		rpGEOMETRYLOCKTEXCOORDS1 | rpGEOMETRYLOCKTEXCOORDS2);

	// vertices and normals
	RpMorphTarget *mt = RpGeometryGetMorphTarget(geo, 0);
	if(mt->verts){
		RwFree(mt->verts);
		mt->verts = nil;
		mt->normals = nil;
	}
	geo->numVertices = 0;

	// triangles
	for(int i = 0; i < RpGeometryGetNumTriangles(geo); i++){
		if(RpGeometryGetTriangles(geo)->matIndex == -1)
			continue;
		RpMaterialDestroy(_rpMaterialListGetMaterial(&geo->matList, RpGeometryGetTriangles(geo)->matIndex));
	}
	if(RpGeometryGetTriangles(geo)){
		RwFree(RpGeometryGetTriangles(geo));
		geo->triangles = nil;
		geo->numTriangles = 0;
	}

	// tex coords
	if(RpGeometryGetVertexTexCoords(geo, 1)){
		RwFree(RpGeometryGetVertexTexCoords(geo, 1));
		geo->texCoords[1] = nil;
	}
	if(RpGeometryGetVertexTexCoords(geo, 0)){
		RwFree(RpGeometryGetVertexTexCoords(geo, 0));
		geo->texCoords[0] = nil;
	}

	// vertex colors
	if(RpGeometryGetPreLightColors(geo)){
		RwFree(RpGeometryGetPreLightColors(geo));
		geo->preLitLum = nil;
	}

	RpGeometryUnlock(geo);

	geo->instanceFlags = rpGEOMETRYPERSISTENT;
	// BUG? don't we have to free the old mesh?
	geo->mesh = newheader;
	geo->refCount--;
#else
	// We can do something for librw here actually, maybe later
	AtomicDefaultRenderCallBack((RpAtomic*)atomic);
#endif

	return true;
}

RpAtomic*
PreInstanceRenderCB(RpAtomic *atomic)
{
	RpGeometry *geo = RpAtomicGetGeometry(atomic);
	if(RpGeometryGetTriangles(geo)){
		PUSH_MEMID(MEMID_STREAM_MODELS);
		rpDefaultGeometryInstance(geo, atomic, 1);
		POP_MEMID();
	}else
		AtomicDefaultRenderCallBack(atomic);
	return atomic;
}
#define RENDERCALLBACK PreInstanceRenderCB
#else
RpAtomic*
DefaultRenderCB_pushid(RpAtomic *atomic)
{
	PUSH_MEMID(MEMID_STREAM_MODELS);
	AtomicDefaultRenderCallBack(atomic);
	POP_MEMID();
	return atomic;
}
#define RENDERCALLBACK DefaultRenderCB_pushid
#endif

void
CVisibilityPlugins::Initialise(void)
{
	m_alphaList.Init(NUMALPHALIST);
	m_alphaList.head.item.sort = 0.0f;
	m_alphaList.tail.item.sort = 100000000.0f;
#ifdef ASPECT_RATIO_SCALE
	// default 150 is not enough for bigger FOVs
	m_alphaEntityList.Init(NUMALPHAENTITYLIST * 3);
#else
	m_alphaEntityList.Init(NUMALPHAENTITYLIST);
#endif // ASPECT_RATIO_SCALE
	m_alphaEntityList.head.item.sort = 0.0f;
	m_alphaEntityList.tail.item.sort = 100000000.0f;

#ifdef NEW_RENDERER
	m_alphaBuildingList.Init(NUMALPHAENTITYLIST);
	m_alphaBuildingList.head.item.sort = 0.0f;
	m_alphaBuildingList.tail.item.sort = 100000000.0f;
#endif
}

void
CVisibilityPlugins::Shutdown(void)
{
	m_alphaList.Shutdown();
	m_alphaEntityList.Shutdown();
#ifdef NEW_RENDERER
	m_alphaBuildingList.Shutdown();
#endif
}

void
CVisibilityPlugins::InitAlphaEntityList(void)
{
	m_alphaEntityList.Clear();
#ifdef NEW_RENDERER
	m_alphaBuildingList.Clear();
#endif
}

bool
CVisibilityPlugins::InsertEntityIntoSortedList(CEntity *e, float dist)
{
#ifdef FIX_BUGS
	if (!e->m_rwObject) return true;
#endif

	AlphaObjectInfo item;
	item.entity = e;
	item.sort = dist;
#ifdef NEW_RENDERER
	if(gbNewRenderer && e->IsBuilding())
		return !!m_alphaBuildingList.InsertSorted(item);
#endif
	bool ret = !!m_alphaEntityList.InsertSorted(item);
//	if(!ret)
//		printf("list full %d\n", m_alphaEntityList.Count());
	return ret;
}

void
CVisibilityPlugins::InitAlphaAtomicList(void)
{
	m_alphaList.Clear();
}

bool
CVisibilityPlugins::InsertAtomicIntoSortedList(RpAtomic *a, float dist)
{
	AlphaObjectInfo item;
	item.atomic = a;
	item.sort = dist;
	bool ret = !!m_alphaList.InsertSorted(item);
//	if(!ret)
//		printf("list full %d\n", m_alphaList.Count());
	return ret;
}

// can't increase this yet unfortunately...
// probably have to fix fading for this so material alpha isn't overwritten
#define VEHICLE_LODDIST_MULTIPLIER (TheCamera.GenerationDistMultiplier)

void
CVisibilityPlugins::SetRenderWareCamera(RwCamera *camera)
{
	ms_pCamera = camera;
	ms_pCameraPosn = RwMatrixGetPos(RwFrameGetMatrix(RwCameraGetFrame(camera)));

	if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
	   TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED)
		ms_cullCompsDist = 1000000.0f;
	else
		ms_cullCompsDist = sq(TheCamera.LODDistMultiplier * 20.0f);

        ms_vehicleLod0Dist = sq(70.0f * VEHICLE_LODDIST_MULTIPLIER);
        ms_vehicleLod1Dist = sq(90.0f * VEHICLE_LODDIST_MULTIPLIER);
        ms_vehicleFadeDist = sq(100.0f * VEHICLE_LODDIST_MULTIPLIER);
        ms_bigVehicleLod0Dist = sq(60.0f * VEHICLE_LODDIST_MULTIPLIER);
        ms_bigVehicleLod1Dist = sq(150.0f * VEHICLE_LODDIST_MULTIPLIER);
        ms_pedLod0Dist = sq(25.0f * TheCamera.LODDistMultiplier);
        ms_pedLod1Dist = sq(60.0f * TheCamera.LODDistMultiplier);
        ms_pedFadeDist = sq(70.0f * TheCamera.LODDistMultiplier);
}

RpMaterial*
SetAlphaCB(RpMaterial *material, void *data)
{
	((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data;
	return material;
}

RpMaterial*
SetTextureCB(RpMaterial *material, void *data)
{
	RpMaterialSetTexture(material, (RwTexture*)data);
	return material;
}

void
CVisibilityPlugins::RenderAlphaAtomics(void)
{
	CLink<AlphaObjectInfo> *node;
	for(node = m_alphaList.tail.prev;
	    node != &m_alphaList.head;
	    node = node->prev)
		RENDERCALLBACK(node->item.atomic);
}

void
CVisibilityPlugins::RenderFadingEntities(void)
{
	CLink<AlphaObjectInfo> *node;
	CSimpleModelInfo *mi;
	for(node = m_alphaEntityList.tail.prev;
	    node != &m_alphaEntityList.head;
	    node = node->prev){
		CEntity *e = node->item.entity;
		if(e->m_rwObject == nil)
			continue;
#ifdef EXTENDED_PIPELINES
		if(CustomPipes::bRenderingEnvMap && (e->IsPed() || e->IsVehicle()))
			continue;
#endif
		mi = (CSimpleModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex());

#ifdef FIX_BUGS
		if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite)
#else
		if(mi->m_noZwrite)
#endif
			RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE);
#ifdef EXTRA_MODEL_FLAGS
		else if(mi->m_bIsTree)
			SetAlphaRef(128);
		if(!e->IsBuilding() || mi->RenderDoubleSided())
			BACKFACE_CULLING_OFF;
#endif

		if(e->bDistanceFade){
			DeActivateDirectional();
			SetAmbientColours();
			e->bImBeingRendered = true;
			PUSH_RENDERGROUP(mi->GetModelName());
			RenderFadingAtomic((RpAtomic*)e->m_rwObject, node->item.sort);
			POP_RENDERGROUP();
			e->bImBeingRendered = false;
		}else
			CRenderer::RenderOneNonRoad(e);

#ifdef EXTRA_MODEL_FLAGS
		if(mi->m_bIsTree)
			SetAlphaRef(2);
		BACKFACE_CULLING_ON;
#endif
#ifdef FIX_BUGS
		if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite)
#else
		if(mi->m_noZwrite)
#endif
			RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE);
	}
}

RpAtomic*
CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic)
{
	RpAtomic *lodatm;
	RwMatrix *m;
	RwV3d view;
	float len;
	CSimpleModelInfo *mi;

	mi = GetAtomicModelInfo(atomic);
	m = RwFrameGetLTM(RpAtomicGetFrame(atomic));
	RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn);
	len = RwV3dLength(&view);
#ifdef FIX_BUGS
	// from VC
	lodatm = mi->GetAtomicFromDistance(len * TheCamera.LODDistMultiplier / VEHICLE_LODDIST_MULTIPLIER);
#else
	lodatm = mi->GetAtomicFromDistance(len);
#endif
	if(lodatm){
		if(RpAtomicGetGeometry(lodatm) != RpAtomicGetGeometry(atomic))
			RpAtomicSetGeometry(atomic, RpAtomicGetGeometry(lodatm), rpATOMICSAMEBOUNDINGSPHERE);
		RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderObjNormalAtomic(RpAtomic *atomic)
{
	RwMatrix *m;
	RwV3d view;
	float len;

	m = RwFrameGetLTM(RpAtomicGetFrame(atomic));
	RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn);
	len = RwV3dLength(&view);
	if(RwV3dDotProduct(&view, RwMatrixGetUp(m)) < -0.3f*len && len > 8.0f)
		return atomic;
	RENDERCALLBACK(atomic);
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderAlphaAtomic(RpAtomic *atomic, int alpha)
{
	RpGeometry *geo;
	uint32 flags;

	geo = RpAtomicGetGeometry(atomic);
	flags = RpGeometryGetFlags(geo);
	RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR);
	RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha);
	RENDERCALLBACK(atomic);
	RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255);
	RpGeometrySetFlags(geo, flags);
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderFadingAtomic(RpAtomic *atomic, float camdist)
{
	RpAtomic *lodatm;
	float fadefactor;
	uint32 alpha;
	CSimpleModelInfo *mi;

	mi = GetAtomicModelInfo(atomic);
	lodatm = mi->GetAtomicFromDistance(camdist - FADE_DISTANCE);
	if(mi->m_additive){
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
		RENDERCALLBACK(atomic);
		RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
	}else{
		fadefactor = (mi->GetLargestLodDistance() - (camdist - FADE_DISTANCE))/FADE_DISTANCE;
		if(fadefactor > 1.0f)
			fadefactor = 1.0f;
		alpha = mi->m_alpha * fadefactor;
		if(alpha == 255)
			RENDERCALLBACK(atomic);
		else{
			RpGeometry *geo = RpAtomicGetGeometry(lodatm);
			uint32 flags = RpGeometryGetFlags(geo);
			RpGeometrySetFlags(geo, flags | rpGEOMETRYMODULATEMATERIALCOLOR);
			RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)alpha);
			if(geo != RpAtomicGetGeometry(atomic))
				RpAtomicSetGeometry(atomic, geo, rpATOMICSAMEBOUNDINGSPHERE); // originally 5 (mistake?)
			RENDERCALLBACK(atomic);
			RpGeometryForAllMaterials(geo, SetAlphaCB, (void*)255);
			RpGeometrySetFlags(geo, flags);
		}
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleHiDetailCB(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_vehicleLod0Dist){
		flags = GetAtomicId(atomic);
		if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
			dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
				RwFrameGetLTM(clumpframe), flags);
			if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
				return atomic;
		}
		RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleHiDetailAlphaCB(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_vehicleLod0Dist){
		flags = GetAtomicId(atomic);
		dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
			RwFrameGetLTM(clumpframe), flags);
		if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
			if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
				return atomic;

		if(flags & ATOMIC_FLAG_DRAWLAST){
			// sort before clump
			if(!InsertAtomicIntoSortedList(atomic, distsq - 0.0001f))
				RENDERCALLBACK(atomic);
		}else{
			if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
				RENDERCALLBACK(atomic);
		}
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_bigVehicleLod0Dist){
		flags = GetAtomicId(atomic);
		if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
			dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
				RwFrameGetLTM(clumpframe), flags);
			if(dot > 0.0f)
				return atomic;
		}
		RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_bigVehicleLod0Dist){
		flags = GetAtomicId(atomic);
		dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
			RwFrameGetLTM(clumpframe), flags);
		if(dot > 0.0f)
			if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
				return atomic;

		if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
			RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleHiDetailCB_Boat(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_bigVehicleLod1Dist)
		RENDERCALLBACK(atomic);
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq >= ms_bigVehicleLod0Dist &&
	   distsq < ms_bigVehicleLod1Dist){
		flags = GetAtomicId(atomic);
		if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
			dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
				RwFrameGetLTM(clumpframe), flags);
			if(dot > 0.0f)
				return atomic;
		}
		RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq >= ms_bigVehicleLod0Dist &&
	   distsq < ms_bigVehicleLod1Dist){
		flags = GetAtomicId(atomic);
		dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
			RwFrameGetLTM(clumpframe), flags);
		if(dot > 0.0f)
			if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
				return atomic;

		if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
			RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderVehicleReallyLowDetailCB(RpAtomic *atomic)
{
	RpClump *clump;
	float dist;
	int32 alpha;

	clump = RpAtomicGetClump(atomic);
	dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump));
	if(dist >= ms_vehicleLod0Dist){
		alpha = GetClumpAlpha(clump);
		if(alpha == 255)
			RENDERCALLBACK(atomic);
		else
			RenderAlphaAtomic(atomic, alpha);
	}
	return atomic;

}

RpAtomic*
CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq >= ms_bigVehicleLod1Dist)
		RENDERCALLBACK(atomic);
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderTrainHiDetailCB(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_bigVehicleLod1Dist){
		flags = GetAtomicId(atomic);
		if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0){
			dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
				RwFrameGetLTM(clumpframe), flags);
			if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
				return atomic;
		}
		RENDERCALLBACK(atomic);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderTrainHiDetailAlphaCB(RpAtomic *atomic)
{
	RwFrame *clumpframe;
	float distsq, dot;
	uint32 flags;

	clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic));
	distsq = GetDistanceSquaredFromCamera(clumpframe);
	if(distsq < ms_bigVehicleLod1Dist){
		flags = GetAtomicId(atomic);
		dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)),
			RwFrameGetLTM(clumpframe), flags);
		if(distsq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0)
			if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*distsq < dot*dot))
				return atomic;

		if(flags & ATOMIC_FLAG_DRAWLAST){
			if(!InsertAtomicIntoSortedList(atomic, distsq))
				RENDERCALLBACK(atomic);
		}else{
			if(!InsertAtomicIntoSortedList(atomic, distsq + dot))
				RENDERCALLBACK(atomic);
		}
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic)
{
	if(CWorld::Players[0].m_pSkinTexture)
		RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetTextureCB, CWorld::Players[0].m_pSkinTexture);
	RENDERCALLBACK(atomic);
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderPedLowDetailCB(RpAtomic *atomic)
{
	RpClump *clump;
	float dist;
	int32 alpha;

	clump = RpAtomicGetClump(atomic);
	dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump));
	if(dist >= ms_pedLod0Dist){
		alpha = GetClumpAlpha(clump);
		if(alpha == 255)
			RENDERCALLBACK(atomic);
		else
			RenderAlphaAtomic(atomic, alpha);
	}
	return atomic;
}

RpAtomic*
CVisibilityPlugins::RenderPedHiDetailCB(RpAtomic *atomic)
{
	RpClump *clump;
	float dist;
	int32 alpha;

	clump = RpAtomicGetClump(atomic);
	dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump));
	if(dist < ms_pedLod0Dist){
		alpha = GetClumpAlpha(clump);
		if(alpha == 255)
			RENDERCALLBACK(atomic);
		else
			RenderAlphaAtomic(atomic, alpha);
	}
	return atomic;
}

// This is needed for peds with only one clump, i.e. skinned models
// strangely even the xbox version has no such thing
RpAtomic*
CVisibilityPlugins::RenderPedCB(RpAtomic *atomic)
{
	int32 alpha;
	RwV3d cam2atm;

	RwV3dSub(&cam2atm, &RwFrameGetLTM(RpAtomicGetFrame(atomic))->pos, ms_pCameraPosn);
	if(RwV3dDotProduct(&cam2atm, &cam2atm) < ms_pedLod1Dist){
		alpha = GetClumpAlpha(RpAtomicGetClump(atomic));
		if(alpha == 255)
			RENDERCALLBACK(atomic);
		else
			RenderAlphaAtomic(atomic, alpha);
	}
	return atomic;
}

float
CVisibilityPlugins::GetDistanceSquaredFromCamera(RwFrame *frame)
{
	RwMatrix *m;
	RwV3d dist;
	m = RwFrameGetLTM(frame);
	RwV3dSub(&dist, RwMatrixGetPos(m), ms_pCameraPosn);
	return RwV3dDotProduct(&dist, &dist);
}

float
CVisibilityPlugins::GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags)
{
	RwV3d dist;
	float dot, dotdoor;

	// Vehicle forward is the y axis (RwMatrix.up)
	// Vehicle right is the x axis (RwMatrix.right)

	RwV3dSub(&dist, RwMatrixGetPos(atomicMat), ms_pCameraPosn);
	// forward/backward facing
	if(flags & (ATOMIC_FLAG_FRONT | ATOMIC_FLAG_REAR))
		dot = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat));
	// left/right facing
	else if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_RIGHT))
		dot = RwV3dDotProduct(&dist, RwMatrixGetRight(clumpMat));
	else
		dot = 0.0f;
	if(flags & (ATOMIC_FLAG_LEFT | ATOMIC_FLAG_REAR))
		dot = -dot;

	if(flags & (ATOMIC_FLAG_REARDOOR | ATOMIC_FLAG_FRONTDOOR)){
		if(flags & ATOMIC_FLAG_REARDOOR)
			dotdoor = -RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat));
		else if(flags & ATOMIC_FLAG_FRONTDOOR)
			dotdoor = RwV3dDotProduct(&dist, RwMatrixGetUp(clumpMat));
		else
			dotdoor = 0.0f;

		if(dot < 0.0f && dotdoor < 0.0f)
			dot += dotdoor;
		if(dot > 0.0f && dotdoor > 0.0f)
			dot += dotdoor;
	}

	return dot;
}

/* These are all unused */

bool
CVisibilityPlugins::DefaultVisibilityCB(RpClump *clump)
{
	return true;
}

bool
CVisibilityPlugins::FrustumSphereCB(RpClump *clump)
{
	RwSphere sphere;
	RwFrame *frame = RpClumpGetFrame(clump);

	CClumpModelInfo *modelInfo = (CClumpModelInfo*)GetFrameHierarchyId(frame);
	sphere.radius = modelInfo->GetColModel()->boundingSphere.radius;
	sphere.center.x = modelInfo->GetColModel()->boundingSphere.center.x;
	sphere.center.y = modelInfo->GetColModel()->boundingSphere.center.y;
	sphere.center.z = modelInfo->GetColModel()->boundingSphere.center.z;
	RwV3dTransformPoints(&sphere.center, &sphere.center, 1, RwFrameGetLTM(frame));
	return RwCameraFrustumTestSphere(ms_pCamera, &sphere) != rwSPHEREOUTSIDE;
}

bool
CVisibilityPlugins::MloVisibilityCB(RpClump *clump)
{
	RwFrame *frame = RpClumpGetFrame(clump);
	CMloModelInfo *modelInfo = (CMloModelInfo*)GetFrameHierarchyId(frame);
	if (SQR(modelInfo->drawDist) < GetDistanceSquaredFromCamera(frame))
		return false;
	return CVisibilityPlugins::FrustumSphereCB(clump);
}

bool
CVisibilityPlugins::VehicleVisibilityCB(RpClump *clump)
{
	RwFrame *frame = RpClumpGetFrame(clump);
	if (ms_vehicleLod1Dist < GetDistanceSquaredFromCamera(frame))
		return false;
	return FrustumSphereCB(clump);
}

bool
CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump *clump)
{
	return FrustumSphereCB(clump);
}

//
// RW Plugins
//

enum
{
	ID_VISIBILITYATOMIC = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x00),
	ID_VISIBILITYCLUMP  = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x01),
	ID_VISIBILITYFRAME  = MAKECHUNKID(rwVENDORID_ROCKSTAR, 0x02),
};

bool
CVisibilityPlugins::PluginAttach(void)
{
	ms_atomicPluginOffset = RpAtomicRegisterPlugin(sizeof(AtomicExt),
		ID_VISIBILITYATOMIC,
		AtomicConstructor, AtomicDestructor, AtomicCopyConstructor);

	ms_framePluginOffset = RwFrameRegisterPlugin(sizeof(FrameExt),
		ID_VISIBILITYFRAME,
		FrameConstructor, FrameDestructor, FrameCopyConstructor);

	ms_clumpPluginOffset = RpClumpRegisterPlugin(sizeof(ClumpExt),
		ID_VISIBILITYCLUMP,
		ClumpConstructor, ClumpDestructor, ClumpCopyConstructor);

#if GTA_VERSION <= GTA3_PS2_160
	Initialise();
#endif

	return ms_atomicPluginOffset != -1 && ms_clumpPluginOffset != -1;
}

#define ATOMICEXT(o) (RWPLUGINOFFSET(AtomicExt, o, ms_atomicPluginOffset))
#define FRAMEEXT(o) (RWPLUGINOFFSET(FrameExt, o, ms_framePluginOffset))
#define CLUMPEXT(o) (RWPLUGINOFFSET(ClumpExt, o, ms_clumpPluginOffset))

//
// Atomic
//

void*
CVisibilityPlugins::AtomicConstructor(void *object, int32, int32)
{
	ATOMICEXT(object)->modelInfo = nil;
	return object;
}

void*
CVisibilityPlugins::AtomicDestructor(void *object, int32, int32)
{
	return object;
}

void*
CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, int32, int32)
{
	*ATOMICEXT(dst) = *ATOMICEXT(src);
	return dst;
}

void
CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic,
                                       CSimpleModelInfo *modelInfo)
{
	AtomicExt *ext = ATOMICEXT(atomic);
	ext->modelInfo = modelInfo;
	switch (modelInfo->GetModelType()) {
		case MITYPE_SIMPLE:
		case MITYPE_TIME:
			if(modelInfo->m_normalCull)
				SetAtomicRenderCallback(atomic, RenderObjNormalAtomic);
		default: break;
	}
}

CSimpleModelInfo*
CVisibilityPlugins::GetAtomicModelInfo(RpAtomic *atomic)
{
	return ATOMICEXT(atomic)->modelInfo;
}

void
CVisibilityPlugins::SetAtomicFlag(RpAtomic *atomic, int f)
{
	ATOMICEXT(atomic)->flags |= f;
}

void
CVisibilityPlugins::ClearAtomicFlag(RpAtomic *atomic, int f)
{
	ATOMICEXT(atomic)->flags &= ~f;
}

void
CVisibilityPlugins::SetAtomicId(RpAtomic *atomic, int id)
{
	ATOMICEXT(atomic)->flags = id;
}

int
CVisibilityPlugins::GetAtomicId(RpAtomic *atomic)
{
	return ATOMICEXT(atomic)->flags;
}

void
CVisibilityPlugins::SetAtomicRenderCallback(RpAtomic *atomic, RpAtomicCallBackRender cb)
{
	if(cb == nil)
		cb = RENDERCALLBACK;
	RpAtomicSetRenderCallBack(atomic, cb);
}

//
// Frame
//

void*
CVisibilityPlugins::FrameConstructor(void *object, int32, int32)
{
	FRAMEEXT(object)->id = 0;
	return object;
}

void*
CVisibilityPlugins::FrameDestructor(void *object, int32, int32)
{
	return object;
}

void*
CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, int32, int32)
{
	*FRAMEEXT(dst) = *FRAMEEXT(src);
	return dst;
}

void
CVisibilityPlugins::SetFrameHierarchyId(RwFrame *frame, intptr id)
{
	FRAMEEXT(frame)->id = id;
}

intptr
CVisibilityPlugins::GetFrameHierarchyId(RwFrame *frame)
{
	return FRAMEEXT(frame)->id;
}

//
// Clump
//

void*
CVisibilityPlugins::ClumpConstructor(void *object, int32, int32)
{
	ClumpExt *ext = CLUMPEXT(object);
	ext->visibilityCB = DefaultVisibilityCB;
	ext->alpha = 0xFF;
	return object;
}

void*
CVisibilityPlugins::ClumpDestructor(void *object, int32, int32)
{
	return object;
}

void*
CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, int32, int32)
{
	CLUMPEXT(dst)->visibilityCB = CLUMPEXT(src)->visibilityCB;
	return dst;
}

void
CVisibilityPlugins::SetClumpModelInfo(RpClump *clump, CClumpModelInfo *modelInfo)
{
	CVehicleModelInfo *vmi;
	SetFrameHierarchyId(RpClumpGetFrame(clump), (intptr)modelInfo);

	// Unused
	switch (modelInfo->GetModelType()) {
	case MITYPE_MLO:
		CLUMPEXT(clump)->visibilityCB = MloVisibilityCB;
		break;
	case MITYPE_VEHICLE:
		vmi = (CVehicleModelInfo*)modelInfo;
		if(vmi->m_vehicleType == VEHICLE_TYPE_TRAIN ||
		   vmi->m_vehicleType == VEHICLE_TYPE_HELI ||
		   vmi->m_vehicleType == VEHICLE_TYPE_PLANE)
			CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB_BigVehicle;
		else
			CLUMPEXT(clump)->visibilityCB = VehicleVisibilityCB;
		break;
	default: break;
	}
}

CClumpModelInfo*
CVisibilityPlugins::GetClumpModelInfo(RpClump *clump)
{
	return (CClumpModelInfo*)GetFrameHierarchyId(RpClumpGetFrame(clump));
}

void
CVisibilityPlugins::SetClumpAlpha(RpClump *clump, int alpha)
{
	CLUMPEXT(clump)->alpha = alpha;
}

int
CVisibilityPlugins::GetClumpAlpha(RpClump *clump)
{
	return CLUMPEXT(clump)->alpha;
}

bool
CVisibilityPlugins::IsClumpVisible(RpClump *clump)
{
	return CLUMPEXT(clump)->visibilityCB(clump);
}


code:
#pragma once

#include "templates.h"

class CEntity;
class CSimpleModelInfo;
class CClumpModelInfo;

typedef bool (*ClumpVisibilityCB)(RpClump*);

class CVisibilityPlugins
{
public:
	struct AlphaObjectInfo
	{
		union {
			CEntity *entity;
			RpAtomic *atomic;
		};
		float sort;
	};

	static CLinkList<AlphaObjectInfo> m_alphaList;
	static CLinkList<AlphaObjectInfo> m_alphaEntityList;
#ifdef NEW_RENDERER
	static CLinkList<AlphaObjectInfo> m_alphaBuildingList;
#endif
	static RwCamera *ms_pCamera;
	static RwV3d *ms_pCameraPosn;
	static float ms_cullCompsDist;
	static float ms_vehicleLod0Dist;
	static float ms_vehicleLod1Dist;
	static float ms_vehicleFadeDist;
	static float ms_bigVehicleLod0Dist;
	static float ms_bigVehicleLod1Dist;
	static float ms_pedLod0Dist;
	static float ms_pedLod1Dist;
	static float ms_pedFadeDist;

	static void Initialise(void);
	static void Shutdown(void);
	static void InitAlphaEntityList(void);
	static bool InsertEntityIntoSortedList(CEntity *e, float dist);
	static void InitAlphaAtomicList(void);
	static bool InsertAtomicIntoSortedList(RpAtomic *a, float dist);

	static void SetRenderWareCamera(RwCamera *camera);

	static RpAtomic *RenderWheelAtomicCB(RpAtomic *atomic);
	static RpAtomic *RenderObjNormalAtomic(RpAtomic *atomic);
	static RpAtomic *RenderAlphaAtomic(RpAtomic *atomic, int alpha);
	static RpAtomic *RenderFadingAtomic(RpAtomic *atm, float dist);

	static RpAtomic *RenderVehicleHiDetailCB(RpAtomic *atomic);
	static RpAtomic *RenderVehicleHiDetailAlphaCB(RpAtomic *atomic);
	static RpAtomic *RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic);
	static RpAtomic *RenderVehicleHiDetailAlphaCB_BigVehicle(RpAtomic *atomic);
	static RpAtomic *RenderVehicleHiDetailCB_Boat(RpAtomic *atomic);
	static RpAtomic *RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic);
	static RpAtomic *RenderVehicleLowDetailAlphaCB_BigVehicle(RpAtomic *atomic);
	static RpAtomic *RenderVehicleReallyLowDetailCB(RpAtomic *atomic);
	static RpAtomic *RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic);
	static RpAtomic *RenderTrainHiDetailCB(RpAtomic *atomic);
	static RpAtomic *RenderTrainHiDetailAlphaCB(RpAtomic *atomic);

	static RpAtomic *RenderPlayerCB(RpAtomic *atomic);
	static RpAtomic *RenderPedLowDetailCB(RpAtomic *atomic);
	static RpAtomic *RenderPedHiDetailCB(RpAtomic *atomic);
	static RpAtomic *RenderPedCB(RpAtomic *atomic);	// for skinned models with only one clump

	static void RenderAlphaAtomics(void);
	static void RenderFadingEntities(void);

	// All actually unused
	static bool DefaultVisibilityCB(RpClump *clump);
	static bool FrustumSphereCB(RpClump *clump);
	static bool MloVisibilityCB(RpClump *clump);
	static bool VehicleVisibilityCB(RpClump *clump);
	static bool VehicleVisibilityCB_BigVehicle(RpClump *clump);

	static float GetDistanceSquaredFromCamera(RwFrame *frame);
	static float GetDotProductWithCameraVector(RwMatrix *atomicMat, RwMatrix *clumpMat, uint32 flags);

	//
	// RW Plugins
	//

	union AtomicExt
	{
		CSimpleModelInfo *modelInfo;	// used by SimpleModelInfo
		int flags;			// used by ClumpModelInfo
	};
	static void SetAtomicModelInfo(RpAtomic*, CSimpleModelInfo*);
	static CSimpleModelInfo *GetAtomicModelInfo(RpAtomic *atomic);
	static void SetAtomicFlag(RpAtomic*, int);
	static void ClearAtomicFlag(RpAtomic*, int);
	static void SetAtomicId(RpAtomic *atomic, int);
	static int GetAtomicId(RpAtomic *atomic);
	static void SetAtomicRenderCallback(RpAtomic*, RpAtomicCallBackRender);

	static void *AtomicConstructor(void *object, int32 offset, int32 len);
	static void *AtomicDestructor(void *object, int32 offset, int32 len);
	static void *AtomicCopyConstructor(void *dst, const void *src,
	                                   int32 offset, int32 len);
	static int32 ms_atomicPluginOffset;

	struct FrameExt
	{
		// BUG: this is abused to hold a pointer by SetClumpModelInfo
		intptr id;
	};
	static void SetFrameHierarchyId(RwFrame *frame, intptr id);
	static intptr GetFrameHierarchyId(RwFrame *frame);

	static void *FrameConstructor(void *object, int32 offset, int32 len);
	static void *FrameDestructor(void *object, int32 offset, int32 len);
	static void *FrameCopyConstructor(void *dst, const void *src,
	                                  int32 offset, int32 len);
	static int32 ms_framePluginOffset;

	struct ClumpExt
	{
		ClumpVisibilityCB visibilityCB;
		int alpha;
	};
	static void SetClumpModelInfo(RpClump*, CClumpModelInfo*);
	static CClumpModelInfo *GetClumpModelInfo(RpClump*);
	static void SetClumpAlpha(RpClump*, int);
	static int GetClumpAlpha(RpClump*);
	static bool IsClumpVisible(RpClump*);

	static void *ClumpConstructor(void *object, int32 offset, int32 len);
	static void *ClumpDestructor(void *object, int32 offset, int32 len);
	static void *ClumpCopyConstructor(void *dst, const void *src,
	                                  int32 offset, int32 len);
	static int32 ms_clumpPluginOffset;

	static bool PluginAttach(void);
};

RpMaterial *SetAlphaCB(RpMaterial *material, void *data);

license: mit


code:
#include "common.h"
#include "Date.h"

CDate::CDate()
{
	m_nYear = 0;
	m_nSecond = 0;
	m_nMinute = 0;
	m_nHour = 0;
	m_nDay = 0;
	m_nMonth = 0;
}

bool
CDate::operator>(const CDate &right)
{
	if (m_nYear > right.m_nYear)
		return true;
	if (m_nYear != right.m_nYear)
		return false;

	if (m_nMonth > right.m_nMonth)
		return true;
	if (m_nMonth != right.m_nMonth)
		return false;

	if (m_nDay > right.m_nDay)
		return true;
	if (m_nDay != right.m_nDay)
		return false;

	if (m_nHour > right.m_nHour)
		return true;
	if (m_nHour != right.m_nHour)
		return false;

	if (m_nMinute > right.m_nMinute)
		return true;
	if (m_nMinute != right.m_nMinute)
		return false;
	return m_nSecond > right.m_nSecond;
}

bool
CDate::operator<(const CDate &right)
{
	if (m_nYear < right.m_nYear)
		return true;
	if (m_nYear != right.m_nYear)
		return false;

	if (m_nMonth < right.m_nMonth)
		return true;
	if (m_nMonth != right.m_nMonth)
		return false;

	if (m_nDay < right.m_nDay)
		return true;
	if (m_nDay != right.m_nDay)
		return false;

	if (m_nHour < right.m_nHour)
		return true;
	if (m_nHour != right.m_nHour)
		return false;

	if (m_nMinute < right.m_nMinute)
		return true;
	if (m_nMinute != right.m_nMinute)
		return false;
	return m_nSecond < right.m_nSecond;
}

bool
CDate::operator==(const CDate &right)
{
	if (m_nYear != right.m_nYear || m_nMonth != right.m_nMonth || m_nDay != right.m_nDay || m_nHour != right.m_nHour || m_nMinute != right.m_nMinute)
		return false;
	return m_nSecond == right.m_nSecond;
}

void
CDate::PopulateDateFields(int8 &second, int8 &minute, int8 &hour, int8 &day, int8 &month, int16 year)
{
	m_nSecond = second;
	m_nMinute = minute;
	m_nHour = hour;
	m_nDay = day;
	m_nMonth = month;
	m_nYear = year;
}


code:
#pragma once

class CDate
{
public:
	int m_nSecond;
	int m_nMinute;
	int m_nHour;
	int m_nDay;
	int m_nMonth;
	int m_nYear;

	CDate();
	bool operator>(const CDate &right);
	bool operator<(const CDate &right);
	bool operator==(const CDate &right);
	void PopulateDateFields(int8 &second, int8 &minute, int8 &hour, int8 &day, int8 &month, int16 year);
};


code:
#define WITHWINDOWS
#include "common.h"
#include "crossplatform.h"
#include "main.h"

#include "DMAudio.h"
#include "AudioScriptObject.h"
#include "Camera.h"
#include "CarGen.h"
#include "Cranes.h"
#include "Clock.h"
#include "Date.h"
#include "FileMgr.h"
#include "Font.h"
#include "Frontend.h"
#include "GameLogic.h"
#include "Gangs.h"
#include "Garages.h"
#include "GenericGameStorage.h"
#include "Pad.h"
#include "Particle.h"
#include "ParticleObject.h"
#include "PathFind.h"
#include "PCSave.h"
#include "Phones.h"
#include "Pickups.h"
#include "PlayerPed.h"
#include "ProjectileInfo.h"
#include "Pools.h"
#include "Radar.h"
#include "Restart.h"
#include "Script.h"
#include "Stats.h"
#include "Streaming.h"
#include "Timer.h"
#include "TimeStep.h"
#include "Weather.h"
#include "World.h"
#include "Zones.h"

#define BLOCK_COUNT 20
#define SIZE_OF_SIMPLEVARS 0xBC

const uint32 SIZE_OF_ONE_GAME_IN_BYTES = 201729;

#ifdef MISSION_REPLAY
int8 IsQuickSave;
const int PAUSE_SAVE_SLOT = SLOT_COUNT;
#endif

char DefaultPCSaveFileName[260];
char ValidSaveName[260];
char LoadFileName[256];
wchar SlotFileName[SLOT_COUNT][260];
wchar SlotSaveDate[SLOT_COUNT][70];
int CheckSum;
eLevelName m_LevelToLoad;
char SaveFileNameJustSaved[260];
int Slots[SLOT_COUNT+1];
CDate CompileDateAndTime;

bool b_FoundRecentSavedGameWantToLoad;
bool JustLoadedDontFadeInYet;
bool StillToFadeOut;
uint32 TimeStartedCountingForFade;
uint32 TimeToStayFadedBeforeFadeOut = 1750;

#define ReadDataFromBufferPointer(buf, to) memcpy(&to, buf, sizeof(to)); buf += align4bytes(sizeof(to));
#define WriteDataToBufferPointer(buf, from) memcpy(buf, &from, sizeof(from)); buf += align4bytes(sizeof(from));

#define LoadSaveDataBlock()\
do {\
	if (!ReadDataFromFile(file, (uint8 *) &size, 4))\
		return false;\
	size = align4bytes(size);\
	if (!ReadDataFromFile(file, work_buff, size))\
		return false;\
	buf = work_buff;\
} while (0)

#define ReadDataFromBlock(msg,load_func)\
do {\
	debug(msg);\
	ReadDataFromBufferPointer(buf, size);\
	load_func(buf, size);\
	size = align4bytes(size);\
	buf += size;\
} while (0)

#define WriteSaveDataBlock(save_func)\
do {\
	buf = work_buff;\
	reserved = 0;\
	MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\
	save_func(buf, &size);\
	CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\
	if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff))\
		return false;\
	totalSize += buf - work_buff;\
} while (0)

bool
GenericSave(int file)
{
	uint8 *buf, *presize, *postsize;
	uint32 size;
	uint32 reserved;

	uint32 totalSize;

	wchar *lastMissionPassed;
	wchar suffix[6];
	wchar saveName[24];
	SYSTEMTIME saveTime;
	CPad *currPad;

	CheckSum = 0;
	buf = work_buff;
	reserved = 0;

	// Save simple vars
	lastMissionPassed = TheText.Get(CStats::LastMissionPassedName);
	if (lastMissionPassed[0] != '\0') {
		AsciiToUnicode("...'", suffix);
#ifdef FIX_BUGS
		// fix buffer overflow
		int len = UnicodeStrlen(lastMissionPassed);
		if (len > ARRAY_SIZE(saveName)-1)
			len = ARRAY_SIZE(saveName)-1;
		memcpy(saveName, lastMissionPassed, sizeof(wchar) * len);
#else
		TextCopy(saveName, lastMissionPassed);
		int len = UnicodeStrlen(saveName);
#endif
		saveName[len] = '\0';
		if (len > ARRAY_SIZE(saveName)-2)
			TextCopy(&saveName[ARRAY_SIZE(saveName)-ARRAY_SIZE(suffix)], suffix);
		saveName[ARRAY_SIZE(saveName)-1] = '\0';
	}
	WriteDataToBufferPointer(buf, saveName);
	GetLocalTime(&saveTime);
	WriteDataToBufferPointer(buf, saveTime);
#ifdef MISSION_REPLAY
	int32 data = IsQuickSave << 24 | SIZE_OF_ONE_GAME_IN_BYTES;
	WriteDataToBufferPointer(buf, data);
#else
	WriteDataToBufferPointer(buf, SIZE_OF_ONE_GAME_IN_BYTES);
#endif
	WriteDataToBufferPointer(buf, CGame::currLevel);
	WriteDataToBufferPointer(buf, TheCamera.GetPosition().x);
	WriteDataToBufferPointer(buf, TheCamera.GetPosition().y);
	WriteDataToBufferPointer(buf, TheCamera.GetPosition().z);
	WriteDataToBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute);
	WriteDataToBufferPointer(buf, CClock::ms_nLastClockTick);
	WriteDataToBufferPointer(buf, CClock::ms_nGameClockHours);
	WriteDataToBufferPointer(buf, CClock::ms_nGameClockMinutes);
	currPad = CPad::GetPad(0);
	WriteDataToBufferPointer(buf, currPad->Mode);
	WriteDataToBufferPointer(buf, CTimer::m_snTimeInMilliseconds);
	WriteDataToBufferPointer(buf, CTimer::ms_fTimeScale);
	WriteDataToBufferPointer(buf, CTimer::ms_fTimeStep);
	WriteDataToBufferPointer(buf, CTimer::ms_fTimeStepNonClipped);
	WriteDataToBufferPointer(buf, CTimer::m_FrameCounter);
	WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeStep);
	WriteDataToBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate);
	WriteDataToBufferPointer(buf, CTimeStep::ms_fTimeScale);
	WriteDataToBufferPointer(buf, CWeather::OldWeatherType);
	WriteDataToBufferPointer(buf, CWeather::NewWeatherType);
	WriteDataToBufferPointer(buf, CWeather::ForcedWeatherType);
	WriteDataToBufferPointer(buf, CWeather::InterpolationValue);
	WriteDataToBufferPointer(buf, CompileDateAndTime.m_nSecond);
	WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMinute);
	WriteDataToBufferPointer(buf, CompileDateAndTime.m_nHour);
	WriteDataToBufferPointer(buf, CompileDateAndTime.m_nDay);
	WriteDataToBufferPointer(buf, CompileDateAndTime.m_nMonth);
	WriteDataToBufferPointer(buf, CompileDateAndTime.m_nYear);
	WriteDataToBufferPointer(buf, CWeather::WeatherTypeInList);
#ifdef COMPATIBLE_SAVES
	// converted to float for compatibility with original format
	// TODO: maybe remove this? not really gonna break anything vital
	float f = TheCamera.CarZoomIndicator;
	WriteDataToBufferPointer(buf, f);
	f = TheCamera.PedZoomIndicator;
	WriteDataToBufferPointer(buf, f);
#else
	WriteDataToBufferPointer(buf, TheCamera.CarZoomIndicator);
	WriteDataToBufferPointer(buf, TheCamera.PedZoomIndicator);
#endif
	assert(buf - work_buff == SIZE_OF_SIMPLEVARS);

	// Save scripts, block is nested within the same block as simple vars for some reason
	presize = buf;
	buf += 4;
	postsize = buf;
	CTheScripts::SaveAllScripts(buf, &size);
	CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);
	if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff))
		return false;

	totalSize = buf - work_buff;

	// Save the rest
	WriteSaveDataBlock(CPools::SavePedPool);
	WriteSaveDataBlock(CGarages::Save);
	WriteSaveDataBlock(CPools::SaveVehiclePool);
	WriteSaveDataBlock(CPools::SaveObjectPool);
	WriteSaveDataBlock(ThePaths.Save);
	WriteSaveDataBlock(CCranes::Save);
	WriteSaveDataBlock(CPickups::Save);
	WriteSaveDataBlock(gPhoneInfo.Save);
	WriteSaveDataBlock(CRestart::SaveAllRestartPoints);
	WriteSaveDataBlock(CRadar::SaveAllRadarBlips);
	WriteSaveDataBlock(CTheZones::SaveAllZones);
	WriteSaveDataBlock(CGangs::SaveAllGangData);
	WriteSaveDataBlock(CTheCarGenerators::SaveAllCarGenerators);
	WriteSaveDataBlock(CParticleObject::SaveParticle);
	WriteSaveDataBlock(cAudioScriptObject::SaveAllAudioScriptObjects);
	WriteSaveDataBlock(CWorld::Players[CWorld::PlayerInFocus].SavePlayerInfo);
	WriteSaveDataBlock(CStats::SaveStats);
	WriteSaveDataBlock(CStreaming::MemoryCardSave);
	WriteSaveDataBlock(CPedType::Save);

	// sure just write garbage data repeatedly ...
#ifndef THIS_IS_STUPID
	memset(work_buff, 0, sizeof(work_buff));
#endif

	// Write padding
	for (int i = 0; i < 4; i++) {
		size = align4bytes(SIZE_OF_ONE_GAME_IN_BYTES - totalSize - 4);
		if (size > sizeof(work_buff))
			size = sizeof(work_buff);
		if (size > 4) {
			if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, size))
				return false;
			totalSize += size;
		}
	}

	// Write checksum and close
	CFileMgr::Write(file, (const char *) &CheckSum, sizeof(CheckSum));
	if (CFileMgr::GetErrorReadWrite(file)) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE;
		if (!CloseFile(file))
			PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE;

		return false;
	}

	return true;
}

bool
GenericLoad()
{
	uint8 *buf;
	int32 file;
	uint32 size;
#ifdef MISSION_REPLAY
	int8 qs;
#endif

	int32 saveSize;
	CPad *currPad;

	// Load SimpleVars and Scripts
	CheckSum = 0;
	CDate dummy; // unused
	CPad::ResetCheats();
	if (!ReadInSizeofSaveFileBuffer(file, size))
		return false;
	size = align4bytes(size);
	ReadDataFromFile(file, work_buff, size);
	buf = (work_buff + 0x40);
	ReadDataFromBufferPointer(buf, saveSize);
#ifdef MISSION_REPLAY // a hack to keep compatibility but get new data from save
	qs = saveSize >> 24;
#endif
	ReadDataFromBufferPointer(buf, CGame::currLevel);
	ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().x);
	ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().y);
	ReadDataFromBufferPointer(buf, TheCamera.GetMatrix().GetPosition().z);
	ReadDataFromBufferPointer(buf, CClock::ms_nMillisecondsPerGameMinute);
	ReadDataFromBufferPointer(buf, CClock::ms_nLastClockTick);
	ReadDataFromBufferPointer(buf, CClock::ms_nGameClockHours);
	ReadDataFromBufferPointer(buf, CClock::ms_nGameClockMinutes);
	currPad = CPad::GetPad(0);
	ReadDataFromBufferPointer(buf, currPad->Mode);
	ReadDataFromBufferPointer(buf, CTimer::m_snTimeInMilliseconds);
	ReadDataFromBufferPointer(buf, CTimer::ms_fTimeScale);
	ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStep);
	ReadDataFromBufferPointer(buf, CTimer::ms_fTimeStepNonClipped);
	ReadDataFromBufferPointer(buf, CTimer::m_FrameCounter);
	ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeStep);
	ReadDataFromBufferPointer(buf, CTimeStep::ms_fFramesPerUpdate);
	ReadDataFromBufferPointer(buf, CTimeStep::ms_fTimeScale);
	ReadDataFromBufferPointer(buf, CWeather::OldWeatherType);
	ReadDataFromBufferPointer(buf, CWeather::NewWeatherType);
	ReadDataFromBufferPointer(buf, CWeather::ForcedWeatherType);
	ReadDataFromBufferPointer(buf, CWeather::InterpolationValue);
	ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nSecond);
	ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nMinute);
	ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nHour);
	ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nDay);
	ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nMonth);
	ReadDataFromBufferPointer(buf, CompileDateAndTime.m_nYear);
	ReadDataFromBufferPointer(buf, CWeather::WeatherTypeInList);
#ifdef COMPATIBLE_SAVES
	// converted to float for compatibility with original format
	// TODO: maybe remove this? not really gonna break anything vital
	float f;
	ReadDataFromBufferPointer(buf, f);
	TheCamera.CarZoomIndicator = f;
	ReadDataFromBufferPointer(buf, f);
	TheCamera.PedZoomIndicator = f;
#else
	ReadDataFromBufferPointer(buf, TheCamera.CarZoomIndicator);
	ReadDataFromBufferPointer(buf, TheCamera.PedZoomIndicator);
#endif
	assert(buf - work_buff == SIZE_OF_SIMPLEVARS);
#ifdef MISSION_REPLAY
	WaitForSave = 0;
	if (FrontEndMenuManager.m_nCurrSaveSlot == PAUSE_SAVE_SLOT && qs == 3)
		WaitForMissionActivate = CTimer::GetTimeInMilliseconds() + 2000;
#endif
	ReadDataFromBlock("Loading Scripts \n", CTheScripts::LoadAllScripts);

	// Load the rest
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading PedPool \n", CPools::LoadPedPool);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Garages \n", CGarages::Load);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Vehicles \n", CPools::LoadVehiclePool);
	LoadSaveDataBlock();
	CProjectileInfo::RemoveAllProjectiles();
	CObject::DeleteAllTempObjects();
	ReadDataFromBlock("Loading Objects \n", CPools::LoadObjectPool);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Paths \n", ThePaths.Load);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Cranes \n", CCranes::Load);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Pickups \n", CPickups::Load);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Phoneinfo \n", gPhoneInfo.Load);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Restart \n", CRestart::LoadAllRestartPoints);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Radar Blips \n", CRadar::LoadAllRadarBlips);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Zones \n", CTheZones::LoadAllZones);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Gang Data \n", CGangs::LoadAllGangData);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Car Generators \n", CTheCarGenerators::LoadAllCarGenerators);
	CParticle::ReloadConfig();
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Particles \n", CParticleObject::LoadParticle);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading AudioScript Objects \n", cAudioScriptObject::LoadAllAudioScriptObjects);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Player Info \n", CWorld::Players[CWorld::PlayerInFocus].LoadPlayerInfo);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Stats \n", CStats::LoadStats);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading Streaming Stuff \n", CStreaming::MemoryCardLoad);
	LoadSaveDataBlock();
	ReadDataFromBlock("Loading PedType Stuff \n", CPedType::Load);

	DMAudio.SetMusicMasterVolume(CMenuManager::m_PrefsMusicVolume);
	DMAudio.SetEffectsMasterVolume(CMenuManager::m_PrefsSfxVolume);
	if (!CloseFile(file)) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE;
		return false;
	}

	DoGameSpecificStuffAfterSucessLoad();
	debug("Game successfully loaded \n");
	return true;
}

bool
ReadInSizeofSaveFileBuffer(int32 &file, uint32 &size)
{
	file = CFileMgr::OpenFile(LoadFileName, "rb");
	if (file == 0) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN;
		return false;
	}
	CFileMgr::Read(file, (const char*)&size, sizeof(size));
	if (CFileMgr::GetErrorReadWrite(file)) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ;
		if (!CloseFile(file))
			PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE;
		return false;
	}
	return true;
}

bool
ReadDataFromFile(int32 file, uint8 *buf, uint32 size)
{
	if (file == 0) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN;
		return false;
	}
	size_t read_size = CFileMgr::Read(file, (const char*)buf, size);
	if (CFileMgr::GetErrorReadWrite(file) || read_size != size) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ;
		if (!CloseFile(file))
			PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE;
		return false;
	}
	return true;
}

bool
CloseFile(int32 file)
{
	return CFileMgr::CloseFile(file) == 0;
}

void
DoGameSpecificStuffAfterSucessLoad()
{
	StillToFadeOut = true;
	JustLoadedDontFadeInYet = true;
	CTheScripts::Process();
}

bool
CheckSlotDataValid(int32 slot)
{
	PcSaveHelper.nErrorCode = SAVESTATUS_SUCCESSFUL;
	if (CheckDataNotCorrupt(slot, LoadFileName)) {
		CStreaming::DeleteAllRwObjects();
		return true;
	}

	PcSaveHelper.nErrorCode = SAVESTATUS_ERR_DATA_INVALID;
	return false;
}

void
MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize)
{
	presize = buf;
	buf += sizeof(uint32);
	postsize = buf;
}

void
CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size)
{
	memcpy(buf, &size, sizeof(size));
	size = align4bytes(size);
	postbuf2 += size;
	postbuf = postbuf2;
}

void
DoGameSpecificStuffBeforeSave()
{
	CGameLogic::PassTime(360);
	CPlayerPed *ped = FindPlayerPed();
	ped->m_fCurrentStamina = ped->m_fMaxStamina;
	CGame::TidyUpMemory(true, false);
}

void
MakeValidSaveName(int32 slot)
{
	ValidSaveName[0] = '\0';
	sprintf(ValidSaveName, "%s%i", DefaultPCSaveFileName, slot + 1);
	strncat(ValidSaveName, ".b", 5);
}

wchar *
GetSavedGameDateAndTime(int32 slot)
{
	return SlotSaveDate[slot];
}

wchar *
GetNameOfSavedGame(int32 slot)
{
	return SlotFileName[slot];
}

bool
CheckDataNotCorrupt(int32 slot, char *name)
{
#ifdef FIX_BUGS
	char filename[MAX_PATH];
#else
	char filename[100];
#endif

	int32 blocknum = 0;
	eLevelName level = LEVEL_GENERIC;
	CheckSum = 0;
	uint32 bytes_processed = 0;
	sprintf(filename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b");
	int file = CFileMgr::OpenFile(filename, "rb");
	if (file == 0)
		return false;
	strcpy(name, filename);
	while (SIZE_OF_ONE_GAME_IN_BYTES - sizeof(uint32) > bytes_processed && blocknum < 40) {
		int32 blocksize;
		if (!ReadDataFromFile(file, (uint8*)&blocksize, sizeof(blocksize))) {
			CloseFile(file);
			return false;
		}
		if (blocksize > align4bytes(sizeof(work_buff)))
			blocksize = sizeof(work_buff) - sizeof(uint32);
		if (!ReadDataFromFile(file, work_buff, align4bytes(blocksize))) {
			CloseFile(file);
			return false;
		}

		CheckSum += ((uint8*)&blocksize)[0];
		CheckSum += ((uint8*)&blocksize)[1];
		CheckSum += ((uint8*)&blocksize)[2];
		CheckSum += ((uint8*)&blocksize)[3];
		uint8 *_work_buf = work_buff;
		for (int i = 0; i < align4bytes(blocksize); i++) {
			CheckSum += *_work_buf++;
			bytes_processed++;
		}

		if (blocknum == 0)
			memcpy(&level, work_buff+4, sizeof(level));
		blocknum++;
	}
	int32 _checkSum;
	if (ReadDataFromFile(file, (uint8*)&_checkSum, sizeof(_checkSum))) {
		if (CloseFile(file)) {
			if (CheckSum == _checkSum) {
				m_LevelToLoad = level;
				return true;
			}
			return false;
		}
		return false;
	}

	CloseFile(file);
	return false;
}

bool
RestoreForStartLoad()
{
	uint8 buf[999];

	int file = CFileMgr::OpenFile(LoadFileName, "rb");
	if (file == 0) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN;
		return false;
	}
	ReadDataFromFile(file, buf, sizeof(buf));
	if (CFileMgr::GetErrorReadWrite(file)) {
		PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ;
		if (!CloseFile(file))
			PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE;
		return false;
	} else {
		uint8 *_buf = buf + sizeof(int32) + sizeof(wchar[24]) + sizeof(SYSTEMTIME) + sizeof(SIZE_OF_ONE_GAME_IN_BYTES);
		ReadDataFromBufferPointer(_buf, CGame::currLevel);
		ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().x);
		ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().y);
		ReadDataFromBufferPointer(_buf, TheCamera.GetMatrix().GetPosition().z);
		ISLAND_LOADING_IS(LOW)
		{
			CStreaming::RemoveUnusedBigBuildings(CGame::currLevel);
			CStreaming::RemoveUnusedBuildings(CGame::currLevel);
		}
		CCollision::SortOutCollisionAfterLoad();
		ISLAND_LOADING_IS(LOW)
		{
			CStreaming::RequestBigBuildings(CGame::currLevel);
			CStreaming::LoadAllRequestedModels(false);
			CStreaming::HaveAllBigBuildingsLoaded(CGame::currLevel);
			CGame::TidyUpMemory(true, false);
		}
		if (CloseFile(file)) {
			return true;
		} else {
			PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE;
			return false;
		}
	}
}

int
align4bytes(int32 size)
{
	return (size + 3) & 0xFFFFFFFC;
}

#ifdef FIX_INCOMPATIBLE_SAVES
#define LoadSaveDataBlockNoCheck(buf, file, size) \
do { \
	CFileMgr::Read(file, (const char *)&size, sizeof(size)); \
	size = align4bytes(size); \
	CFileMgr::Read(file, (const char *)work_buff, size); \
	buf = work_buff; \
} while(0)

#define WriteSavaDataBlockNoFunc(buf, file, size) \
do { \
	if (!PcSaveHelper.PcClassSaveRoutine(file, buf, size)) \
		goto fail; \
	totalSize += size; \
} while(0)

#define FixSaveDataBlock(fix_func, file, size) \
do { \
	ReadDataFromBufferPointer(buf, size); \
	memset(work_buff2, 0, sizeof(work_buff2)); \
	buf2 = work_buff2; \
	reserved = 0; \
	MakeSpaceForSizeInBufferPointer(presize, buf2, postsize); \
	fix_func(save_type, buf, buf2, &size); \
	CopySizeAndPreparePointer(presize, buf2, postsize, reserved, size); \
	if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff2, buf2 - work_buff2)) \
		goto fail; \
	totalSize += buf2 - work_buff2; \
} while(0)

#define ReadDataFromBufferPointerWithSize(buf, to, size) memcpy(&to, buf, size); buf += align4bytes(size)

#define ReadBuf(buf, to) memcpy(&to, buf, sizeof(to)); buf += sizeof(to)
#define WriteBuf(buf, from) memcpy(buf, &from, sizeof(from)); buf += sizeof(from)
#define CopyBuf(from, to, size) memcpy(to, from, size); to += (size); from += (size)
#define CopyPtr(from, to) memcpy(to, from, 4); to += 4; from += 8
#define SkipBuf(buf, size) buf += (size)
#define SkipBoth(from, to, size) to += (size); from += (size)
#define SkipPtr(from, to) to += 4; from += 8

// unfortunately we need a 2nd buffer of the same size to store the fixed output ...
static uint8 work_buff2[sizeof(work_buff)];

enum
{
	SAVE_TYPE_NONE = 0,
	SAVE_TYPE_32_BIT = 1,
	SAVE_TYPE_64_BIT = 2,
	SAVE_TYPE_MSVC = 4,
	SAVE_TYPE_GCC = 8,
};

uint8
GetSaveType(char *savename)
{
	uint8 save_type = SAVE_TYPE_NONE;
	int file = CFileMgr::OpenFile(savename, "rb");

	uint32 size;
	CFileMgr::Read(file, (const char *)&size, sizeof(size));

	uint8 *buf = work_buff;
	CFileMgr::Read(file, (const char *)work_buff, size); // simple vars + scripts

	LoadSaveDataBlockNoCheck(buf, file, size); // ped pool

	LoadSaveDataBlockNoCheck(buf, file, size); // garages
	ReadDataFromBufferPointer(buf, size);

	// store for later after we know how much data we need to skip
	ReadDataFromBufferPointerWithSize(buf, work_buff2, size);

	LoadSaveDataBlockNoCheck(buf, file, size); // vehicle pool
	LoadSaveDataBlockNoCheck(buf, file, size); // object pool
	LoadSaveDataBlockNoCheck(buf, file, size); // paths

	LoadSaveDataBlockNoCheck(buf, file, size); // cranes

	CFileMgr::CloseFile(file);

	ReadDataFromBufferPointer(buf, size);

	if (size == 1032)
		save_type |= SAVE_TYPE_32_BIT;
	else if (size == 1160)
		save_type |= SAVE_TYPE_64_BIT;
	else
		assert(0); // this should never happen

	buf = work_buff2;

	buf += 760; // skip everything before the first garage
	buf += save_type & SAVE_TYPE_32_BIT ? 28 : 40; // skip first garage up to m_fX1

	// now the values we want to verify
	float fX1, fX2, fY1, fY2, fZ1, fZ2;

	ReadBuf(buf, fX1);
	ReadBuf(buf, fX2);
	ReadBuf(buf, fY1);
	ReadBuf(buf, fY2);
	ReadBuf(buf, fZ1);
	ReadBuf(buf, fZ2);

	if (fX1 == CRUSHER_GARAGE_X1 && fX2 == CRUSHER_GARAGE_X2 &&
		fY1 == CRUSHER_GARAGE_Y1 && fY2 == CRUSHER_GARAGE_Y2 &&
		fZ1 == CRUSHER_GARAGE_Z1 && fZ2 == CRUSHER_GARAGE_Z2)
		save_type |= SAVE_TYPE_MSVC;
	else
		save_type |= SAVE_TYPE_GCC;

	return save_type;
}

static void
FixGarages(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size)
{
	// hardcoded: 5484
	// x86 msvc: 5240
	// x86 gcc: 5040
	// amd64 msvc: 5880
	// amd64 gcc: 5808

	uint8 *buf_start = buf;
	uint8 *buf2_start = buf2;
	uint32 read;
	uint32 written = 5240;

	if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_GCC)
		read = 5040;
	else if (save_type & SAVE_TYPE_64_BIT && save_type & SAVE_TYPE_GCC)
		read = 5808;
	else
		read = 5880;

	uint32 ptrsize = save_type & SAVE_TYPE_32_BIT ? 4 : 8;

	CopyBuf(buf, buf2, 4 * 6);
	CopyBuf(buf, buf2, 4 * TOTAL_COLLECTCARS_GARAGES);
	CopyBuf(buf, buf2, 4);

	if (save_type & SAVE_TYPE_GCC)
	{
		for (int32 i = 0; i < NUM_GARAGE_STORED_CARS; i++)
		{
#define FixStoredCar(buf, buf2) \
do { \
	CopyBuf(buf, buf2, 4 + sizeof(CVector) + sizeof(CVector)); \
	uint8 nFlags8; \
	ReadBuf(buf, nFlags8); \
	int32 nFlags32 = nFlags8; \
	WriteBuf(buf2, nFlags32); \
	CopyBuf(buf, buf2, 1 * 6); \
	SkipBuf(buf, 1); \
	SkipBuf(buf2, 2); \
} while(0)

			FixStoredCar(buf, buf2);
			FixStoredCar(buf, buf2);
			FixStoredCar(buf, buf2);

#undef FixStoredCar
		}
	}
	else
	{
		CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS);
		CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS);
		CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS);
	}

	for (int32 i = 0; i < NUM_GARAGES; i++)
	{
		// skip the last 5 garages in 64bit builds without FIX_GARAGE_SIZE since they weren't actually saved and are unused
		if (save_type & SAVE_TYPE_64_BIT && *size == 5484 && i >= NUM_GARAGES - 5)
		{
			SkipBuf(buf, 160); // sizeof(CGarage) on x64
			SkipBuf(buf2, 140); // sizeof(CGarage) on x86
		}
		else
		{
			CopyBuf(buf, buf2, 1 * 6);
			SkipBoth(buf, buf2, 2);
			CopyBuf(buf, buf2, 4);
			SkipBuf(buf, ptrsize - 4); // write 4 bytes padding if 8 byte pointer, if not, write 0
			SkipBuf(buf, ptrsize * 2);
			SkipBuf(buf2, 4 * 2);
			CopyBuf(buf, buf2, 1 * 7);
			SkipBoth(buf, buf2, 1);
			CopyBuf(buf, buf2, 4 * 15 + 1);
			SkipBoth(buf, buf2, 3);
			SkipBuf(buf, ptrsize * 2);
			SkipBuf(buf2, 4 * 2);

			if (save_type & SAVE_TYPE_GCC)
				SkipBuf(buf, save_type & SAVE_TYPE_64_BIT ? 36 + 4 : 36); // sizeof(CStoredCar) on gcc 64/32 before fix
			else
				SkipBuf(buf, sizeof(CStoredCar));

			SkipBuf(buf2, sizeof(CStoredCar));
		}
	}

	*size = 0;

	assert(buf - buf_start == read);
	assert(buf2 - buf2_start == written);

#ifdef FIX_GARAGE_SIZE
	*size = (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CGarages::CarTypesCollected) + sizeof(uint32) + 3 * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage));
#else
	*size = 5484;
#endif
}

static void
FixCranes(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size)
{
	uint8 *buf_start = buf;
	uint8 *buf2_start = buf2;
	uint32 read = 2 * sizeof(uint32) + 0x480; // sizeof(aCranes)
	uint32 written = 2 * sizeof(uint32) + 0x400; // see CRANES_SAVE_SIZE

	CopyBuf(buf, buf2, 4 + 4);

	for (int32 i = 0; i < NUM_CRANES; i++)
	{
		CopyPtr(buf, buf2);
		CopyPtr(buf, buf2);
		CopyBuf(buf, buf2, 15 * 4 + sizeof(CVector) * 3 + sizeof(CVector2D));
		CopyPtr(buf, buf2);
		CopyBuf(buf, buf2, 4 + 7 * 1);
		SkipBuf(buf, 5);
		SkipBuf(buf2, 1);
	}

	*size = 0;

	assert(buf - buf_start == read);
	assert(buf2 - buf2_start == written);

	*size = written;
}

static void
FixPickups(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size)
{
	uint8 *buf_start = buf;
	uint8 *buf2_start = buf2;
	uint32 read = 0x3480 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // sizeof(aPickUps)
	uint32 written = 0x24C0 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // see PICKUPS_SAVE_SIZE

	for (int32 i = 0; i < NUMPICKUPS; i++)
	{
		CopyBuf(buf, buf2, 1 + 1 + 2);
		SkipBuf(buf, 4);
		CopyPtr(buf, buf2);
		CopyBuf(buf, buf2, 4 + 2 + 2 + sizeof(CVector));
		SkipBuf(buf, 4);
	}

	CopyBuf(buf, buf2, 2);
	SkipBoth(buf, buf2, 2);

	CopyBuf(buf, buf2, NUMCOLLECTEDPICKUPS * 4);

	*size = 0;

	assert(buf - buf_start == read);
	assert(buf2 - buf2_start == written);

	*size = written;
}

static void
FixPhoneInfo(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size)
{
	uint8 *buf_start = buf;
	uint8 *buf2_start = buf2;
	uint32 read = 0x1138; // sizeof(CPhoneInfo)
	uint32 written = 0xA30; // see PHONEINFO_SAVE_SIZE

	CopyBuf(buf, buf2, 4 + 4);

	for (int32 i = 0; i < NUMPHONES; i++)
	{
		CopyBuf(buf, buf2, sizeof(CVector));
		SkipBuf(buf, 4);
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		CopyBuf(buf, buf2, 4);
		SkipBuf(buf, 4);
		CopyPtr(buf, buf2);
		CopyBuf(buf, buf2, 4 + 1);
		SkipBoth(buf, buf2, 3);
	}

	*size = 0;

	assert(buf - buf_start == read);
	assert(buf2 - buf2_start == written);

	*size = written;
}

static void
FixZones(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size)
{
	uint8 *buf_start = buf;
	uint8 *buf2_start = buf2;
	uint32 read = 11300; // see SaveAllZones
	uint32 written = 10100; // see SaveAllZones

	CopyBuf(buf, buf2, 1 * 4);

	SkipBuf(buf, 4);
	uint32 hdr_size = 10100 - (1 * 4 + 4); // see SaveAllZones
	WriteBuf(buf2, hdr_size);

	CopyBuf(buf, buf2, 4 * 2 + 2);
	SkipBoth(buf, buf2, 2);

#define FixOneZone(buf, buf2) \
do { \
	CopyBuf(buf, buf2, 8 + 8 * 4 + 2 * 2); \
	SkipBuf(buf, 4); \
	CopyPtr(buf, buf2); \
	CopyPtr(buf, buf2); \
	CopyPtr(buf, buf2); \
} while(0)

	for (int32 i = 0; i < NUMZONES; i++)
		FixOneZone(buf, buf2);

	CopyBuf(buf, buf2, sizeof(CZoneInfo) * NUMZONES * 2);
	CopyBuf(buf, buf2, 2 + 2);

	for (int32 i = 0; i < NUMMAPZONES; i++)
		FixOneZone(buf, buf2);

	CopyBuf(buf, buf2, 2 * NUMAUDIOZONES);
	CopyBuf(buf, buf2, 2 + 2);

#undef FixOneZone

	*size = 0;

	assert(buf - buf_start == read);
	assert(buf2 - buf2_start == written);

	*size = written;
}

static void
FixParticles(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size)
{
	uint8 *buf_start = buf;
	uint8 *buf2_start = buf2;

	int32 numObjects;
	ReadBuf(buf, numObjects);
	WriteBuf(buf2, numObjects);

	uint32 read = 0xA0 * (numObjects + 1) + 4; // sizeof(CParticleObject)
	uint32 written = 0x88 * (numObjects + 1) + 4; // see PARTICLE_OBJECT_SIZEOF

	for (int32 i = 0; i < numObjects; i++)
	{
		// CPlaceable
		SkipPtr(buf, buf2);
		CopyBuf(buf, buf2, 4 * 4 * 4);
		SkipPtr(buf, buf2);
		CopyBuf(buf, buf2, 1);
		SkipBuf(buf, 7);
		SkipBuf(buf2, 3);

		// CParticleObject
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		SkipPtr(buf, buf2);
		CopyBuf(buf, buf2, 4 * 3 + 2 * 1 + 2 * 2);
		SkipBoth(buf, buf2, 2);
		CopyBuf(buf, buf2, sizeof(CVector) + 2 * 4 + sizeof(CRGBA) + 2 * 1);
		SkipBoth(buf, buf2, 2);
	}

	SkipBuf(buf, 0xA0); // sizeof(CParticleObject)
	SkipBuf(buf2, 0x88); // see PARTICLE_OBJECT_SIZEOF

	*size = 0;

	assert(buf - buf_start == read);
	assert(buf2 - buf2_start == written);

	*size = written;
}

bool
FixSave(int32 slot, uint8 save_type)
{
	if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_MSVC)
		return true;

	bool success = false;

	uint8 *buf, *presize, *postsize, *buf2;
	uint32 size;
	uint32 reserved;

	uint32 totalSize;

	char savename[MAX_PATH];
	char savename_bak[MAX_PATH];

	sprintf(savename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b");
	sprintf(savename_bak, "%s%i%s.%lld.bak", DefaultPCSaveFileName, slot + 1, ".b", time(nil));

	assert(caserename(savename, savename_bak) == 0);

	int file_in = CFileMgr::OpenFile(savename_bak, "rb");
	int file_out = CFileMgr::OpenFileForWriting(savename);

	CheckSum = 0;
	totalSize = 0;

	CFileMgr::Read(file_in, (const char *)&size, sizeof(size));
	size = align4bytes(size);

	buf = work_buff;
	CFileMgr::Read(file_in, (const char *)work_buff, size); // simple vars + scripts

	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // ped pool
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // garages
	FixSaveDataBlock(FixGarages, file_out, size); // garages need to be fixed in either case

	LoadSaveDataBlockNoCheck(buf, file_in, size); // vehicle pool
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // object pool
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // paths
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // cranes
	if (save_type & SAVE_TYPE_64_BIT)
		FixSaveDataBlock(FixCranes, file_out, size);
	else
		WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // pickups
	if (save_type & SAVE_TYPE_64_BIT)
		FixSaveDataBlock(FixPickups, file_out, size);
	else
		WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // phoneinfo
	if (save_type & SAVE_TYPE_64_BIT)
		FixSaveDataBlock(FixPhoneInfo, file_out, size);
	else
		WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // restart
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // radar blips
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // zones
	if (save_type & SAVE_TYPE_64_BIT)
		FixSaveDataBlock(FixZones, file_out, size);
	else
		WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // gang data
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // car generators
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // particles
	if (save_type & SAVE_TYPE_64_BIT)
		FixSaveDataBlock(FixParticles, file_out, size);
	else
		WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // audio script objects
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // player info
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // stats
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // streaming
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	LoadSaveDataBlockNoCheck(buf, file_in, size); // ped type
	WriteSavaDataBlockNoFunc(buf, file_out, size);

	memset(work_buff, 0, sizeof(work_buff));

	for (int i = 0; i < 4; i++) {
		size = align4bytes(SIZE_OF_ONE_GAME_IN_BYTES - totalSize - 4);
		if (size > sizeof(work_buff))
			size = sizeof(work_buff);
		if (size > 4) {
			if (!PcSaveHelper.PcClassSaveRoutine(file_out, work_buff, size))
				goto fail;
			totalSize += size;
		}
	}

	if (!CFileMgr::Write(file_out, (const char *)&CheckSum, sizeof(CheckSum)))
		goto fail;

	success = true;

fail:;
	CFileMgr::CloseFile(file_in);
	CFileMgr::CloseFile(file_out);

	return success;
}

#undef LoadSaveDataBlockNoCheck
#undef WriteSavaDataBlockNoFunc
#undef FixSaveDataBlock
#undef ReadDataFromBufferPointerWithSize
#undef ReadBuf
#undef WriteBuf
#undef CopyBuf
#undef CopyPtr
#undef SkipBuf
#undef SkipBoth
#undef SkipPtr
#endif

#ifdef MISSION_REPLAY

void DisplaySaveResult(int unk, char* name)
{}

bool SaveGameForPause(int type)
{
	if (AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL)
		return false;
	if (type != SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY && WaitForSave > CTimer::GetTimeInMilliseconds())
		return false;
	WaitForSave = 0;
	if (gGameState != GS_PLAYING_GAME || CTheScripts::IsPlayerOnAMission() || CStats::LastMissionPassedName[0] == '\0') {
		DisplaySaveResult(3, CStats::LastMissionPassedName);
		return false;
	}
	IsQuickSave = type;
	MissionStartTime = 0;
	int res = PcSaveHelper.SaveSlot(PAUSE_SAVE_SLOT);
	PcSaveHelper.PopulateSlotInfo();
	IsQuickSave = 0;
	DisplaySaveResult(res, CStats::LastMissionPassedName);
	return true;
}
#endif


code:
#pragma once

#include "Game.h"
#include "PCSave.h"

#define SLOT_COUNT (8)

bool GenericSave(int file);
bool GenericLoad();
bool ReadInSizeofSaveFileBuffer(int32 &file, uint32 &size);
bool ReadDataFromFile(int32 file, uint8 *buf, uint32 size);
bool CloseFile(int32 file);
void DoGameSpecificStuffAfterSucessLoad();
bool CheckSlotDataValid(int32 slot);
void MakeSpaceForSizeInBufferPointer(uint8 *&presize, uint8 *&buf, uint8 *&postsize);
void CopySizeAndPreparePointer(uint8 *&buf, uint8 *&postbuf, uint8 *&postbuf2, uint32 &unused, uint32 &size);
void DoGameSpecificStuffBeforeSave();
void MakeValidSaveName(int32 slot);
wchar *GetSavedGameDateAndTime(int32 slot);
wchar *GetNameOfSavedGame(int32 slot);
bool CheckDataNotCorrupt(int32 slot, char *name);
bool RestoreForStartLoad();
int align4bytes(int32 size);

#ifdef FIX_INCOMPATIBLE_SAVES
uint8 GetSaveType(char *savename);
bool FixSave(int32 slot, uint8 save_type);
#endif

extern class CDate CompileDateAndTime;

extern char DefaultPCSaveFileName[260];
extern char ValidSaveName[260];
extern char LoadFileName[256];
extern wchar SlotFileName[SLOT_COUNT][260];
extern wchar SlotSaveDate[SLOT_COUNT][70];
extern int CheckSum;
extern enum eLevelName m_LevelToLoad;
extern int Slots[SLOT_COUNT+1];

extern bool b_FoundRecentSavedGameWantToLoad;
extern bool JustLoadedDontFadeInYet;
extern bool StillToFadeOut;
extern uint32 TimeStartedCountingForFade;
extern uint32 TimeToStayFadedBeforeFadeOut;

extern char SaveFileNameJustSaved[260]; // 8F2570

const char TopLineEmptyFile[] = "THIS FILE IS NOT VALID YET";

#ifdef MISSION_REPLAY
extern int8 IsQuickSave; // originally int

bool SaveGameForPause(int);

enum {
	SAVE_TYPE_NORMAL,
	SAVE_TYPE_QUICKSAVE,
	SAVE_TYPE_2,
	SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY
};

#endif