code:
#pragma once
#include "Placeable.h"

class CEntity;
class CPed;
class CAutomobile;
class CGarage;

extern int16 DebugCamMode;

enum
{
	NUMBER_OF_VECTORS_FOR_AVERAGE = 2,
	MAX_NUM_OF_SPLINETYPES = 4,
	MAX_NUM_OF_NODES = 800	// for trains
};

#define DEFAULT_NEAR (0.9f)
enum
{
	CAM_ZOOM_1STPRS,
	CAM_ZOOM_1,
	CAM_ZOOM_2,
	CAM_ZOOM_3,
	CAM_ZOOM_TOPDOWN,
	CAM_ZOOM_CINEMATIC,
};

#ifdef FREE_CAM // LCS values
#define FREE_CAR_ZOOM_VALUE_1 (-1.0f)
#define FREE_CAR_ZOOM_VALUE_2 (2.0f)
#define FREE_CAR_ZOOM_VALUE_3 (6.0f)

#define FREE_BOAT_ZOOM_VALUE_1 (-2.41f)
#define FREE_BOAT_ZOOM_VALUE_2 (6.49f)
#define FREE_BOAT_ZOOM_VALUE_3 (15.0f)
#endif

#define DEFAULT_CAR_ZOOM_VALUE_1 (0.05f)
#define DEFAULT_CAR_ZOOM_VALUE_2 (1.9f)
#define DEFAULT_CAR_ZOOM_VALUE_3 (3.9f)

const float DefaultFOV = 70.0f; // beta: 80.0f

class CCam
{
public:
	enum
	{
		MODE_NONE = 0,
		MODE_TOPDOWN,
		MODE_GTACLASSIC,
		MODE_BEHINDCAR,
		MODE_FOLLOWPED,
		MODE_AIMING,
		MODE_DEBUG,
		MODE_SNIPER,
		MODE_ROCKETLAUNCHER,
		MODE_MODELVIEW,
		MODE_BILL,
		MODE_SYPHON,
		MODE_CIRCLE,
		MODE_CHEESYZOOM,
		MODE_WHEELCAM,
		MODE_FIXED,
		MODE_1STPERSON,
		MODE_FLYBY,
		MODE_CAM_ON_A_STRING,
		MODE_REACTION,
		MODE_FOLLOW_PED_WITH_BIND,
		MODE_CHRIS,
		MODE_BEHINDBOAT,
		MODE_PLAYER_FALLEN_WATER,
		MODE_CAM_ON_TRAIN_ROOF,
		MODE_CAM_RUNNING_SIDE_TRAIN,
		MODE_BLOOD_ON_THE_TRACKS,
		MODE_IM_THE_PASSENGER_WOOWOO,
		MODE_SYPHON_CRIM_IN_FRONT,
		MODE_PED_DEAD_BABY,
		MODE_PILLOWS_PAPS,
		MODE_LOOK_AT_CARS,
		MODE_ARRESTCAM_ONE,
		MODE_ARRESTCAM_TWO,
		MODE_M16_1STPERSON,
		MODE_SPECIAL_FIXED_FOR_SYPHON,
		MODE_FIGHT_CAM,
		MODE_TOP_DOWN_PED,
		MODE_SNIPER_RUNABOUT,
		MODE_ROCKETLAUNCHER_RUNABOUT,
		MODE_1STPERSON_RUNABOUT,
		MODE_M16_1STPERSON_RUNABOUT,
		MODE_FIGHT_CAM_RUNABOUT,
		MODE_EDITOR,
		MODE_HELICANNON_1STPERSON, // vice city leftover
	};

	bool    bBelowMinDist; //used for follow ped mode
	bool    bBehindPlayerDesired; //used for follow ped mode
	bool    m_bCamLookingAtVector;
	bool    m_bCollisionChecksOn;
	bool    m_bFixingBeta; //used for camera on a string
	bool    m_bTheHeightFixerVehicleIsATrain;
	bool    LookBehindCamWasInFront;
	bool    LookingBehind;
	bool    LookingLeft;
	bool    LookingRight;
	bool    ResetStatics; //for interpolation type stuff to work
	bool    Rotating;

	int16   Mode;                   // CameraMode
	uint32  m_uiFinishTime;

	int     m_iDoCollisionChecksOnFrameNum;
	int     m_iDoCollisionCheckEveryNumOfFrames;
	int     m_iFrameNumWereAt;
	int     m_iRunningVectorArrayPos;
	int     m_iRunningVectorCounter;
	int     DirectionWasLooking;

	float   f_max_role_angle; //=DEGTORAD(5.0f);
	float   f_Roll; //used for adding a slight roll to the camera in the
	float	f_rollSpeed;
	float   m_fSyphonModeTargetZOffSet;
	float	m_fRoadOffSet;
	float   m_fAmountFractionObscured;
	float   m_fAlphaSpeedOverOneFrame;
	float   m_fBetaSpeedOverOneFrame;
	float   m_fBufferedTargetBeta;
	float   m_fBufferedTargetOrientation;
	float   m_fBufferedTargetOrientationSpeed;
	float   m_fCamBufferedHeight;
	float   m_fCamBufferedHeightSpeed;
	float   m_fCloseInPedHeightOffset;
	float   m_fCloseInPedHeightOffsetSpeed;
	float   m_fCloseInCarHeightOffset;
	float   m_fCloseInCarHeightOffsetSpeed;
	float   m_fDimensionOfHighestNearCar;
	float   m_fDistanceBeforeChanges;
	float   m_fFovSpeedOverOneFrame;
	float   m_fMinDistAwayFromCamWhenInterPolating;
	float   m_fPedBetweenCameraHeightOffset;
	float   m_fPlayerInFrontSyphonAngleOffSet;
	float   m_fRadiusForDead;
	float   m_fRealGroundDist; //used for follow ped mode
	float   m_fTargetBeta;
	float   m_fTimeElapsedFloat;

	float   m_fTransitionBeta;
	float   m_fTrueBeta;
	float   m_fTrueAlpha;
	float   m_fInitialPlayerOrientation; //used for first person

	float   Alpha;
	float   AlphaSpeed;
	float   FOV;
	float   FOVSpeed;
	float   Beta;
	float   BetaSpeed;
	float   Distance;
	float   DistanceSpeed;
	float   CA_MIN_DISTANCE;
	float   CA_MAX_DISTANCE;
	float   SpeedVar;

	CVector m_cvecSourceSpeedOverOneFrame;
	CVector m_cvecTargetSpeedOverOneFrame;
	CVector m_cvecUpOverOneFrame;

	CVector m_cvecTargetCoorsForFudgeInter;
	CVector m_cvecCamFixedModeVector;
	CVector m_cvecCamFixedModeSource;
	CVector m_cvecCamFixedModeUpOffSet;
	CVector m_vecLastAboveWaterCamPosition;  //helper for when the player has gone under the water
	CVector m_vecBufferedPlayerBodyOffset;

	// The three vectors that determine this camera for this frame
	CVector Front;                                                  // Direction of looking in
	CVector Source;                                                 // Coors in world space
	CVector SourceBeforeLookBehind;
	CVector Up;                                                     // Just that
	CVector m_arrPreviousVectors[NUMBER_OF_VECTORS_FOR_AVERAGE];    // used to average stuff
	CEntity *CamTargetEntity;

	float       m_fCameraDistance;
	float       m_fIdealAlpha;
	float       m_fPlayerVelocity;
	CAutomobile  *m_pLastCarEntered; // So interpolation works
	CPed         *m_pLastPedLookedAt;// So interpolation works
	bool        m_bFirstPersonRunAboutActive;

	CCam(void) { Init(); }
	void Init(void);
	void Process(void);
	void ProcessSpecialHeightRoutines(void);
	void GetVectorsReadyForRW(void);
	CVector DoAverageOnVector(const CVector &vec);
	float GetPedBetaAngleForClearView(const CVector &Target, float Dist, float BetaOffset, bool checkBuildings, bool checkVehicles, bool checkPeds, bool checkObjects, bool checkDummies);
	void WorkOutCamHeightWeeCar(CVector &TargetCoors, float TargetOrientation);
	void WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight);
	bool RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation);
	bool FixCamIfObscured(CVector &TargetCoors, float TargetHeight, float TargetOrientation);
	void Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist);
	void FixCamWhenObscuredByVehicle(const CVector &TargetCoors);
	void LookBehind(void);
	void LookLeft(void);
	void LookRight(void);
	void ClipIfPedInFrontOfPlayer(void);
	void KeepTrackOfTheSpeed(const CVector &source, const CVector &target, const CVector &up, const float &alpha, const float &beta, const float &fov);
	bool Using3rdPersonMouseCam(void);
	bool GetWeaponFirstPersonOn(void);
	bool IsTargetInWater(const CVector &CamCoors);
	void AvoidWallsTopDownPed(const CVector &TargetCoors, const CVector &Offset, float *Adjuster, float *AdjusterSpeed, float yDistLimit);
	void PrintMode(void);

	void Process_Debug(const CVector&, float, float, float);
#ifdef GTA_SCENE_EDIT
	void Process_Editor(const CVector&, float, float, float);
#endif
	void Process_ModelView(const CVector &CameraTarget, float, float, float);
	void Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_FollowPedWithMouse(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_BehindCar(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_TopDown(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar);
	void Process_TopDownPed(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Rocket(const CVector &CameraTarget, float, float, float);
	void Process_M16_1stPerson(const CVector &CameraTarget, float, float, float);
	void Process_1stPerson(const CVector &CameraTarget, float, float, float);
	void Process_1rstPersonPedOnPC(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Sniper(const CVector &CameraTarget, float, float, float);
	void Process_Syphon(const CVector &CameraTarget, float, float, float);
	void Process_Syphon_Crim_In_Front(const CVector &CameraTarget, float, float, float);
	void Process_BehindBoat(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Fight_Cam(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_FlyBy(const CVector&, float, float, float);
	void Process_WheelCam(const CVector&, float, float, float);
	void Process_Fixed(const CVector &CameraTarget, float, float, float);
	void Process_Player_Fallen_Water(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Circle(const CVector &CameraTarget, float, float, float);
	void Process_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float);
	void ProcessPedsDeadBaby(void);
	bool ProcessArrestCamOne(void);
	bool ProcessArrestCamTwo(void);

	/* Some of the unused PS2 cams */
	void Process_Chris_With_Binding_PlusRotation(const CVector &CameraTarget, float, float, float);
	void Process_ReactionCam(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_FollowPed_WithBinding(const CVector &CameraTarget, float TargetOrientation, float, float);
	// TODO:
	// CCam::Process_CushyPillows_Arse
	// CCam::Process_Look_At_Cars
	// CCam::Process_CheesyZoom
	// CCam::Process_Aiming
	void Process_Bill(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar);
	void Process_Im_The_Passenger_Woo_Woo(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Blood_On_The_Tracks(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Cam_Running_Side_Train(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_Cam_On_Train_Roof(const CVector &CameraTarget, float TargetOrientation, float, float);

	// custom stuff
	void Process_FollowPed_Rotation(const CVector &CameraTarget, float TargetOrientation, float, float);
	void Process_FollowCar_SA(const CVector &CameraTarget, float TargetOrientation, float, float);
};

VALIDATE_SIZE(CCam, 0x1A4);

class CCamPathSplines
{
public:
	enum {MAXPATHLENGTH=800};
	float m_arr_PathData[MAXPATHLENGTH];
	CCamPathSplines(void);
};

struct CTrainCamNode
{
	CVector m_cvecCamPosition;
	CVector m_cvecPointToLookAt;
	CVector m_cvecMinPointInRange;
	CVector m_cvecMaxPointInRange;
	float m_fDesiredFOV;
	float m_fNearClip;
};

struct CQueuedMode
{
	int16 Mode;
	float Duration;
	int16 MinZoom;
	int16 MaxZoom;
};

enum
{
	LOOKING_BEHIND,
	LOOKING_LEFT,
	LOOKING_RIGHT,
	LOOKING_FORWARD,
};

enum
{
	// TODO: find better names
	FADE_0,	// faded in
	FADE_1,	// mid fade
	FADE_2,	// faded out

	// Direction
	FADE_OUT = 0,
	FADE_IN,
	FADE_NONE
};

enum
{
	MOTION_BLUR_NONE = 0,
	MOTION_BLUR_SNIPER,
	MOTION_BLUR_LIGHT_SCENE,
	MOTION_BLUR_SECURITY_CAM,
	MOTION_BLUR_CUT_SCENE,
	MOTION_BLUR_INTRO,
	MOTION_BLUR_INTRO2,
	MOTION_BLUR_SNIPER_ZOOM,
	MOTION_BLUR_INTRO3,
	MOTION_BLUR_INTRO4,
};

enum
{
	NONE = 0,
	INTERPOLATION,
	JUMP_CUT
};

enum
{
	CAMCONTROL_GAME,
	CAMCONTROL_SCRIPT,
	CAMCONTROL_OBBE
};

class CCamera : public CPlaceable
{
public:
	bool m_bAboveGroundTrainNodesLoaded;
	bool m_bBelowGroundTrainNodesLoaded;
	bool m_bCamDirectlyBehind;
	bool m_bCamDirectlyInFront;
	bool m_bCameraJustRestored;
	bool m_bcutsceneFinished;
	bool m_bCullZoneChecksOn;
	bool m_bFirstPersonBeingUsed;
	bool m_bUnknown;
	bool m_bIdleOn;
	bool m_bInATunnelAndABigVehicle;
	bool m_bInitialNodeFound;
	bool m_bInitialNoNodeStaticsSet;
	bool m_bIgnoreFadingStuffForMusic;
	bool m_bPlayerIsInGarage;
	bool m_bJustCameOutOfGarage;
	bool m_bJustInitalised;
	bool m_bJust_Switched;
	bool m_bLookingAtPlayer;
	bool m_bLookingAtVector;
	bool m_bMoveCamToAvoidGeom;
	bool m_bObbeCinematicPedCamOn;
	bool m_bObbeCinematicCarCamOn;
	bool m_bRestoreByJumpCut;
	bool m_bUseNearClipScript;
	bool m_bStartInterScript;
	bool m_bStartingSpline;
	bool m_bTargetJustBeenOnTrain;
	bool m_bTargetJustCameOffTrain;
	bool m_bUseSpecialFovTrain;
	bool m_bUseTransitionBeta;
	bool m_bUseScriptZoomValuePed;
	bool m_bUseScriptZoomValueCar;
	bool m_bWaitForInterpolToFinish;
	bool m_bItsOkToLookJustAtThePlayer;
	bool m_bWantsToSwitchWidescreenOff;
	bool m_WideScreenOn;
	bool m_1rstPersonRunCloseToAWall;
	bool m_bHeadBob;
	bool m_bFailedCullZoneTestPreviously;

	bool m_FadeTargetIsSplashScreen;

	bool WorldViewerBeingUsed;
	uint8 ActiveCam;
	uint32 m_uiCamShakeStart;
	uint32 m_uiFirstPersonCamLastInputTime;

	uint32 m_uiLongestTimeInMill;
	uint32 m_uiNumberOfTrainCamNodes;
	uint8   m_uiTransitionJUSTStarted;
	uint8   m_uiTransitionState;        // 0:one mode 1:transition

	uint32 m_uiTimeLastChange;
	uint32 m_uiTimeWeEnteredIdle;
	uint32 m_uiTimeTransitionStart;
	uint32 m_uiTransitionDuration;
	int m_BlurBlue;
	int m_BlurGreen;
	int m_BlurRed;
	int m_BlurType;

	uint32    unknown;	// some counter having to do with music
	int m_iWorkOutSpeedThisNumFrames;
	int m_iNumFramesSoFar;

	int m_iCurrentTrainCamNode;
	int m_motionBlur;
	int m_imotionBlurAddAlpha;
	int m_iCheckCullZoneThisNumFrames;
	int m_iZoneCullFrameNumWereAt;
	int WhoIsInControlOfTheCamera;

	float CamFrontXNorm;
	float CamFrontYNorm;
#ifdef FIX_BUGS
	int32 CarZoomIndicator;
#else
	float CarZoomIndicator;
#endif
	float CarZoomValue;
	float CarZoomValueSmooth;

	float DistanceToWater;
#ifndef PS2_CAM_TRANSITION
	float FOVDuringInter;
#endif
	float LODDistMultiplier;
	float GenerationDistMultiplier;
#ifndef PS2_CAM_TRANSITION
	float m_fAlphaSpeedAtStartInter;
	float m_fAlphaWhenInterPol;
	float m_fAlphaDuringInterPol;
	float m_fBetaDuringInterPol;
	float m_fBetaSpeedAtStartInter;
	float m_fBetaWhenInterPol;
	float m_fFOVWhenInterPol;
	float m_fFOVSpeedAtStartInter;
	float m_fStartingBetaForInterPol;
	float m_fStartingAlphaForInterPol;
#endif
	float m_PedOrientForBehindOrInFront;
	float m_CameraAverageSpeed;
	float m_CameraSpeedSoFar;
	float m_fCamShakeForce;
	float m_fCarZoomValueScript;
	float m_fFovForTrain;
	float m_fFOV_Wide_Screen;
	float m_fNearClipScript;
	float m_fOldBetaDiff;
	float m_fPedZoomValue;

	float m_fPedZoomValueScript;
	float m_fPedZoomValueSmooth;
	float m_fPositionAlongSpline;
	float m_ScreenReductionPercentage;
	float m_ScreenReductionSpeed;
	float m_AlphaForPlayerAnim1rstPerson;
	float Orientation;
#ifdef FIX_BUGS
	int32 PedZoomIndicator;
#else
	float PedZoomIndicator;
#endif
	float PlayerExhaustion;
	float SoundDistUp, SoundDistLeft, SoundDistRight;
	float SoundDistUpAsRead, SoundDistLeftAsRead, SoundDistRightAsRead;
	float SoundDistUpAsReadOld, SoundDistLeftAsReadOld, SoundDistRightAsReadOld;
	float m_fWideScreenReductionAmount;
	float m_fStartingFOVForInterPol;

	// not static yet
	float m_fMouseAccelHorzntl;// acceleration multiplier for 1st person controls
	float m_fMouseAccelVertical;// acceleration multiplier for 1st person controls
	float m_f3rdPersonCHairMultX;
	float m_f3rdPersonCHairMultY;

	CCam Cams[3];
	CGarage *pToGarageWeAreIn;
	CGarage *pToGarageWeAreInForHackAvoidFirstPerson;
	CQueuedMode m_PlayerMode;
	CQueuedMode PlayerWeaponMode;
	CVector m_PreviousCameraPosition;
	CVector m_RealPreviousCameraPosition;
	CVector m_cvecAimingTargetCoors;
	CVector m_vecFixedModeVector;
	CVector m_vecFixedModeSource;
	CVector m_vecFixedModeUpOffSet;
	CVector m_vecCutSceneOffset;
#ifndef PS2_CAM_TRANSITION
	CVector m_cvecStartingSourceForInterPol;
	CVector m_cvecStartingTargetForInterPol;
	CVector m_cvecStartingUpForInterPol;
	CVector m_cvecSourceSpeedAtStartInter;
	CVector m_cvecTargetSpeedAtStartInter;
	CVector m_cvecUpSpeedAtStartInter;
	CVector m_vecSourceWhenInterPol;
	CVector m_vecTargetWhenInterPol;
	CVector m_vecUpWhenInterPol;
#endif
	CVector m_vecGameCamPos;
#ifndef PS2_CAM_TRANSITION
	CVector SourceDuringInter;
	CVector TargetDuringInter;
	CVector UpDuringInter;
#endif
	RwCamera *m_pRwCamera;
	CEntity *pTargetEntity;
	CCamPathSplines m_arrPathArray[MAX_NUM_OF_SPLINETYPES];
	CTrainCamNode m_arrTrainCamNode[MAX_NUM_OF_NODES];
	CMatrix m_cameraMatrix;
	bool m_bGarageFixedCamPositionSet;
	bool m_vecDoingSpecialInterPolation;
	bool m_bScriptParametersSetForInterPol;
	bool m_bFading;
	bool m_bMusicFading;
	CMatrix m_viewMatrix;
	CVector m_vecFrustumNormals[4];
	CVector m_vecOldSourceForInter;
	CVector m_vecOldFrontForInter;
	CVector m_vecOldUpForInter;
	float m_vecOldFOVForInter;
	float m_fFLOATingFade;
	float m_fFLOATingFadeMusic;
	float m_fTimeToFadeOut;
	float m_fTimeToFadeMusic;
	float m_fFractionInterToStopMoving;
	float m_fFractionInterToStopCatchUp;
	float m_fGaitSwayBuffer;
	float m_fScriptPercentageInterToStopMoving;
	float m_fScriptPercentageInterToCatchUp;

	uint32  m_fScriptTimeForInterPolation;

	int16   m_iFadingDirection;
	int     m_iModeObbeCamIsInForCar;
	int16   m_iModeToGoTo;
	int16   m_iMusicFadingDirection;
	int16   m_iTypeOfSwitch;

	uint32 m_uiFadeTimeStarted;
	uint32 m_uiFadeTimeStartedMusic;

	static bool m_bUseMouse3rdPerson;
#ifdef FREE_CAM
	static bool bFreeCam;
#endif

	// High level and misc
	CCamera(void);
	CCamera(float);
	void Init(void);
	void Process(void);
	void CamControl(void);
	void UpdateTargetEntity(void);
	void UpdateSoundDistances(void);
	void InitialiseCameraForDebugMode(void);
	void CamShake(float strength, float x, float y, float z);
	bool Get_Just_Switched_Status() { return m_bJust_Switched; }

	// Who's in control
	void TakeControl(CEntity *target, int16 mode, int16 typeOfSwitch, int32 controller);
	void TakeControlNoEntity(const CVector &position, int16 typeOfSwitch, int32 controller);
	void TakeControlWithSpline(int16 typeOfSwitch);
	void Restore(void);
	void RestoreWithJumpCut(void);
	void SetCamPositionForFixedMode(const CVector &Source, const CVector &UppOffSet);

	// Transition
	void StartTransition(int16 mode);
	void StartTransitionWhenNotFinishedInter(int16 mode);
	void StoreValuesDuringInterPol(CVector &source, CVector &target, CVector &up, float &FOV);

	// Widescreen borders
	void SetWideScreenOn(void);
	void SetWideScreenOff(void);
	void ProcessWideScreenOn(void);
	void DrawBordersForWideScreen(void);

	// Obbe's cam
	bool IsItTimeForNewcam(int32 obbeMode, int32 time);
	bool TryToStartNewCamMode(int32 obbeMode);
	void DontProcessObbeCinemaCamera(void);
	void ProcessObbeCinemaCameraCar(void);
	void ProcessObbeCinemaCameraPed(void);

	// Train
	void LoadTrainCamNodes(char const *name);
	void Process_Train_Camera_Control(void);

	// Script
	void LoadPathSplines(int file);
	void FinishCutscene(void);
	float GetPositionAlongSpline(void) { return m_fPositionAlongSpline; }
	uint32 GetCutSceneFinishTime(void);
	void SetCamCutSceneOffSet(const CVector &pos);
	void SetPercentAlongCutScene(float percent);
	void SetParametersForScriptInterpolation(float stopMoving, float catchUp, int32 time);
	void SetZoomValueFollowPedScript(int16 dist);
	void SetZoomValueCamStringScript(int16 dist);
	void SetNearClipScript(float);

	// Fading
	void ProcessFade(void);
	void ProcessMusicFade(void);
	void Fade(float timeout, int16 direction);
	void SetFadeColour(uint8 r, uint8 g, uint8 b);
	bool GetFading(void);
	int GetFadingDirection(void);
	int GetScreenFadeStatus(void);

	// Motion blur
	void RenderMotionBlur(void);
	void SetMotionBlur(int r, int g, int b, int a, int type);
	void SetMotionBlurAlpha(int a);

	// Player looking and aiming
	int GetLookDirection(void);
	bool GetLookingForwardFirstPerson(void);
	bool GetLookingLRBFirstPerson(void);
	void SetCameraDirectlyInFrontForFollowPed_CamOnAString(void);
	void SetCameraDirectlyBehindForFollowPed_CamOnAString(void);
	void SetNewPlayerWeaponMode(int16 mode, int16 minZoom, int16 maxZoom);
	void ClearPlayerWeaponMode(void);
	void UpdateAimingCoors(CVector const &coors);
	bool Find3rdPersonCamTargetVector(float dist, CVector pos, CVector &source, CVector &target);
	float Find3rdPersonQuickAimPitch(void);

	// Physical camera
	void SetRwCamera(RwCamera *cam);
	const CMatrix& GetCameraMatrix(void) { return m_cameraMatrix; }
	CVector &GetGameCamPosition(void) { return m_vecGameCamPos; }
	void CalculateDerivedValues(void);
	bool IsPointVisible(const CVector &center, const CMatrix *mat);
	bool IsSphereVisible(const CVector &center, float radius, Const CMatrix *mat);
	bool IsSphereVisible(const CVector &center, float radius);
	bool IsBoxVisible(CVUVECTOR *box, const CMatrix *mat);
};

VALIDATE_SIZE(CCamera, 0xE9D8);

extern CCamera TheCamera;

void CamShakeNoPos(CCamera*, float);
void MakeAngleLessThan180(float &Angle);
void WellBufferMe(float Target, float *CurrentValue, float *CurrentSpeed, float MaxSpeed, float Acceleration, bool IsAngle);


code:
#ifdef _WIN32
#define WITHWINDOWS
#include "common.h"

#include "CdStream.h"
#include "rwcore.h"
#include "RwHelper.h"
#include "MemoryMgr.h"

struct CdReadInfo
{
	uint32 nSectorOffset;
	uint32 nSectorsToRead;
	void *pBuffer;
	char field_C;
	bool bLocked;
	bool bReading;
	int32 nStatus;
	HANDLE pDoneSemaphore; // used for CdStreamSync
	HANDLE hFile;
	OVERLAPPED Overlapped;
};

VALIDATE_SIZE(CdReadInfo, 0x30);

char gCdImageNames[MAX_CDIMAGES+1][64];
int32 gNumImages;
int32 gNumChannels;

HANDLE gImgFiles[MAX_CDIMAGES];

HANDLE _gCdStreamThread;
HANDLE gCdStreamSema; // released when we have new thing to read(so channel is set)
DWORD _gCdStreamThreadId;

CdReadInfo *gpReadInfo;
Queue gChannelRequestQ;

int32 lastPosnRead;

BOOL _gbCdStreamOverlapped;
BOOL _gbCdStreamAsync;
DWORD _gdwCdStreamFlags;

DWORD WINAPI CdStreamThread(LPVOID lpThreadParameter);

void
CdStreamInitThread(void)
{
	SetLastError(0);

	if ( gNumChannels > 0 )
	{
		for ( int32 i = 0; i < gNumChannels; i++ )
		{
			gpReadInfo[i].pDoneSemaphore = CreateSemaphore(nil, 0, 2, nil);

			if ( gpReadInfo[i].pDoneSemaphore == nil )
			{
				printf("%s: failed to create sync semaphore\n", "cdvd_stream");
				ASSERT(0);
				return;
			}
		}
	}

	gChannelRequestQ.items = (int32 *)LocalAlloc(LMEM_ZEROINIT, sizeof(int32) * (gNumChannels + 1));
	gChannelRequestQ.head = 0;
	gChannelRequestQ.tail = 0;
	gChannelRequestQ.size = gNumChannels + 1;
	ASSERT(gChannelRequestQ.items != nil );

#ifdef FIX_BUGS
	gCdStreamSema = CreateSemaphore(nil, 0, 5, nil);
#else
	gCdStreamSema = CreateSemaphore(nil, 0, 5, "CdStream");
#endif

	if ( gCdStreamSema == nil )
	{
		printf("%s: failed to create stream semaphore\n", "cdvd_stream");
		ASSERT(0);
		return;
	}

	_gCdStreamThread = CreateThread(nil, 64*1024/*64KB*/, CdStreamThread, nil, CREATE_SUSPENDED, &_gCdStreamThreadId);

	if ( _gCdStreamThread == nil )
	{
		printf("%s: failed to create streaming thread\n", "cdvd_stream");
		ASSERT(0);
		return;
	}

	SetThreadPriority(_gCdStreamThread, GetThreadPriority(GetCurrentThread()) - 1);

	ResumeThread(_gCdStreamThread);
}

void
CdStreamInit(int32 numChannels)
{
	DWORD SectorsPerCluster;
	DWORD BytesPerSector;
	DWORD NumberOfFreeClusters;
	DWORD TotalNumberOfClusters;

	GetDiskFreeSpace(nil, &SectorsPerCluster, &BytesPerSector, &NumberOfFreeClusters, &TotalNumberOfClusters);

	_gdwCdStreamFlags = 0;

#ifndef FIX_BUGS // this just slows down streaming
	if ( BytesPerSector <= CDSTREAM_SECTOR_SIZE )
	{
		_gdwCdStreamFlags |= FILE_FLAG_NO_BUFFERING;
		debug("Using no buffered loading for streaming\n");
	}
#endif

	_gbCdStreamOverlapped = TRUE;

	_gdwCdStreamFlags |= FILE_FLAG_OVERLAPPED;

	_gbCdStreamAsync = FALSE;

	void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, BytesPerSector);
	ASSERT( pBuffer != nil );

	SetLastError(0);

	gNumImages = 0;

	gNumChannels = numChannels;

	gpReadInfo = (CdReadInfo *)LocalAlloc(LMEM_ZEROINIT, sizeof(CdReadInfo) * numChannels);
	ASSERT( gpReadInfo != nil );

	debug("%s: read info %p\n", "cdvd_stream", gpReadInfo);

	CdStreamAddImage("MODELS\\GTA3.IMG");

	int32 nStatus = CdStreamRead(0, pBuffer, 0, 1);

	CdStreamRemoveImages();

	if ( nStatus == STREAM_SUCCESS )
	{
		_gbCdStreamAsync = TRUE;

		debug("Using async loading for streaming\n");
	}
	else
	{
		_gdwCdStreamFlags &= ~FILE_FLAG_OVERLAPPED;

		_gbCdStreamOverlapped = FALSE;

		_gbCdStreamAsync = TRUE;

		debug("Using sync loading for streaming\n");
	}

	CdStreamInitThread();

	ASSERT( pBuffer != nil );
	RwFreeAlign(pBuffer);
}

uint32
GetGTA3ImgSize(void)
{
	ASSERT( gImgFiles[0] != nil );
	return (uint32)GetFileSize(gImgFiles[0], nil);
}

void
CdStreamShutdown(void)
{
	if ( _gbCdStreamAsync )
	{
		LocalFree(gChannelRequestQ.items);
		CloseHandle(gCdStreamSema);
		CloseHandle(_gCdStreamThread);

		for ( int32 i = 0; i < gNumChannels; i++ )
			CloseHandle(gpReadInfo[i].pDoneSemaphore);
	}

	LocalFree(gpReadInfo);
}

int32
CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size)
{
	ASSERT( channel < gNumChannels );
	ASSERT( buffer != nil );

	lastPosnRead = size + offset;

	ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES );
	HANDLE hImage = gImgFiles[_GET_INDEX(offset)];
	ASSERT( hImage != nil );

	CdReadInfo *pChannel = &gpReadInfo[channel];
	ASSERT( pChannel != nil );

	pChannel->hFile = hImage;

	SetLastError(0);

	if ( _gbCdStreamAsync )
	{
		if ( pChannel->nSectorsToRead != 0 || pChannel->bReading )
			return STREAM_NONE;

		pChannel->nStatus = STREAM_NONE;
		pChannel->nSectorOffset = _GET_OFFSET(offset);
		pChannel->nSectorsToRead = size;
		pChannel->pBuffer = buffer;
		pChannel->bLocked = 0;

		AddToQueue(&gChannelRequestQ, channel);

		if ( !ReleaseSemaphore(gCdStreamSema, 1, nil) )
			printf("Signal Sema Error\n");

		return STREAM_SUCCESS;
	}

	if ( _gbCdStreamOverlapped )
	{
		ASSERT( channel < gNumChannels );
		CdReadInfo *pChannel = &gpReadInfo[channel];
		ASSERT( pChannel != nil );

		pChannel->Overlapped.Offset = _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE;

		if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, NULL, &pChannel->Overlapped)
					&& GetLastError() != ERROR_IO_PENDING )
			return STREAM_NONE;
		else
			return STREAM_SUCCESS;
	}

#ifdef BIG_IMG
	LARGE_INTEGER liDistanceToMove;
	liDistanceToMove.QuadPart = _GET_OFFSET(offset);
	liDistanceToMove.QuadPart *= CDSTREAM_SECTOR_SIZE;
	SetFilePointerEx(hImage, liDistanceToMove, nil, FILE_BEGIN);
#else
	SetFilePointer(hImage, _GET_OFFSET(offset) * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN);
#endif

	DWORD NumberOfBytesRead;

	if ( !ReadFile(hImage, buffer, size * CDSTREAM_SECTOR_SIZE, &NumberOfBytesRead, nil) )
		return STREAM_NONE;
	else
		return STREAM_SUCCESS;
}

int32
CdStreamGetStatus(int32 channel)
{
	ASSERT( channel < gNumChannels );
	CdReadInfo *pChannel = &gpReadInfo[channel];
	ASSERT( pChannel != nil );

	if ( _gbCdStreamAsync )
	{
		if ( pChannel->bReading )
			return STREAM_READING;

		if ( pChannel->nSectorsToRead != 0 )
			return STREAM_WAITING;

		if ( pChannel->nStatus != STREAM_NONE )
		{
			int32 status = pChannel->nStatus;

			pChannel->nStatus = STREAM_NONE;

			return status;
		}

		return STREAM_NONE;
	}

	if ( _gbCdStreamOverlapped )
	{
		ASSERT( pChannel->hFile != nil );
		if ( WaitForSingleObjectEx(pChannel->hFile, 0, TRUE) == WAIT_OBJECT_0 )
			return STREAM_NONE;
		else
			return STREAM_READING;
	}

	return STREAM_NONE;
}

int32
CdStreamGetLastPosn(void)
{
	return lastPosnRead;
}

// wait for channel to finish reading
int32
CdStreamSync(int32 channel)
{
	ASSERT( channel < gNumChannels );
	CdReadInfo *pChannel = &gpReadInfo[channel];
	ASSERT( pChannel != nil );

	if ( _gbCdStreamAsync )
	{
		if ( pChannel->nSectorsToRead != 0 )
		{
			pChannel->bLocked = true;

			ASSERT( pChannel->pDoneSemaphore != nil );

			// Deadlock fix 1
#ifdef FIX_BUGS
			// This is while loop on Posix streamer, for spurious wakeups
			if (pChannel->bLocked && pChannel->nSectorsToRead != 0){
				WaitForSingleObject(pChannel->pDoneSemaphore, INFINITE);
			}
			pChannel->bLocked = false;
#else
			WaitForSingleObject(pChannel->pDoneSemaphore, INFINITE);
#endif
		}

		pChannel->bReading = false;

		return pChannel->nStatus;
	}

	DWORD NumberOfBytesTransferred;

	if ( _gbCdStreamOverlapped && pChannel->hFile )
	{
		ASSERT(pChannel->hFile != nil );
		// Beware: This is blocking call (because of last parameter)
		if ( GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) )
			return STREAM_NONE;
		else
			return STREAM_ERROR;
	}

	return STREAM_NONE;
}

void
AddToQueue(Queue *queue, int32 item)
{
	ASSERT( queue != nil );
	ASSERT( queue->items != nil );
	queue->items[queue->tail] = item;

	queue->tail = (queue->tail + 1) % queue->size;

	if ( queue->head == queue->tail )
		debug("Queue is full\n");
}

int32
GetFirstInQueue(Queue *queue)
{
	ASSERT( queue != nil );
	if ( queue->head == queue->tail )
		return -1;

	ASSERT( queue->items != nil );
	return queue->items[queue->head];
}

void
RemoveFirstInQueue(Queue *queue)
{
	ASSERT( queue != nil );
	if ( queue->head == queue->tail )
	{
		debug("Queue is empty\n");
		return;
	}

	queue->head = (queue->head + 1) % queue->size;
}

DWORD
WINAPI CdStreamThread(LPVOID lpThreadParameter)
{
	debug("Created cdstream thread\n");

	while ( true )
	{
		WaitForSingleObject(gCdStreamSema, INFINITE);

		int32 channel = GetFirstInQueue(&gChannelRequestQ);
		ASSERT( channel < gNumChannels );

		CdReadInfo *pChannel = &gpReadInfo[channel];
		ASSERT( pChannel != nil );

		pChannel->bReading = true;

		if ( pChannel->nStatus == STREAM_NONE )
		{
			if ( _gbCdStreamOverlapped )
			{
				pChannel->Overlapped.Offset = pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE;

				ASSERT(pChannel->hFile != nil );
				ASSERT(pChannel->pBuffer != nil );

				DWORD NumberOfBytesTransferred;

				if ( ReadFile(pChannel->hFile,
						pChannel->pBuffer,
						pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE,
						NULL,
						&pChannel->Overlapped) )
				{
					pChannel->nStatus = STREAM_NONE;
				}
				// Beware: This is blocking call (because of last parameter)
				else if ( GetLastError() == ERROR_IO_PENDING
						&& GetOverlappedResult(pChannel->hFile, &pChannel->Overlapped, &NumberOfBytesTransferred, TRUE) )
				{
					pChannel->nStatus = STREAM_NONE;
				}
				else
				{
					pChannel->nStatus = STREAM_ERROR;
				}
			}
			else
			{
				ASSERT(pChannel->hFile != nil );
				ASSERT(pChannel->pBuffer != nil );

				SetFilePointer(pChannel->hFile, pChannel->nSectorOffset * CDSTREAM_SECTOR_SIZE, nil, FILE_BEGIN);

				DWORD NumberOfBytesRead;
				if ( ReadFile(pChannel->hFile,
						pChannel->pBuffer,
						pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE,
						&NumberOfBytesRead,
						NULL) )
				{
					pChannel->nStatus = STREAM_NONE;
				}
			}
		}

		RemoveFirstInQueue(&gChannelRequestQ);

		pChannel->nSectorsToRead = 0;

		if ( pChannel->bLocked )
		{
			ASSERT( pChannel->pDoneSemaphore != nil );
			// Deadlock fix 2
#ifdef FIX_BUGS
			pChannel->bLocked = 0;
#endif
			ReleaseSemaphore(pChannel->pDoneSemaphore, 1, NULL);
		}

		pChannel->bReading = false;
	}
}

bool
CdStreamAddImage(char const *path)
{
	ASSERT(path != nil);
	ASSERT(gNumImages < MAX_CDIMAGES);

	SetLastError(0);

	gImgFiles[gNumImages] = CreateFile(path,
	                                   GENERIC_READ,
	                                   FILE_SHARE_READ,
	                                   nil,
	                                   OPEN_EXISTING,
	                                   _gdwCdStreamFlags | FILE_FLAG_RANDOM_ACCESS | FILE_ATTRIBUTE_READONLY,
	                                   nil);

	ASSERT( gImgFiles[gNumImages] != nil );
	if ( gImgFiles[gNumImages] == NULL )
		return false;

	strcpy(gCdImageNames[gNumImages], path);

	gNumImages++;

	return true;
}

char *
CdStreamGetImageName(int32 cd)
{
	ASSERT(cd < MAX_CDIMAGES);
	if ( gImgFiles[cd] != nil )
		return gCdImageNames[cd];

	return nil;
}

void
CdStreamRemoveImages(void)
{
	for ( int32 i = 0; i < gNumChannels; i++ )
		CdStreamSync(i);

	for ( int32 i = 0; i < gNumImages; i++ )
	{
		SetLastError(0);

		CloseHandle(gImgFiles[i]);
		gImgFiles[i] = nil;
	}

	gNumImages = 0;
}

int32
CdStreamGetNumImages(void)
{
	return gNumImages;
}
#endif

license: mit


code:
#pragma once

#define CDSTREAM_SECTOR_SIZE 2048

#define _GET_INDEX(a) (a >> 24)
#define _GET_OFFSET(a)  (a & 0xFFFFFF)

enum
{
	STREAM_NONE            = uint8( 0),
	STREAM_SUCCESS         = uint8( 1),
	STREAM_READING         = uint8(-1), // 0xFF,
	STREAM_ERROR           = uint8(-2), // 0xFE,
	STREAM_ERROR_NOCD      = uint8(-3), // 0xFD,
	STREAM_ERROR_WRONGCD   = uint8(-4), // 0xFC,
	STREAM_ERROR_OPENCD    = uint8(-5), // 0xFB,
	STREAM_WAITING         = uint8(-6)  // 0xFA,
};

struct Queue
{
	int32 *items;
	int32 head;
	int32 tail;
	int32 size;
};

VALIDATE_SIZE(Queue, 0x10);

void CdStreamInitThread(void);
void CdStreamInit(int32 numChannels);
uint32 GetGTA3ImgSize(void);
void CdStreamShutdown(void);
int32 CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size);
int32 CdStreamGetStatus(int32 channel);
int32 CdStreamGetLastPosn(void);
int32 CdStreamSync(int32 channel);
void AddToQueue(Queue *queue, int32 item);
int32 GetFirstInQueue(Queue *queue);
void RemoveFirstInQueue(Queue *queue);
bool CdStreamAddImage(char const *path);
char *CdStreamGetImageName(int32 cd);
void CdStreamRemoveImages(void);
int32 CdStreamGetNumImages(void);

#ifdef FLUSHABLE_STREAMING
extern bool flushStream[MAX_CDCHANNELS];
#endif


code:
#ifndef _WIN32
#include "common.h"
#include "crossplatform.h"
#include <signal.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <stdarg.h>
#include <limits.h>

#ifdef __linux__
#include <sys/syscall.h>
#endif

#include "CdStream.h"
#include "rwcore.h"
#include "MemoryMgr.h"

#define CDDEBUG(f, ...)   debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__)
#define CDTRACE(f, ...)   printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__)

#ifdef FLUSHABLE_STREAMING
bool flushStream[MAX_CDCHANNELS];
#endif

#ifdef USE_UNNAMED_SEM

#define RE3_SEM_OPEN(name, ...) re3_sem_open()
sem_t*
re3_sem_open(void)
{
	sem_t* sem = (sem_t*)malloc(sizeof(sem_t));
	if (sem_init(sem, 0, 1) == -1) {
		sem = SEM_FAILED;
	}

	return sem;
}

#define RE3_SEM_CLOSE(sem, format, ...) re3_sem_close(sem)
void
re3_sem_close(sem_t* sem)
{
	sem_destroy(sem);
	free(sem);
}

#else

#define RE3_SEM_OPEN re3_sem_open
sem_t*
re3_sem_open(const char* format, ...)
{
	char semName[21];
	va_list va;
	va_start(va, format);
	vsprintf(semName, format, va);

	return sem_open(semName, O_CREAT, 0644, 1);
}

#define RE3_SEM_CLOSE re3_sem_close
void
re3_sem_close(sem_t* sem, const char* format, ...)
{
	sem_close(sem);

	char semName[21];
	va_list va;
	va_start(va, format);
	vsprintf(semName, format, va);

	sem_unlink(semName);
}

#endif

struct CdReadInfo
{
	uint32 nSectorOffset;
	uint32 nSectorsToRead;
	void *pBuffer;
	bool bLocked;
	bool bReading;
	int32 nStatus;
#ifdef ONE_THREAD_PER_CHANNEL
	int8 nThreadStatus; // 0: created 1:priority set up 2:abort now
	pthread_t pChannelThread;
	sem_t *pStartSemaphore;
#endif
	sem_t *pDoneSemaphore; // used for CdStreamSync
	int32 hFile;
};

char gCdImageNames[MAX_CDIMAGES+1][64];
int32 gNumImages;
int32 gNumChannels;

int32 gImgFiles[MAX_CDIMAGES]; // -1: error 0:unused otherwise: fd
char *gImgNames[MAX_CDIMAGES];

#ifndef ONE_THREAD_PER_CHANNEL
pthread_t _gCdStreamThread;
sem_t *gCdStreamSema; // released when we have new thing to read(so channel is set)
int8 gCdStreamThreadStatus; // 0: created 1:priority set up 2:abort now
Queue gChannelRequestQ;
bool _gbCdStreamOverlapped;
#endif

CdReadInfo *gpReadInfo;

int32 lastPosnRead;

int _gdwCdStreamFlags;

void *CdStreamThread(void* channelId);

void
CdStreamInitThread(void)
{
	int status;
#ifndef ONE_THREAD_PER_CHANNEL
	gChannelRequestQ.items = (int32 *)calloc(gNumChannels + 1, sizeof(int32));
	gChannelRequestQ.head = 0;
	gChannelRequestQ.tail = 0;
	gChannelRequestQ.size = gNumChannels + 1;
	ASSERT(gChannelRequestQ.items != nil );
	gCdStreamSema = RE3_SEM_OPEN("/semaphore_cd_stream");

	if (gCdStreamSema == SEM_FAILED) {
		CDTRACE("failed to create stream semaphore");
		ASSERT(0);
		return;
	}
#endif

	if ( gNumChannels > 0 )
	{
		for ( int32 i = 0; i < gNumChannels; i++ )
		{
			gpReadInfo[i].pDoneSemaphore = RE3_SEM_OPEN("/semaphore_done%d", i);

			if (gpReadInfo[i].pDoneSemaphore == SEM_FAILED)
			{
				CDTRACE("failed to create sync semaphore");
				ASSERT(0);
				return;
			}

#ifdef ONE_THREAD_PER_CHANNEL
			gpReadInfo[i].pStartSemaphore = RE3_SEM_OPEN("/semaphore_start%d", i);

			if (gpReadInfo[i].pStartSemaphore == SEM_FAILED)
			{
				CDTRACE("failed to create start semaphore");
				ASSERT(0);
				return;
			}
			gpReadInfo[i].nThreadStatus = 0;
			int *channelI = (int*)malloc(sizeof(int));
			*channelI = i;
			status = pthread_create(&gpReadInfo[i].pChannelThread, NULL, CdStreamThread, (void*)channelI);

			if (status == -1)
			{
				CDTRACE("failed to create sync thread");
				ASSERT(0);
				return;
			}
#endif
		}
	}

#ifndef ONE_THREAD_PER_CHANNEL
	debug("Using one streaming thread for all channels\n");
	gCdStreamThreadStatus = 0;
	status = pthread_create(&_gCdStreamThread, NULL, CdStreamThread, nil);

	if (status == -1)
	{
		CDTRACE("failed to create sync thread");
		ASSERT(0);
		return;
	}
#else
	debug("Using separate streaming threads for each channel\n");
#endif
}

void
CdStreamInit(int32 numChannels)
{
	struct statvfs fsInfo;

	if((statvfs("models/gta3.img", &fsInfo)) < 0)
	{
		CDTRACE("can't get filesystem info");
		ASSERT(0);
		return;
	}
#ifdef __linux__
	_gdwCdStreamFlags = O_RDONLY | O_NOATIME;
#else
	_gdwCdStreamFlags = O_RDONLY;
#endif
	// People say it's slower
/*
	if ( fsInfo.f_bsize <= CDSTREAM_SECTOR_SIZE )
	{
		_gdwCdStreamFlags |= O_DIRECT;
		debug("Using no buffered loading for streaming\n");
	}
*/
	void *pBuffer = (void *)RwMallocAlign(CDSTREAM_SECTOR_SIZE, (RwUInt32)fsInfo.f_bsize);
	ASSERT( pBuffer != nil );

	gNumImages = 0;

	gNumChannels = numChannels;
	ASSERT( gNumChannels != 0 );

	gpReadInfo = (CdReadInfo *)calloc(numChannels, sizeof(CdReadInfo));
	ASSERT( gpReadInfo != nil );

	CDDEBUG("read info %p", gpReadInfo);

	CdStreamInitThread();

	ASSERT( pBuffer != nil );
	RwFreeAlign(pBuffer);
}

uint32
GetGTA3ImgSize(void)
{
	ASSERT( gImgFiles[0] > 0 );
	struct stat statbuf;

	char path[PATH_MAX];
	realpath(gImgNames[0], path);
	if (stat(path, &statbuf) == -1) {
		// Try case-insensitivity
		char* real = casepath(gImgNames[0], false);
		if (real)
		{
			realpath(real, path);
			free(real);
			if (stat(path, &statbuf) != -1)
				goto ok;
		}

		CDTRACE("can't get size of gta3.img");
		ASSERT(0);
		return 0;
	}
	ok:
	return (uint32)statbuf.st_size;
}

void
CdStreamShutdown(void)
{
    // Destroying semaphores and free(gpReadInfo) will be done at threads
#ifndef ONE_THREAD_PER_CHANNEL
	gCdStreamThreadStatus = 2;
	sem_post(gCdStreamSema);
	pthread_join(_gCdStreamThread, nil);
#else
	for ( int32 i = 0; i < gNumChannels; i++ ) {
		gpReadInfo[i].nThreadStatus = 2;
		sem_post(gpReadInfo[i].pStartSemaphore);
		pthread_join(gpReadInfo[i].pChannelThread, nil);
	}
#endif
}

int32
CdStreamRead(int32 channel, void *buffer, uint32 offset, uint32 size)
{
	ASSERT( channel < gNumChannels );
	ASSERT( buffer != nil );

	lastPosnRead = size + offset;

	ASSERT( _GET_INDEX(offset) < MAX_CDIMAGES );
	int32 hImage = gImgFiles[_GET_INDEX(offset)];
	ASSERT( hImage > 0 );

	CdReadInfo *pChannel = &gpReadInfo[channel];
	ASSERT( pChannel != nil );

	if ( pChannel->nSectorsToRead != 0 || pChannel->bReading ) {
		if (pChannel->hFile == hImage - 1 && pChannel->nSectorOffset == _GET_OFFSET(offset) && pChannel->nSectorsToRead >= size)
			return STREAM_SUCCESS;
#ifdef FLUSHABLE_STREAMING
		flushStream[channel] = 1;
		CdStreamSync(channel);
#else
		return STREAM_NONE;
#endif
	}

	pChannel->hFile = hImage - 1;
	pChannel->nStatus = STREAM_NONE;
	pChannel->nSectorOffset = _GET_OFFSET(offset);
	pChannel->nSectorsToRead = size;
	pChannel->pBuffer = buffer;
	pChannel->bLocked = 0;

#ifndef ONE_THREAD_PER_CHANNEL
	AddToQueue(&gChannelRequestQ, channel);
	if ( sem_post(gCdStreamSema) != 0 )
		printf("Signal Sema Error\n");
#else
	if ( sem_post(pChannel->pStartSemaphore) != 0 )
		printf("Signal Sema Error\n");
#endif

	return STREAM_SUCCESS;
}

int32
CdStreamGetStatus(int32 channel)
{
	ASSERT( channel < gNumChannels );
	CdReadInfo *pChannel = &gpReadInfo[channel];
	ASSERT( pChannel != nil );

#ifdef ONE_THREAD_PER_CHANNEL
	if (pChannel->nThreadStatus == 2)
		return STREAM_NONE;
#else
	if (gCdStreamThreadStatus == 2)
		return STREAM_NONE;
#endif

	if ( pChannel->bReading )
		return STREAM_READING;

	if ( pChannel->nSectorsToRead != 0 )
		return STREAM_WAITING;

	if ( pChannel->nStatus != STREAM_NONE )
	{
		int32 status = pChannel->nStatus;
		pChannel->nStatus = STREAM_NONE;

		return status;
	}

	return STREAM_NONE;
}

int32
CdStreamGetLastPosn(void)
{
	return lastPosnRead;
}

// wait for channel to finish reading
int32
CdStreamSync(int32 channel)
{
	ASSERT( channel < gNumChannels );
	CdReadInfo *pChannel = &gpReadInfo[channel];
	ASSERT( pChannel != nil );

#ifdef FLUSHABLE_STREAMING
	if (flushStream[channel]) {
		pChannel->nSectorsToRead = 0;
#ifdef ONE_THREAD_PER_CHANNEL
		pthread_kill(pChannel->pChannelThread, SIGUSR1);
		if (pChannel->bReading) {
			pChannel->bLocked = true;
#else
		if (pChannel->bReading) {
			pChannel->bLocked = true;
			pthread_kill(_gCdStreamThread, SIGUSR1);
#endif
			while (pChannel->bLocked)
				sem_wait(pChannel->pDoneSemaphore);
		}
		pChannel->bReading = false;
		flushStream[channel] = false;
		return STREAM_NONE;
	}
#endif

	if ( pChannel->nSectorsToRead != 0 )
	{
		pChannel->bLocked = true;
		while (pChannel->bLocked && pChannel->nSectorsToRead != 0){
			sem_wait(pChannel->pDoneSemaphore);
		}
		pChannel->bLocked = false;
	}

	pChannel->bReading = false;

	return pChannel->nStatus;
}

void
AddToQueue(Queue *queue, int32 item)
{
	ASSERT( queue != nil );
	ASSERT( queue->items != nil );
	queue->items[queue->tail] = item;

	queue->tail = (queue->tail + 1) % queue->size;

	if ( queue->head == queue->tail )
		debug("Queue is full\n");
}

int32
GetFirstInQueue(Queue *queue)
{
	ASSERT( queue != nil );
	if ( queue->head == queue->tail )
		return -1;

	ASSERT( queue->items != nil );
	return queue->items[queue->head];
}

void
RemoveFirstInQueue(Queue *queue)
{
	ASSERT( queue != nil );
	if ( queue->head == queue->tail )
	{
		debug("Queue is empty\n");
		return;
	}

	queue->head = (queue->head + 1) % queue->size;
}

void *CdStreamThread(void *param)
{
	debug("Created cdstream thread\n");

#ifndef ONE_THREAD_PER_CHANNEL
	while (gCdStreamThreadStatus != 2) {
		sem_wait(gCdStreamSema);

		int32 channel = GetFirstInQueue(&gChannelRequestQ);

		// spurious wakeup
		if (channel == -1)
			continue;
#else
	int channel = *((int*)param);
	while (gpReadInfo[channel].nThreadStatus != 2){
		sem_wait(gpReadInfo[channel].pStartSemaphore);
#endif

		CdReadInfo *pChannel = &gpReadInfo[channel];
		ASSERT( pChannel != nil );

		// spurious wakeup or we sent interrupt signal for flushing
		if(pChannel->nSectorsToRead == 0)
			continue;

		pChannel->bReading = true;

		// Not standard POSIX :shrug:
#ifdef __linux__
#ifdef ONE_THREAD_PER_CHANNEL
		if (gpReadInfo[channel].nThreadStatus == 0){
			gpReadInfo[channel].nThreadStatus = 1;
#else
		if (gCdStreamThreadStatus == 0){
			gCdStreamThreadStatus = 1;
#endif
			pid_t tid = syscall(SYS_gettid);
			int ret = setpriority(PRIO_PROCESS, tid, getpriority(PRIO_PROCESS, getpid()) + 1);
		}
#endif
		if ( pChannel->nStatus == STREAM_NONE )
		{
			ASSERT(pChannel->hFile >= 0);
			ASSERT(pChannel->pBuffer != nil );

			lseek(pChannel->hFile, (size_t)pChannel->nSectorOffset * (size_t)CDSTREAM_SECTOR_SIZE, SEEK_SET);
			if (read(pChannel->hFile, pChannel->pBuffer, pChannel->nSectorsToRead * CDSTREAM_SECTOR_SIZE) == -1) {
				// pChannel->nSectorsToRead == 0 at this point means we wanted to flush channel
				// STREAM_WAITING is a little hack to make CStreaming not process this data
				pChannel->nStatus = pChannel->nSectorsToRead == 0 ? STREAM_WAITING : STREAM_ERROR;
			} else {
				pChannel->nStatus = STREAM_NONE;
			}
		}

#ifndef ONE_THREAD_PER_CHANNEL
		RemoveFirstInQueue(&gChannelRequestQ);
#endif

		pChannel->nSectorsToRead = 0;
		if ( pChannel->bLocked )
		{
			pChannel->bLocked = 0;
			sem_post(pChannel->pDoneSemaphore);
		}
		pChannel->bReading = false;
	}
	char semName[20];
#ifndef ONE_THREAD_PER_CHANNEL
	for ( int32 i = 0; i < gNumChannels; i++ )
	{
		RE3_SEM_CLOSE(gpReadInfo[i].pDoneSemaphore, "/semaphore_done%d", i);
	}
	RE3_SEM_CLOSE(gCdStreamSema, "/semaphore_cd_stream");
	free(gChannelRequestQ.items);
#else
	RE3_SEM_CLOSE(gpReadInfo[channel].pStartSemaphore, "/semaphore_start%d", channel);

	RE3_SEM_CLOSE(gpReadInfo[channel].pDoneSemaphore, "/semaphore_done%d", channel);
#endif
	if (gpReadInfo)
		free(gpReadInfo);
	gpReadInfo = nil;
	pthread_exit(nil);
}

bool
CdStreamAddImage(char const *path)
{
	ASSERT(path != nil);
	ASSERT(gNumImages < MAX_CDIMAGES);

	gImgFiles[gNumImages] = open(path, _gdwCdStreamFlags);

	// Fix case sensitivity and backslashes.
	if (gImgFiles[gNumImages] == -1) {
		char* real = casepath(path, false);
		if (real)
		{
			gImgFiles[gNumImages] = open(real, _gdwCdStreamFlags);
			free(real);
		}
	}

	if ( gImgFiles[gNumImages] == -1 ) {
		assert(false);
		return false;
	}

	gImgNames[gNumImages] = strdup(path);
	gImgFiles[gNumImages]++; // because -1: error 0: not used

	strcpy(gCdImageNames[gNumImages], path);

	gNumImages++;

	return true;
}

char *
CdStreamGetImageName(int32 cd)
{
	ASSERT(cd < MAX_CDIMAGES);
	if ( gImgFiles[cd] > 0)
		return gCdImageNames[cd];

	return nil;
}

void
CdStreamRemoveImages(void)
{
	for ( int32 i = 0; i < gNumChannels; i++ ) {
#ifdef FLUSHABLE_STREAMING
		flushStream[i] = 1;
#endif
		CdStreamSync(i);
	}

	for ( int32 i = 0; i < gNumImages; i++ )
	{
		close(gImgFiles[i] - 1);
		free(gImgNames[i]);
		gImgFiles[i] = 0;
	}

	gNumImages = 0;
}

int32
CdStreamGetNumImages(void)
{
	return gNumImages;
}
#endif


code:
#include "common.h"

#include "Timer.h"
#include "Pad.h"
#include "Clock.h"
#include "Stats.h"

_TODO("gbFastTime");
bool gbFastTime;

uint8  CClock::ms_nGameClockHours;
uint8  CClock::ms_nGameClockMinutes;
uint16 CClock::ms_nGameClockSeconds;
uint8  CClock::ms_Stored_nGameClockHours;
uint8  CClock::ms_Stored_nGameClockMinutes;
uint16 CClock::ms_Stored_nGameClockSeconds;
uint32 CClock::ms_nMillisecondsPerGameMinute;
uint32  CClock::ms_nLastClockTick;
bool   CClock::ms_bClockHasBeenStored;

void
CClock::Initialise(uint32 scale)
{
	debug("Initialising CClock...\n");
	ms_nGameClockHours = 12;
	ms_nGameClockMinutes = 0;
	ms_nGameClockSeconds = 0;
	ms_nMillisecondsPerGameMinute = scale;
	ms_nLastClockTick = CTimer::GetTimeInMilliseconds();
	ms_bClockHasBeenStored = false;
	debug("CClock ready\n");
}

void
CClock::Update(void)
{
	if(CPad::GetPad(1)->GetRightShoulder1())
	{
		ms_nGameClockMinutes += 8;
		ms_nLastClockTick = CTimer::GetTimeInMilliseconds();

		if(ms_nGameClockMinutes >= 60)
		{
			ms_nGameClockHours++;
			ms_nGameClockMinutes = 0;
			if(ms_nGameClockHours >= 24)
				ms_nGameClockHours = 0;
		}

	}
	else if(CTimer::GetTimeInMilliseconds() - ms_nLastClockTick > ms_nMillisecondsPerGameMinute || gbFastTime)
	{
		ms_nGameClockMinutes++;
		ms_nLastClockTick += ms_nMillisecondsPerGameMinute;

		if ( gbFastTime )
			ms_nLastClockTick = CTimer::GetTimeInMilliseconds();

		if(ms_nGameClockMinutes >= 60)
		{
			ms_nGameClockHours++;
			ms_nGameClockMinutes = 0;
			if(ms_nGameClockHours >= 24)
			{
				CStats::DaysPassed++;
				ms_nGameClockHours = 0;
			}
		}
	}
	ms_nGameClockSeconds = 60 * (CTimer::GetTimeInMilliseconds() - ms_nLastClockTick) / ms_nMillisecondsPerGameMinute;
}

void
CClock::SetGameClock(uint8 h, uint8 m)
{
	ms_nGameClockHours = h;
	ms_nGameClockMinutes = m;
	ms_nGameClockSeconds = 0;
	ms_nLastClockTick = CTimer::GetTimeInMilliseconds();
}

int32
CClock::GetGameClockMinutesUntil(uint8 h, uint8 m)
{
	int32 now, then;
	now = ms_nGameClockHours*60 + ms_nGameClockMinutes;
	then = h*60 + m;
	if(then < now)
		then += 24*60;
	return then-now;
}

bool
CClock::GetIsTimeInRange(uint8 h1, uint8 h2)
{
	if(h1 > h2)
		return ms_nGameClockHours >= h1 || ms_nGameClockHours < h2;
	else
		return ms_nGameClockHours >= h1 && ms_nGameClockHours < h2;
}

void
CClock::StoreClock(void)
{
	ms_Stored_nGameClockHours = ms_nGameClockHours;
	ms_Stored_nGameClockMinutes = ms_nGameClockMinutes;
	ms_Stored_nGameClockSeconds = ms_nGameClockSeconds;
	ms_bClockHasBeenStored = true;
}

void
CClock::RestoreClock(void)
{
	ms_nGameClockHours = ms_Stored_nGameClockHours;
	ms_nGameClockMinutes = ms_Stored_nGameClockMinutes;
	ms_nGameClockSeconds = ms_Stored_nGameClockSeconds;
}


code:
#pragma once

class CClock
{
public:
	static uint8  ms_nGameClockHours;
	static uint8  ms_nGameClockMinutes;
	static uint16 ms_nGameClockSeconds;
	static uint8  ms_Stored_nGameClockHours;
	static uint8  ms_Stored_nGameClockMinutes;
	static uint16 ms_Stored_nGameClockSeconds;
	static uint32 ms_nMillisecondsPerGameMinute;
	static uint32 ms_nLastClockTick;
	static bool   ms_bClockHasBeenStored;

	static void Initialise(uint32 scale);
	static void Update(void);
	static void SetGameClock(uint8 h, uint8 m);
	static int32 GetGameClockMinutesUntil(uint8 h, uint8 m);
	static bool GetIsTimeInRange(uint8 h1, uint8 h2);
	static void StoreClock(void);
	static void RestoreClock(void);

	static uint8 GetHours(void) { return ms_nGameClockHours; }
	static uint8 GetMinutes(void) { return ms_nGameClockMinutes; }
	static int16 GetSeconds(void) { return ms_nGameClockSeconds; }

	static uint8 &GetHoursRef(void) { return ms_nGameClockHours; }
	static uint8 &GetMinutesRef(void) { return ms_nGameClockMinutes; }
};