#include "StdAfxSA.h" #include #include #include #include #include #include #include #include #include "ScriptSA.h" #include "GeneralSA.h" #include "ModelInfoSA.h" #include "VehicleSA.h" #include "PedSA.h" #include "AudioHardwareSA.h" #include "LinkListSA.h" #include "PNGFile.h" #include "PlayerInfoSA.h" #include "FireManagerSA.h" #include "WaveDecoderSA.h" #include "FLACDecoderSA.h" #include "Utils/Patterns.h" #include "Utils/DelimStringReader.h" #include "Utils/ModuleList.hpp" #include "Utils/ScopedUnprotect.hpp" #include "Utils/HookEach.hpp" #include "Desktop.h" #include "debugmenu_public.h" #include "resource.h" EXTERN_C IMAGE_DOS_HEADER __ImageBase; // ============= Mod compatibility stuff ============= namespace ModCompat { bool SkygfxPatchesMoonphases( HMODULE module ) { if ( module == nullptr ) return false; // SkyGfx not installed struct Config { uint32_t version; // The rest isn't relevant at the moment }; auto func = (Config*(*)())GetProcAddress( module, "GetConfig" ); if ( func == nullptr ) return false; // Old version? const Config* config = func(); if ( config == nullptr ) return false; // Old version/error? constexpr uint32_t SKYGFX_VERSION_WITH_MOONPHASES = 0x360; return config->version >= SKYGFX_VERSION_WITH_MOONPHASES; } bool bCdStreamFallBackForOldML = false; bool ModloaderCdStreamRaceConditionAware( HMODULE module ) { if ( module == nullptr ) return false; // modloader not installed HMODULE stdStreamModule; if ( GetModuleHandleEx( 0, TEXT("std.stream.dll"), &stdStreamModule ) == 0 ) return false; // std.data not loaded // ML is installed, so if it's an old version we need to fall back to a less safe implementation (no condition variables) bCdStreamFallBackForOldML = true; bool aware = false; const auto func = (uint32_t(*)())GetProcAddress( stdStreamModule, "CdStreamRaceConditionAware" ); if ( func != nullptr ) { aware = func() >= 1; } FreeLibrary( stdStreamModule ); return aware; } namespace Utils { template HMODULE GetModuleHandleFromAddress( AT address ) { HMODULE result = nullptr; GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT|GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, LPCTSTR(address), &result ); return result; } // Resolves a re-route if it comes from a no-CD executable uintptr_t GetFunctionAddrIfRerouted(uintptr_t address) { if (*reinterpret_cast(address) == 0xE9) { uintptr_t jumpDestination; Memory::ReadCall(address, jumpDestination); if (GetModuleHandleFromAddress(address) == GetModuleHandleFromAddress(jumpDestination)) { return jumpDestination; } } return address; } } } #pragma warning(disable:4733) // RW wrappers static void* varAtomicDefaultRenderCallBack = AddressByVersion(0x7491C0, 0x749AD0, 0x783180); WRAPPER RpAtomic* AtomicDefaultRenderCallBack(RpAtomic* atomic) { WRAPARG(atomic); VARJMP(varAtomicDefaultRenderCallBack); } static void* varRtPNGImageRead = AddressByVersion(0x7CF9B0, 0x7D02B0, 0x809970); WRAPPER RwImage* RtPNGImageRead(const RwChar* imageName) { WRAPARG(imageName); VARJMP(varRtPNGImageRead); } static void* varRwTextureCreate = AddressByVersion(0x7F37C0, 0x7F40C0, 0x82D780); WRAPPER RwTexture* RwTextureCreate(RwRaster* raster) { WRAPARG(raster); VARJMP(varRwTextureCreate); } static void* varRwRasterCreate = AddressByVersion(0x7FB230, 0x7FBB30, 0x8351F0, { "8B 0D ? ? ? ? 56 68 07 04 03 00 8B 54 01 60", -5 }); WRAPPER RwRaster* RwRasterCreate(RwInt32 width, RwInt32 height, RwInt32 depth, RwInt32 flags) { WRAPARG(width); WRAPARG(height); WRAPARG(depth); WRAPARG(flags); VARJMP(varRwRasterCreate); } static void* varRwImageDestroy = AddressByVersion(0x802740, 0x803040, 0x83C700); WRAPPER RwBool RwImageDestroy(RwImage* image) { WRAPARG(image); VARJMP(varRwImageDestroy); } static void* varRpMaterialSetTexture = AddressByVersion(0x74DBC0, 0x74E4D0, 0x787B80); WRAPPER RpMaterial* RpMaterialSetTexture(RpMaterial* material, RwTexture* texture) { VARJMP(varRpMaterialSetTexture); } static void* varRwFrameGetLTM = AddressByVersion(0x7F0990, 0x7F1290, 0x82A950); WRAPPER RwMatrix* RwFrameGetLTM(RwFrame* frame) { VARJMP(varRwFrameGetLTM); } static void* varRwMatrixRotate = AddressByVersion(0x7F1FD0, 0x7F28D0, 0x82BF90); WRAPPER RwMatrix* RwMatrixRotate(RwMatrix* matrix, const RwV3d* axis, RwReal angle, RwOpCombineType combineOp) { WRAPARG(matrix); WRAPARG(axis); WRAPARG(angle); WRAPARG(combineOp); VARJMP(varRwMatrixRotate); } static void* varRwD3D9SetRenderState = AddressByVersion(0x7FC2D0, 0x7FCBD0, 0x836290); WRAPPER void RwD3D9SetRenderState(RwUInt32 state, RwUInt32 value) { WRAPARG(state); WRAPARG(value); VARJMP(varRwD3D9SetRenderState); } RwCamera* RwCameraBeginUpdate(RwCamera* camera) { return camera->beginUpdate(camera); } RwCamera* RwCameraEndUpdate(RwCamera* camera) { return camera->endUpdate(camera); } RwCamera* RwCameraClear(RwCamera* camera, RwRGBA* colour, RwInt32 clearMode) { return RWSRCGLOBAL(stdFunc[rwSTANDARDCAMERACLEAR])(camera, colour, clearMode) != FALSE ? camera : NULL; } RwMatrix* RwMatrixTranslate(RwMatrix* matrix, const RwV3d* translation, RwOpCombineType combineOp) { if ( combineOp == rwCOMBINEREPLACE ) { RwMatrixSetIdentity(matrix); matrix->pos = *translation; } else if ( combineOp == rwCOMBINEPRECONCAT ) { matrix->pos.x += matrix->at.x * translation->z + matrix->up.x * translation->y + matrix->right.x * translation->x; matrix->pos.y += matrix->at.y * translation->z + matrix->up.y * translation->y + matrix->right.y * translation->x; matrix->pos.z += matrix->at.z * translation->z + matrix->up.z * translation->y + matrix->right.z * translation->x; } else if ( combineOp == rwCOMBINEPOSTCONCAT ) { matrix->pos.x += translation->x; matrix->pos.y += translation->y; matrix->pos.z += translation->z; } rwMatrixSetFlags(matrix, rwMatrixGetFlags(matrix) & ~(rwMATRIXINTERNALIDENTITY)); return matrix; } RwFrame* RwFrameForAllChildren(RwFrame* frame, RwFrameCallBack callBack, void* data) { for ( RwFrame* curFrame = frame->child; curFrame != nullptr; curFrame = curFrame->next ) { if ( callBack(curFrame, data) == NULL ) break; } return frame; } RwFrame* RwFrameForAllObjects(RwFrame* frame, RwObjectCallBack callBack, void* data) { for ( RwLLLink* link = rwLinkListGetFirstLLLink(&frame->objectList); link != rwLinkListGetTerminator(&frame->objectList); link = rwLLLinkGetNext(link) ) { if ( callBack(&rwLLLinkGetData(link, RwObjectHasFrame, lFrame)->object, data) == NULL ) break; } return frame; } RwFrame* RwFrameUpdateObjects(RwFrame* frame) { if ( !rwObjectTestPrivateFlags(&frame->root->object, rwFRAMEPRIVATEHIERARCHYSYNCLTM|rwFRAMEPRIVATEHIERARCHYSYNCOBJ) ) rwLinkListAddLLLink(&RWSRCGLOBAL(dirtyFrameList), &frame->root->inDirtyListLink); rwObjectSetPrivateFlags(&frame->root->object, rwObjectGetPrivateFlags(&frame->root->object) | (rwFRAMEPRIVATEHIERARCHYSYNCLTM|rwFRAMEPRIVATEHIERARCHYSYNCOBJ)); rwObjectSetPrivateFlags(&frame->object, rwObjectGetPrivateFlags(&frame->object) | (rwFRAMEPRIVATESUBTREESYNCLTM|rwFRAMEPRIVATESUBTREESYNCOBJ)); return frame; } RwMatrix* RwMatrixUpdate(RwMatrix* matrix) { matrix->flags &= ~(rwMATRIXTYPEMASK|rwMATRIXINTERNALIDENTITY); return matrix; } RwRaster* RwRasterSetFromImage(RwRaster* raster, RwImage* image) { if ( RWSRCGLOBAL(stdFunc[rwSTANDARDRASTERSETIMAGE])(raster, image, 0) != FALSE ) { if ( image->flags & rwIMAGEGAMMACORRECTED ) raster->privateFlags |= rwRASTERGAMMACORRECTED; return raster; } return NULL; } RwImage* RwImageFindRasterFormat(RwImage* ipImage, RwInt32 nRasterType, RwInt32* npWidth, RwInt32* npHeight, RwInt32* npDepth, RwInt32* npFormat) { RwRaster outRaster; if ( RWSRCGLOBAL(stdFunc[rwSTANDARDIMAGEFINDRASTERFORMAT])(&outRaster, ipImage, nRasterType) != FALSE ) { *npFormat = RwRasterGetFormat(&outRaster) | outRaster.cType; *npWidth = RwRasterGetWidth(&outRaster); *npHeight = RwRasterGetHeight(&outRaster); *npDepth = RwRasterGetDepth(&outRaster); return ipImage; } return NULL; } RpClump* RpClumpForAllAtomics(RpClump* clump, RpAtomicCallBack callback, void* pData) { for ( RwLLLink* link = rwLinkListGetFirstLLLink(&clump->atomicList); link != rwLinkListGetTerminator(&clump->atomicList); link = rwLLLinkGetNext(link) ) { if ( callback(rwLLLinkGetData(link, RpAtomic, inClumpLink), pData) == NULL ) break; } return clump; } RpClump* RpClumpRender(RpClump* clump) { RpClump* retClump = clump; for ( RwLLLink* link = rwLinkListGetFirstLLLink(&clump->atomicList); link != rwLinkListGetTerminator(&clump->atomicList); link = rwLLLinkGetNext(link) ) { RpAtomic* curAtomic = rwLLLinkGetData(link, RpAtomic, inClumpLink); if ( RpAtomicGetFlags(curAtomic) & rpATOMICRENDER ) { // Not sure why they need this RwFrameGetLTM(RpAtomicGetFrame(curAtomic)); if ( RpAtomicRender(curAtomic) == NULL ) retClump = NULL; } } return retClump; } RpGeometry* RpGeometryForAllMaterials(RpGeometry* geometry, RpMaterialCallBack fpCallBack, void* pData) { for ( RwInt32 i = 0, j = geometry->matList.numMaterials; i < j; i++ ) { if ( fpCallBack(geometry->matList.materials[i], pData) == NULL ) break; } return geometry; } RwInt32 RpHAnimIDGetIndex(RpHAnimHierarchy* hierarchy, RwInt32 ID) { for ( RwInt32 i = 0, j = hierarchy->numNodes; i < j; i++ ) { if ( ID == hierarchy->pNodeInfo[i].nodeID ) return i; } return -1; } RwMatrix* RpHAnimHierarchyGetMatrixArray(RpHAnimHierarchy* hierarchy) { return hierarchy->pMatrixArray; } void RwD3D9DeleteVertexShader(void* shader) { static_cast(shader)->Release(); } RwBool _rpD3D9VertexDeclarationInstColor(RwUInt8* mem, const RwRGBA* color, RwInt32 numVerts, RwUInt32 stride) { RwUInt8 alpha = 255; for ( RwInt32 i = 0; i < numVerts; i++ ) { *reinterpret_cast(mem) = (color->alpha << 24) | (color->red << 16) | (color->green << 8) | color->blue; alpha &= color->alpha; color++; mem += stride; } return alpha != 255; } // Unreachable stub RwBool RwMatrixDestroy(RwMatrix* mpMat) { assert(!"Unreachable!"); return TRUE; } struct AlphaObjectInfo { RpAtomic* pAtomic; RpAtomic* (*callback)(RpAtomic*, float); float fCompareValue; friend bool operator < (const AlphaObjectInfo &a, const AlphaObjectInfo &b) { return a.fCompareValue < b.fCompareValue; } }; // Other wrappers void (*GTAdelete)(void*) = AddressByVersion(0x82413F, 0x824EFF, 0x85E58C); const char* (*GetFrameNodeName)(RwFrame*) = AddressByVersion(0x72FB30, 0x730360, 0x769C20); RpHAnimHierarchy* (*GetAnimHierarchyFromSkinClump)(RpClump*) = AddressByVersion(0x734A40, 0x735270, 0x7671B0); auto InitializeUtrax = AddressByVersion(0x4F35B0, 0x4F3A10, 0x4FFA80); auto RpAnimBlendClumpGetAssociation = AddressByVersion(0x4D68B0, { "8B 0D ? ? ? ? 8B 14 01 8B 02 85 C0 74 11 8B 4D 0C", -6 }); auto GetAnimationBlockIndex = AddressByVersion(0x4D3990, { "83 C4 04 85 C0 75 05", -0xC }); auto RequestModel = AddressByVersion(0x4087E0, { "57 8D 3C 9B", -0x8 }); auto LoadAllRequestedModels = AddressByVersion(0x40EA10, { "A1 ? ? ? ? 03 C0", -0x20 }); auto IsPlayerOnAMission = AddressByVersion(0x464D50, {"85 C0 74 0C 83 B8 ? ? ? ? ? 75 03 B0 01 C3", -5}); static void (__thiscall* SetVolume)(void*,float); static BOOL (*IsAlreadyRunning)(); static void (*TheScriptsLoad)(); static void (*DoSunAndMoon)(); auto WorldRemove = AddressByVersion(0x563280, 0, 0x57D370, { "8B 06 8B 50 0C 8B CE FF D2 8A 46 36 24 07 3C 01 76 0D", -7 }); // SA variables void** rwengine = *AddressByVersion(0x58FFC0, 0x53F032, 0x48C194, { "8B 48 20 53 56 57 6A 01", -5 + 1 }); unsigned char& nGameClockDays = **AddressByVersion(0x4E841D, 0x4E886D, 0x4F3871); unsigned char& nGameClockMonths = **AddressByVersion(0x4E842D, 0x4E887D, 0x4F3861); void*& pUserTracksStuff = **AddressByVersion(0x4D9B7B, 0x4DA06C, 0x4E4A43); float& fFarClipZ = **AddressByVersion(0x70D21F, 0x70DA4F, 0x421AB2); CZoneInfo*& pCurrZoneInfo = **AddressByVersion(0x58ADB1, 0x58B581, 0x407F93); CRGBA* HudColour = *AddressByVersion(0x58ADF6, 0x58B5C6, 0x440648); CLinkListSA& ms_weaponPedsForPC = **AddressByVersion**>(0x53EACA, 0x53EF6A, 0x551101); CLinkListSA& m_alphaList = **AddressByVersion**>(0x733A4D, 0x73427D, 0x76DCA3); uint32_t& bDrawCrossHair = **AddressByVersion(0x58E7BF + 2, {"83 3D ? ? ? ? ? 74 29", 2}); DebugMenuAPI gDebugMenuAPI; // Custom variables static float fSunFarClip; static struct { char Extension[8]; unsigned int Codec; } UserTrackExtensions[] = { { ".ogg", DECODER_VORBIS }, { ".mp3", DECODER_QUICKTIME }, { ".wav", DECODER_WAVE }, { ".wma", DECODER_WINDOWSMEDIA }, { ".wmv", DECODER_WINDOWSMEDIA }, { ".aac", DECODER_QUICKTIME }, { ".m4a", DECODER_QUICKTIME }, { ".mov", DECODER_QUICKTIME }, { ".fla", DECODER_FLAC }, { ".flac", DECODER_FLAC } }; static bool IgnoresWeaponPedsForPCFix(); // Regular functions static RpAtomic* RenderAtomic(RpAtomic* pAtomic, float fComp) { UNREFERENCED_PARAMETER(fComp); return AtomicDefaultRenderCallBack(pAtomic); } static RpAtomic* StaticPropellerRender(RpAtomic* pAtomic) { RwScopedRenderState alphaRef(rwRENDERSTATEALPHATESTFUNCTIONREF); RwRenderStateSet(rwRENDERSTATEALPHATESTFUNCTIONREF, 0); pAtomic = AtomicDefaultRenderCallBack(pAtomic); return pAtomic; } static RpAtomic* MovingPropellerRender(RpAtomic* pAtomic) { RwScopedRenderState alphaRef(rwRENDERSTATEALPHATESTFUNCTIONREF); RwScopedRenderState vertexAlpha(rwRENDERSTATEVERTEXALPHAENABLE); RwRenderStateSet(rwRENDERSTATEALPHATESTFUNCTIONREF, 0); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, reinterpret_cast(TRUE)); pAtomic = AtomicDefaultRenderCallBack(pAtomic); return pAtomic; } RpAtomic* RenderBigVehicleActomic(RpAtomic* pAtomic, float) { const char* pNodeName = GetFrameNodeName(RpAtomicGetFrame(pAtomic)); if ( strncmp(pNodeName, "moving_prop", 11) == 0 ) return MovingPropellerRender(pAtomic); if ( strncmp(pNodeName, "static_prop", 11) == 0 ) return StaticPropellerRender(pAtomic); return AtomicDefaultRenderCallBack(pAtomic); } void RenderVehicleHiDetailAlphaCB_HunterDoor(RpAtomic* pAtomic) { AlphaObjectInfo NewObject; NewObject.callback = RenderAtomic; NewObject.fCompareValue = -std::numeric_limits::infinity(); NewObject.pAtomic = pAtomic; m_alphaList.InsertFront(NewObject); } void RenderWeapon(CPed* pPed) { if ( !IgnoresWeaponPedsForPCFix() ) { pPed->RenderWeapon(true, false, false); } ms_weaponPedsForPC.Insert(pPed); } void RenderWeaponPedsForPC() { RwScopedRenderState vertexAlpha(rwRENDERSTATEVERTEXALPHAENABLE); RwScopedRenderState zWrite(rwRENDERSTATEZWRITEENABLE); RwScopedRenderState fogEnable(rwRENDERSTATEFOGENABLE); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, reinterpret_cast(TRUE)); RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, reinterpret_cast(TRUE)); RwRenderStateSet(rwRENDERSTATEFOGENABLE, reinterpret_cast(TRUE)); const bool renderWeapon = IgnoresWeaponPedsForPCFix(); for ( auto it = ms_weaponPedsForPC.Next( nullptr ); it != nullptr; it = ms_weaponPedsForPC.Next( it ) ) { CPed* ped = **it; ped->SetupLighting(); ped->RenderWeapon(renderWeapon, true, false); ped->RemoveLighting(); } } static CAEFLACDecoder* __stdcall DecoderCtor(CAEDataStream* pData) { return new CAEFLACDecoder(pData); } static CAEWaveDecoder* __stdcall CAEWaveDecoderInit(CAEDataStream* pStream) { return new CAEWaveDecoder(pStream); } namespace ScriptFixes { static void BasketballFix(unsigned char* pBuf, int nSize) { for ( int i = 0, hits = 0; i < nSize && hits < 7; i++, pBuf++ ) { // Pattern check for save pickup XYZ if ( *(unsigned int*)pBuf == 0x449DE19A ) // Save pickup X { hits++; *(float*)pBuf = 1291.8f; } else if ( *(unsigned int*)pBuf == 0xC4416AE1 ) // Save pickup Y { hits++; *(float*)pBuf = -797.8284f; } else if ( *(unsigned int*)pBuf == 0x44886C7B ) // Save pickup Z { hits++; *(float*)pBuf = 1089.5f; } else if ( *(unsigned int*)pBuf == 0x449DF852 ) // Save point X { hits++; *(float*)pBuf = 1286.8f; } else if ( *(unsigned int*)pBuf == 0xC44225C3 ) // Save point Y { hits++; *(float*)pBuf = -797.69f; } else if ( *(unsigned int*)pBuf == 0x44885C7B ) // Save point Z { hits++; *(float*)pBuf = 1089.1f; } else if ( *(unsigned int*)pBuf == 0x43373AE1 ) // Save point A { hits++; *(float*)pBuf = 90.0f; } } } static unsigned char* ScriptSpace; static int* ScriptParams; static size_t ScriptFileSize, ScriptMissionSize; static void InitializeScriptGlobals() { static bool bInitScriptStuff = [] () {; ScriptSpace = *AddressByVersion(0x5D5380, 0x5D5B60, 0x450E34); ScriptParams = *AddressByVersion(0x48995B, 0x46410A, 0x46979A); ScriptFileSize = *AddressByVersion( 0x468E74+1, 0, 0x46E572+1); ScriptMissionSize = *AddressByVersion( 0x489A5A+1, 0, 0x490798+1); return true; } (); } static void SweetsGirlFix() { // Changes @ == int to @ >= int in two places if ( *(uint16_t*)(ScriptSpace+ScriptFileSize+2510) == 0x0039 ) *(uint16_t*)(ScriptSpace+ScriptFileSize+2510) = 0x0029; if ( *(uint16_t*)(ScriptSpace+ScriptFileSize+2680) == 0x0039 ) *(uint16_t*)(ScriptSpace+ScriptFileSize+2680) = 0x0029; } static hook::pattern MakeScriptPattern(bool isMission, std::string_view bytes) { uintptr_t begin, end; if (isMission) { begin = uintptr_t(ScriptSpace) + ScriptFileSize; end = begin + ScriptMissionSize; } else { begin = uintptr_t(ScriptSpace); end = begin + ScriptFileSize; } return hook::make_range_pattern(begin, end, bytes).count_hint(100); } static void MountainCloudBoysFix() { auto pattern = MakeScriptPattern(true, "D6 00 04 00 39 00 03 EF 00 04 02 4D 00 01 90 F2 FF FF D6 00 04 01"); if ( pattern.size() == 1 ) // Faulty code lies under offset 3367 - replace it if it matches { const uint8_t bNewCode[22] = { 0x00, 0x00, 0x00, 0x00, 0xD6, 0x00, 0x04, 0x03, 0x39, 0x00, 0x03, 0x2B, 0x00, 0x04, 0x0B, 0x39, 0x00, 0x03, 0xEF, 0x00, 0x04, 0x02 }; memcpy( pattern.get(0).get(), bNewCode, sizeof(bNewCode) ); } } static void SupplyLinesFix( bool isBeefyBaron ) { auto pattern = MakeScriptPattern(true, isBeefyBaron ? "B8 9E 3A 44" : "B8 1E 2F 44"); if ( pattern.size() == 1 ) // 700.48 -> 10.0 (teleports car with CJ under the building instead) { *pattern.get(0).get() = 10.0f; } } static void DrivingSchoolConesFix() { auto pattern = MakeScriptPattern(true, "04 00 02 20 03 04 00 D6 00 04 00 1A 00 04 2E 02 20 03 4D 00 01 60 75 FF FF BE 00 08 01 07 24 03 20 03 2E 80 08 00 02 20 03 04 01"); if (pattern.size() == 1) // Only destroy as many cones as were created { const uint8_t gotoSkipAssignment[] = { 0x02, 0x00, 0x01, 0x8B, 0x75, 0xFF, 0xFF }; memcpy(pattern.get(0).get(0), gotoSkipAssignment, sizeof(gotoSkipAssignment)); const uint8_t cmpVal[] = { 0x18, 0x00, 0x02, 0x20, 0x03, 0x04, 0x00 }; // trafficcone_counter > 0 memcpy(pattern.get(0).get(11), cmpVal, sizeof(cmpVal)); // trafficcone_counter-- \ DELETE_OBJECT trafficcones[trafficcone_counter] const uint8_t subValDeleteObject[] = { 0x0C, 0x00, 0x02, 0x20, 0x03, 0x04, 0x01, 0x08, 0x01, 0x07, 0x24, 0x03, 0x20, 0x03, 0x2E, 0x80 }; memcpy(pattern.get(0).get(0x1B), subValDeleteObject, sizeof(subValDeleteObject)); // Also set trafficcone_counter to 0 so the first destruction doesn't happen int32_t* trafficcone_counter = reinterpret_cast(ScriptSpace+800); *trafficcone_counter = 0; } } static void BikeSchoolConesFix() { auto pattern = MakeScriptPattern(true, "04 00 02 20 03 04 00 D6 00 04 00 1A 00 04 2E 02 20 03 4D 00 01 F8 AD FF FF 08 01 07 24 03 20 03 2E 80 08 00 02 20 03 04 01"); if (pattern.size() == 1) // Only destroy as many cones as were created { const uint8_t gotoSkipAssignment[] = { 0x02, 0x00, 0x01, 0x21, 0xAE, 0xFF, 0xFF }; memcpy(pattern.get(0).get(0), gotoSkipAssignment, sizeof(gotoSkipAssignment)); const uint8_t cmpVal[] = { 0x18, 0x00, 0x02, 0x20, 0x03, 0x04, 0x00 }; // trafficcone_counter > 0 memcpy(pattern.get(0).get(11), cmpVal, sizeof(cmpVal)); // trafficcone_counter-- \ DELETE_OBJECT trafficcones[trafficcone_counter] const uint8_t subValDeleteObject[] = { 0x0C, 0x00, 0x02, 0x20, 0x03, 0x04, 0x01, 0x08, 0x01, 0x07, 0x24, 0x03, 0x20, 0x03, 0x2E, 0x80 }; memcpy(pattern.get(0).get(0x19), subValDeleteObject, sizeof(subValDeleteObject)); // Also set trafficcone_counter to 0 so the first destruction doesn't happen int32_t* trafficcone_counter = reinterpret_cast(ScriptSpace+800); *trafficcone_counter = 0; } } static void AirRaidFix() { // Give the player back their weapon from the 8th slot instead of stealing it, // but do it properly and load the model before giving the wepaon. // 1.01 PC script saves and restores the weapon, but forgets to load the model. auto save_weapon_gosub_place = MakeScriptPattern(true, "BD 01 03 65 01 06 00 03 48 01 04 00"); auto free_space_after_mission = MakeScriptPattern(true, "EF 04 0E 06 43 41 53 49 4E 4F EF 04 0E 0A 4F 4E 5F 4C 4F 4F 4B 45 52 53 D8 00 51 00"); if (save_weapon_gosub_place.size() == 1 && free_space_after_mission.size() == 1) { using namespace Memory; // "Assemble" the script bytecode because it's more readable this way uintptr_t afterMissionSpace = free_space_after_mission.get(0).get_uintptr(28); auto assembleCommand = [](uintptr_t& mem, std::initializer_list bytes) { uint8_t* buf = reinterpret_cast(mem); std::copy(bytes.begin(), bytes.end(), buf); mem += bytes.size(); }; auto assembleMissionOffset = [](uintptr_t& mem, uintptr_t dest) { const int32_t offset = (uintptr_t(ScriptSpace) + ScriptFileSize) - dest; memcpy(reinterpret_cast(mem), &offset, sizeof(offset)); mem += sizeof(offset); }; auto assembleInt32 = [](uintptr_t& mem, int32_t val) { memcpy(reinterpret_cast(mem), &val, sizeof(val)); mem += sizeof(val); }; // Store the weapon { // GOSUB zero1_store_weapon uintptr_t missionInitSpace = save_weapon_gosub_place.get(0).get_uintptr(5); assembleCommand(missionInitSpace, { 0x50, 0x00, 0x01 }); assembleMissionOffset(missionInitSpace, afterMissionSpace); // zero1_store_weapon: assembleCommand(afterMissionSpace, { 0x06, 0x00, 0x03, 0x48, 0x01, 0x04, 0x00 }); // index_zero1 = 0 assembleCommand(afterMissionSpace, { 0xB8, 0x04, 0x02, 0x0C, 0x00, 0x04, 0x08, 0x03, 0x72, 0x01, 0x03, 0x73, 0x01, 0x03, 0x74, 0x01}); // GET_CHAR_WEAPON_IN_SLOT scplayer 8 weapontype_zero1 ammo_zero1 model_for_weapon_zero1 assembleCommand(afterMissionSpace, { 0x51, 0x00 }); // RETURN } // Restore the weapon { uintptr_t missionCleanupGosub = uintptr_t(ScriptSpace) + ScriptFileSize + 0x1E; const int32_t originalMissionCleanup = *reinterpret_cast(missionCleanupGosub); assembleMissionOffset(missionCleanupGosub, afterMissionSpace); assembleCommand(afterMissionSpace, { 0x12, 0x81 }); // NOT HAS_DEATHARREST_BEEN_EXECUTED assembleCommand(afterMissionSpace, { 0x4D, 0x00, 0x01 }); // GOTO_IF_FALSE originalMissionCleanup // We can jupm directly back to the original mission cleanup to simplify code generation assembleInt32(afterMissionSpace, originalMissionCleanup); // The original fix from 1.01/2.0 PC versions only added GIVE_WEAPON_TO_CHAR, without loading the model // We fix it properly by ensuring the model is loaded before giving the weapon assembleCommand(afterMissionSpace, { 0x47, 0x02, 0x03, 0x74, 0x01 }); // REQUEST_MODEL model_for_weapon_zero1 assembleCommand(afterMissionSpace, { 0x8B, 0x03 }); // LOAD_ALL_MODELS_NOW assembleCommand(afterMissionSpace, { 0xB2, 0x01, 0x02, 0x0C, 0x00, 0x03, 0x72, 0x01, 0x03, 0x73, 0x01 }); // GIVE_WEAPON_TO_CHAR scplayer weapontype_zero1 ammo_zero1 assembleCommand(afterMissionSpace, { 0x49, 0x02, 0x03, 0x74, 0x01 }); // MARK_MODEL_AS_NO_LONGER_NEEDED model_for_weapon_zero1 assembleCommand(afterMissionSpace, { 0x02, 0x00, 0x01 }); // GOTO originalMissionCleanup assembleInt32(afterMissionSpace, originalMissionCleanup); } } } static void QuadrupleStuntBonus() { // IF HEIGHT_FLOAT_HJ > 4.0 -> IF HEIGHT_INT_HJ > 4 auto pattern = MakeScriptPattern(false, "20 00 02 60 14 06 00 00 80 40"); if ( pattern.size() == 1 ) { const uint8_t newCode[10] = { 0x18, 0x00, 0x02, 0x30, 0x14, 0x01, 0x04, 0x00, 0x00, 0x00 }; memcpy( pattern.get(0).get(), newCode, sizeof(newCode) ); } } void TheScriptsLoad_BasketballFix() { TheScriptsLoad(); InitializeScriptGlobals(); BasketballFix(ScriptSpace+8, *(int*)(ScriptSpace+3)); QuadrupleStuntBonus(); } static void StartNewMission_SCMFixes() { InitializeScriptGlobals(); const int missionID = ScriptParams[0]; switch (missionID) { // INITIAL - Basketball fix, Quadruple Stunt Bonus case 0: BasketballFix(ScriptSpace+ScriptFileSize, ScriptMissionSize); QuadrupleStuntBonus(); break; // HOODS5 - Sweet's Girl fix case 18: SweetsGirlFix(); break; // WUZI1 - Mountain Cloud Boys fix case 53: MountainCloudBoysFix(); break; // DSKOOL - Driving School cones fix // By Wesser case 71: DrivingSchoolConesFix(); break; // BSKOOL - Bike School cones fix // By Wesser case 120: BikeSchoolConesFix(); break; // ZERO1 - Air Raid fix case 72: AirRaidFix(); break; // ZERO2 - Supply Lines fix case 73: SupplyLinesFix( false ); break; // ZERO5 - Beefy Baron fix case 10: SupplyLinesFix( true ); break; default: break; } } template static void (*orgWipeLocalVariableMemoryForMissionScript)(); template static void WipeLocalVariableMemoryForMissionScript_ApplyFixes() { orgWipeLocalVariableMemoryForMissionScript(); StartNewMission_SCMFixes(); } HOOK_EACH_FUNC(SCMFixes, orgWipeLocalVariableMemoryForMissionScript, WipeLocalVariableMemoryForMissionScript_ApplyFixes) } // 1.01 kinda fixed it bool GetCurrentZoneLockedOrUnlocked(float fPosX, float fPosY) { // Exploit RAII really bad static const float GridXOffset = **(float**)(0x572135+2), GridYOffset = **(float**)(0x57214A+2); static const float GridXSize = **(float**)(0x57213B+2), GridYSize = **(float**)(0x572153+2); static const int GridXNum = static_cast((2.0f*GridXOffset) * GridXSize), GridYNum = static_cast((2.0f*GridYOffset) * GridYSize); static unsigned char* const ZonesVisited = *(unsigned char**)(0x57216A) - (GridYNum-1); // 1.01 fixed it! int Xindex = static_cast((fPosX+GridXOffset) * GridXSize); int Yindex = static_cast((fPosY+GridYOffset) * GridYSize); // "Territories fix" if ( (Xindex >= 0 && Xindex < GridXNum) && (Yindex >= 0 && Yindex < GridYNum) ) return ZonesVisited[GridXNum*Xindex - Yindex + (GridYNum-1)] != 0; // Outside of map bounds return true; } bool GetCurrentZoneLockedOrUnlocked_Steam(float fPosX, float fPosY) { static unsigned char* const ZonesVisited = *(unsigned char**)(0x5870E8) - 9; int Xindex = static_cast((fPosX+3000.0f) / 600.0f); int Yindex = static_cast((fPosY+3000.0f) / 600.0f); // "Territories fix" if ( (Xindex >= 0 && Xindex < 10) && (Yindex >= 0 && Yindex < 10) ) return ZonesVisited[10*Xindex - Yindex + 9] != 0; // Outside of map bounds return true; } CRGBA* __fastcall BlendGangColour(CRGBA* pThis, void*, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { const double colourIntensity = std::min( static_cast(pCurrZoneInfo->ZoneColour.a) / 120.0, 1.0 ); *pThis = CRGBA(BlendSqr( HudColour[3], CRGBA(r, g, b), colourIntensity ), a); return pThis; } static bool bColouredZoneNames; CRGBA* __fastcall BlendGangColour_Dynamic(CRGBA* pThis, void*, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if ( bColouredZoneNames ) { return BlendGangColour(pThis, nullptr, r, g, b, a); } *pThis = CRGBA(HudColour[3], a); return pThis; } void SunAndMoonFarClip() { fSunFarClip = std::min(1500.0f, fFarClipZ); DoSunAndMoon(); } // STEAM ONLY template void DrawRect_HalfPixel_Steam(CRect& rect, const CRGBA& rgba) { if constexpr ( bX1 ) rect.x1 -= 0.5f; if constexpr ( bY1 ) rect.y1 -= 0.5f; if constexpr ( bX2 ) rect.x2 -= 0.5f; if constexpr ( bY2 ) rect.y2 -= 0.5f; // Steam CSprite2d::DrawRect ((void(*)(const CRect&, const CRGBA&))0x75CDA0)(rect, rgba); } char* GetMyDocumentsPathSA() { static char* const pDocumentsPath = [&] () -> char* { static char cUserFilesPath[MAX_PATH]; char* const ppTempBufPtr = Memory::GetVersion().version == 0 ? *AddressByRegion_10(0x744FE5) : cUserFilesPath; if ( SHGetFolderPathA(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, ppTempBufPtr) == S_OK ) { char** const ppUserFilesDir = AddressByVersion(0x74503F, 0x74586F, 0x77EE50, { "6A 00 68 80 00 00 02 6A 03 6A 00 6A 01 B9 07 00 00 00", 0x12 + 1 }); PathAppendA(ppTempBufPtr, *ppUserFilesDir); CreateDirectoryA(ppTempBufPtr, nullptr); } else { strcpy_s(ppTempBufPtr, MAX_PATH, "data"); } char cTmpPath[MAX_PATH]; strcpy_s(cTmpPath, ppTempBufPtr); PathAppendA(cTmpPath, "Gallery"); CreateDirectoryA(cTmpPath, nullptr); strcpy_s(cTmpPath, ppTempBufPtr); PathAppendA(cTmpPath, "User Tracks"); CreateDirectoryA(cTmpPath, nullptr); return ppTempBufPtr; } (); return pDocumentsPath; } static LARGE_INTEGER FrameTime; __declspec(safebuffers) int32_t GetTimeSinceLastFrame() { LARGE_INTEGER curTime; QueryPerformanceCounter(&curTime); return int32_t(curTime.QuadPart - FrameTime.QuadPart); } static int (*RsEventHandler)(int, void*); int NewFrameRender(int nEvent, void* pParam) { QueryPerformanceCounter(&FrameTime); return RsEventHandler(nEvent, pParam); } #include #include static std::ranlux48 generator (time(nullptr)); int32_t Int32Rand() { return generator() & INT32_MAX; } auto FlushSpriteBuffer = AddressByVersion(0x70CF20, 0x70D750, 0x7591E0, { "85 C0 0F 8E ? ? ? ? 83 3D", -5 }); void FlushLensSwitchZ( RwRenderState rwa, void* rwb ) { FlushSpriteBuffer(); RwRenderStateSet( rwa, rwb ); } auto InitSpriteBuffer2D = AddressByVersion(0x70CFD0, 0x70D800, 0x759290, { "A1 ? ? ? ? D9 80 ? ? ? ? A1" }); void InitBufferSwitchZ( RwRenderState rwa, void* rwb ) { RwRenderStateSet( rwa, rwb ); InitSpriteBuffer2D(); } static void* const g_fx = *AddressByVersion(0x4A9649, 0x4AA4EF, 0x4B2BB9, { "56 8D 4F 0C E8", 9 + 1 }); static int32_t GetFxQuality() { return *(int32_t*)( (uint8_t*)g_fx + 0x54 ); } DWORD* msaaValues = *AddressByVersion(0x4CCBC5, 0x4CCDB5, 0x4D7462, { "8B 3D ? ? ? ? 57 8B 7B 18", 2 }); // These patterns have 3 hits, but that's fine as all 3 refer to exact same variables RwRaster*& pMirrorBuffer = **AddressByVersion(0x723001, 0x723831, 0x754971, { "A1 ? ? ? ? 3B C6 74 0F 50 E8 ? ? ? ? 83 C4 04 89 35 ? ? ? ? 89 35 ? ? ? ? 89 35 ? ? ? ? 5E C3", -6 + 2 }); RwRaster*& pMirrorZBuffer = **AddressByVersion(0x72301C, 0x72384C, 0x75498C, { "A1 ? ? ? ? 3B C6 74 0F 50 E8 ? ? ? ? 83 C4 04 89 35 ? ? ? ? 89 35 ? ? ? ? 89 35 ? ? ? ? 5E C3", 1 }); void CreateMirrorBuffers() { if ( pMirrorBuffer == nullptr ) { DWORD oldMsaa[2] = { msaaValues[0], msaaValues[1] }; msaaValues[0] = msaaValues[1] = 0; int32_t quality = GetFxQuality(); RwInt32 width, height; if ( quality >= 3 ) // Very High { width = 2048; height = 1024; } else if ( quality >= 1 ) // Medium { width = 1024; height = 512; } else { width = 512; height = 256; } pMirrorBuffer = RwRasterCreate( width, height, 0, rwRASTERTYPECAMERATEXTURE ); pMirrorZBuffer = RwRasterCreate( width, height, 0, rwRASTERTYPEZBUFFER ); msaaValues[0] = oldMsaa[0]; msaaValues[1] = oldMsaa[1]; } } namespace MSAAFixes { static RwUInt32 GetMaxMultiSamplingLevels_BitScan(RwUInt32 maxSamples) { RwUInt32 option; _BitScanForward( (DWORD*)&option, maxSamples ); return option + 1; } template static RwUInt32 (*orgGetMaxMultiSamplingLevels)(); template static RwUInt32 GetMaxMultiSamplingLevels() { return GetMaxMultiSamplingLevels_BitScan(orgGetMaxMultiSamplingLevels()); } HOOK_EACH_FUNC(GetMaxMultiSamplingLevels, orgGetMaxMultiSamplingLevels, GetMaxMultiSamplingLevels); template static void (*orgSetOrChangeMultiSamplingLevels)(RwUInt32); template static void SetOrChangeMultiSamplingLevels(RwUInt32 level) { orgSetOrChangeMultiSamplingLevels( 1 << (level - 1) ); } HOOK_EACH_FUNC(SetOrChangeMultiSamplingLevels, orgSetOrChangeMultiSamplingLevels, SetOrChangeMultiSamplingLevels); void MSAAText( char* buffer, const char*, DWORD level ) { sprintf_s( buffer, 100, "%ux", 1 << level ); } } static RwInt32 numSavedVideoModes; static RwInt32 (*orgGetNumVideoModes)(); RwInt32 GetNumVideoModes_Store() { return numSavedVideoModes = orgGetNumVideoModes(); } RwInt32 GetNumVideoModes_Retrieve() { return numSavedVideoModes; } namespace UnitializedCollisionDataFix { static void* (*orgMemMgrMalloc)(RwUInt32, RwUInt32); static void* CollisionData_MallocAndInit( RwUInt32 size, RwUInt32 hint ) { CColData* mem = (CColData*)orgMemMgrMalloc( size, hint ); mem->m_bFlags = 0; mem->m_dwNumShadowTriangles = mem->m_dwNumShadowVertices = 0; mem->m_pShadowVertices = mem->m_pShadowTriangles = nullptr; return mem; } template static void* (*orgNewAlloc)(size_t); template static void* CollisionData_NewAndInit(size_t size) { CColData* mem = (CColData*)orgNewAlloc(size); mem->m_bFlags = 0; return mem; } HOOK_EACH_FUNC(CollisionDataNew, orgNewAlloc, CollisionData_NewAndInit); } static void (*orgEscalatorsUpdate)(); void UpdateEscalators() { if ( !CEscalator::ms_entitiesToRemove.empty() ) { for ( auto it : CEscalator::ms_entitiesToRemove ) { WorldRemove( it ); delete it; } CEscalator::ms_entitiesToRemove.clear(); } orgEscalatorsUpdate(); } static char** pStencilShadowsPad = *AddressByVersion(0x70FC4F, 0, 0x75E286, { "8B 15 ? ? ? ? D8 65 A8", 2 }); void StencilShadowAlloc( ) { static char* pMemory = [] () {; char* mem = static_cast( ::operator new( 3 * 0x6000 ) ); pStencilShadowsPad[0] = mem; pStencilShadowsPad[1] = mem+0x6000; pStencilShadowsPad[2] = mem+(2*0x6000); return mem; } (); } RwBool GTARtAnimInterpolatorSetCurrentAnim(RtAnimInterpolator* animI, RtAnimAnimation* anim) { animI->pCurrentAnim = anim; animI->currentTime = 0.0f; const RtAnimInterpolatorInfo* info = anim->interpInfo; animI->currentInterpKeyFrameSize = info->interpKeyFrameSize; animI->currentAnimKeyFrameSize = info->animKeyFrameSize; animI->keyFrameApplyCB = info->keyFrameApplyCB; animI->keyFrameBlendCB = info->keyFrameBlendCB; animI->keyFrameInterpolateCB = info->keyFrameInterpolateCB; animI->keyFrameAddCB = info->keyFrameAddCB; for ( RwInt32 i = 0; i < animI->numNodes; ++i ) { RtAnimKeyFrameInterpolate( animI, rtANIMGETINTERPFRAME( animI, i ), (RwChar*)anim->pFrames + i * animI->currentAnimKeyFrameSize, (RwChar*)anim->pFrames + ( i + animI->numNodes) * animI->currentAnimKeyFrameSize, 0.0f ); } animI->pNextFrame = (RwChar*)anim->pFrames + 2 * animI->currentAnimKeyFrameSize * animI->numNodes; return TRUE; } DWORD WINAPI CdStreamSetFilePointer( HANDLE hFile, uint32_t distanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod ) { assert( lpDistanceToMoveHigh == nullptr ); LARGE_INTEGER li; li.QuadPart = int64_t(distanceToMove) << 11; return SetFilePointer( hFile, li.LowPart, &li.HighPart, dwMoveMethod ); } static auto* const pCdStreamSetFilePointer = CdStreamSetFilePointer; static void (*orgDrawScriptSpritesAndRectangles)(uint8_t); void DrawScriptSpritesAndRectangles( uint8_t arg ) { RwRenderStateSet( rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR ); orgDrawScriptSpritesAndRectangles( arg ); } // Now in VehicleSA.cpp bool ReadDoubleRearWheels(const wchar_t* pPath); bool __stdcall CheckDoubleRWheelsList( void* modelInfo, uint8_t* handlingData ); CVehicleModelInfo* (__thiscall *orgVehicleModelInfoCtor)(CVehicleModelInfo*); CVehicleModelInfo* __fastcall VehicleModelInfoCtor(CVehicleModelInfo* me) { orgVehicleModelInfoCtor(me); // Hack to satisfy some null checks static uintptr_t DUMMY; me->__removedInSilentPatch = &DUMMY; me->m_dirtMaterials = nullptr; me->m_numDirtMaterials = 0; std::fill( std::begin( me->m_staticDirtMaterials ), std::end( me->m_staticDirtMaterials ), nullptr ); return me; } static void (*RemoveFromInterestingVehicleList)(CVehicle*) = AddressByVersion( 0x423ED0, Memory::PatternAndOffset("39 10 75 06 C7 00 00 00 00 00 83 C0 04 49 75 F0 5D C3", -0x10) ); static void (*orgRecordVehicleDeleted)(CVehicle*); static void RecordVehicleDeleted_AndRemoveFromVehicleList( CVehicle* vehicle ) { orgRecordVehicleDeleted( vehicle ); RemoveFromInterestingVehicleList( vehicle ); } static int currDisplayedSplash_ForLastSplash = 0; static void DoPCScreenChange_Mod() { static int& currDisplayedSplash = **AddressByVersion( 0x590B22 + 1, Memory::PatternAndOffset("8B 51 20 6A 01 6A 0C FF D2 83 C4 08 E8", 17 + 1) ); static const int numSplashes = [] () -> int { RwTexture** begin = *AddressByVersion( 0x590CB4 + 1, Memory::PatternAndOffset("8D 49 00 83 3E 00 74 07 8B CE E8", -5 + 1) ); RwTexture** end = *AddressByVersion( 0x590CCE + 2, Memory::PatternAndOffset("8D 49 00 83 3E 00 74 07 8B CE E8", 18 + 2) ); return std::distance( begin, end ); } () - 1; if ( currDisplayedSplash >= numSplashes ) { currDisplayedSplash = 1; currDisplayedSplash_ForLastSplash = numSplashes + 1; } else { currDisplayedSplash_ForLastSplash = ++currDisplayedSplash; } } static bool bUseAaronSun; static CVector curVecToSun; static void (*orgSetLightsWithTimeOfDayColour)( RpWorld* ); static void SetLightsWithTimeOfDayColour_SilentPatch( RpWorld* world ) { static CVector* const VectorToSun = *AddressByVersion( 0x6FC5B7 + 3, Memory::PatternAndOffset("DC 0D ? ? ? ? 8D 04 40 8B 0C 85", 9 + 3) ); static int& CurrentStoredValue = **AddressByVersion( 0x6FC632 + 1, Memory::PatternAndOffset("84 C0 0F 84 AB 01 00 00 A1", 8 + 1) ); static CVector& vecDirnLightToSun = **AddressByVersion( 0x5BC040 + 2, Memory::PatternAndOffset("E8 ? ? ? ? D9 5D F8 D9 45 F8 D8 4D F4 D9 1D", 15 + 2) ); curVecToSun = bUseAaronSun ? VectorToSun[CurrentStoredValue] : vecDirnLightToSun; orgSetLightsWithTimeOfDayColour( world ); } // ============= CdStream data racing issue ============= struct CdStream { DWORD nSectorOffset; DWORD nSectorsToRead; LPVOID lpBuffer; BYTE field_C; BYTE bLocked; BYTE bInUse; BYTE field_F; DWORD status; union Sync { HANDLE semaphore; CONDITION_VARIABLE cv; } sync; HANDLE hFile; OVERLAPPED overlapped; }; static_assert(sizeof(CdStream) == 0x30, "Incorrect struct size: CdStream"); namespace CdStreamSync { static CRITICAL_SECTION CdStreamCritSec; // WRL-like lock object // Balancing lock/unlock is up to the user! class SyncLock { public: SyncLock( CRITICAL_SECTION& critSec ) : m_critSec( critSec ) { Lock(); } ~SyncLock() { Unlock(); } void Lock() const { EnterCriticalSection( &m_critSec ); } void Unlock() const { LeaveCriticalSection( &m_critSec ); } CRITICAL_SECTION* Get() const { return &m_critSec; } private: SyncLock( const SyncLock& ) = delete; SyncLock( SyncLock&& ) = delete; SyncLock& operator=( const SyncLock& ) = delete; SyncLock& operator=( SyncLock&& ) = delete; CRITICAL_SECTION& m_critSec; }; // Function pointers for game to use static CdStream::Sync (__stdcall *CdStreamInitializeSyncObject)(); static DWORD (__stdcall *CdStreamSyncOnObject)( CdStream* stream ); static void (__stdcall *CdStreamThreadOnObject)( CdStream* stream ); static void (__stdcall *CdStreamCloseObject)( CdStream::Sync* sync ); static void (__stdcall *CdStreamShutdownSyncObject)( CdStream* stream ); static void __stdcall CdStreamShutdownSyncObject_Stub( CdStream* stream, size_t idx ) { CdStreamShutdownSyncObject( &stream[idx] ); } // Fixed return values for GetOverlappedResult - stock code assumes "nonzero" equals 1, might not be future proof static uint32_t WINAPI GetOverlappedResult_SilentPatch( HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait ) { return GetOverlappedResult( hFile, lpOverlapped, lpNumberOfBytesTransferred, bWait ) != FALSE ? 0 : 254; } static auto* const pGetOverlappedResult = &GetOverlappedResult_SilentPatch; namespace Sema { CdStream::Sync __stdcall InitializeSyncObject() { CdStream::Sync object; object.semaphore = CreateSemaphore( nullptr, 0, 2, nullptr ); return object; } void __stdcall ShutdownSyncObject( CdStream* stream ) { CloseHandle( stream->sync.semaphore ); } DWORD __stdcall CdStreamSync( CdStream* stream ) { auto lock = SyncLock( CdStreamCritSec ); if ( stream->nSectorsToRead != 0 ) { stream->bLocked = 1; lock.Unlock(); WaitForSingleObject( stream->sync.semaphore, INFINITE ); lock.Lock(); } stream->bInUse = 0; return stream->status; } void __stdcall CdStreamThread( CdStream* stream ) { auto lock = SyncLock( CdStreamCritSec ); stream->nSectorsToRead = 0; if ( stream->bLocked != 0 ) { ReleaseSemaphore( stream->sync.semaphore, 1, nullptr ); } stream->bInUse = 0; } } namespace CV { namespace Funcs { static decltype(InitializeConditionVariable)* pInitializeConditionVariable = nullptr; static decltype(SleepConditionVariableCS)* pSleepConditionVariableCS = nullptr; static decltype(WakeConditionVariable)* pWakeConditionVariable = nullptr; static bool TryInit() { const HMODULE kernelDLL = GetModuleHandle( TEXT("kernel32") ); assert( kernelDLL != nullptr ); pInitializeConditionVariable = (decltype(pInitializeConditionVariable))GetProcAddress( kernelDLL, "InitializeConditionVariable" ); pSleepConditionVariableCS = (decltype(pSleepConditionVariableCS))GetProcAddress( kernelDLL, "SleepConditionVariableCS" ); pWakeConditionVariable = (decltype(pWakeConditionVariable))GetProcAddress( kernelDLL, "WakeConditionVariable" ); return pInitializeConditionVariable != nullptr && pSleepConditionVariableCS != nullptr && pWakeConditionVariable != nullptr; } } CdStream::Sync __stdcall InitializeSyncObject() { CdStream::Sync object; Funcs::pInitializeConditionVariable( &object.cv ); return object; } void __stdcall ShutdownSyncObject( CdStream* stream ) { } DWORD __stdcall CdStreamSync( CdStream* stream ) { auto lock = SyncLock( CdStreamCritSec ); while ( stream->nSectorsToRead != 0 ) { Funcs::pSleepConditionVariableCS( &stream->sync.cv, lock.Get(), INFINITE ); } stream->bInUse = 0; return stream->status; } void __stdcall CdStreamThread( CdStream* stream ) { auto lock = SyncLock( CdStreamCritSec ); stream->nSectorsToRead = 0; Funcs::pWakeConditionVariable( &stream->sync.cv ); stream->bInUse = 0; } } static void (*orgCdStreamInitThread)(); static void CdStreamInitThread() { if ( ModCompat::bCdStreamFallBackForOldML != true && CV::Funcs::TryInit() ) { CdStreamInitializeSyncObject = CV::InitializeSyncObject; CdStreamShutdownSyncObject = CV::ShutdownSyncObject; CdStreamSyncOnObject = CV::CdStreamSync; CdStreamThreadOnObject = CV::CdStreamThread; } else { CdStreamInitializeSyncObject = Sema::InitializeSyncObject; CdStreamShutdownSyncObject = Sema::ShutdownSyncObject; CdStreamSyncOnObject = Sema::CdStreamSync; CdStreamThreadOnObject = Sema::CdStreamThread; } InitializeCriticalSection( &CdStreamCritSec ); FLAUtils::SetCdStreamWakeFunction( []( CdStream* pStream ) { CdStreamThreadOnObject( pStream ); } ); orgCdStreamInitThread(); } } // Dancing timers fix static long UtilsVariablesInit = 0; static LARGE_INTEGER UtilsStartTime; static LARGE_INTEGER UtilsFrequency; static BOOL WINAPI AudioUtilsFrequency( PLARGE_INTEGER lpFrequency ) { ::QueryPerformanceFrequency( &UtilsFrequency ); lpFrequency->QuadPart = UtilsFrequency.QuadPart; return TRUE; } static auto* const pAudioUtilsFrequency = &AudioUtilsFrequency; static int64_t AudioUtilsGetStartTime() { QueryPerformanceCounter( &UtilsStartTime ); _InterlockedExchange( &UtilsVariablesInit, 1 ); return UtilsStartTime.QuadPart; } static int64_t AudioUtilsGetCurrentTimeInMs() { if ( _InterlockedCompareExchange( &UtilsVariablesInit, 0, 0 ) == 0 ) { return 0; } LARGE_INTEGER currentTime; QueryPerformanceCounter( ¤tTime ); return ((currentTime.QuadPart - UtilsStartTime.QuadPart) * 1000) / UtilsFrequency.QuadPart; } // Minimal HUD changes namespace MinimalHud { static CRGBA* __fastcall SetRGBA_FloatAlpha( CRGBA* rgba, void*, uint8_t red, uint8_t green, uint8_t blue, float alpha ) { rgba->r = red; rgba->g = green; rgba->b = blue; rgba->a = static_cast(alpha); return rgba; } static void (*orgRenderOneXLUSprite)(float, float, float, float, float, uint8_t, uint8_t, uint8_t, int16_t, float, uint8_t, uint8_t, uint8_t); static void RenderXLUSprite_FloatAlpha( float arg1, float arg2, float arg3, float arg4, float arg5, uint8_t red, uint8_t green, uint8_t blue, int16_t mult, float arg10, float alpha, uint8_t arg12, uint8_t arg13 ) { orgRenderOneXLUSprite( arg1, arg2, arg3, arg4, arg5, red, green, blue, mult, arg10, static_cast(alpha), arg12, arg13 ); } } // 6 directionals on Medium/High/Very High Visual FX int32_t GetMaxExtraDirectionals( uint32_t areaID ) { return areaID != 0 || GetFxQuality() >= 1 ? 6 : 4; } static CVehicle* FindPlayerVehicle_RCWrap( int playerID, bool ) { return FindPlayerVehicle( playerID, true ); } // ============= Credits! ============= namespace Credits { static void (*PrintCreditText)(float scaleX, float scaleY, const char* text, unsigned int& pos, float timeOffset, bool isHeader); static void (*PrintCreditText_Hooked)(float scaleX, float scaleY, const char* text, unsigned int& pos, float timeOffset, bool isHeader); static void PrintCreditSpace( float scale, unsigned int& pos ) { pos += static_cast( scale * 25.0f ); } constexpr char xvChar(const char ch) { constexpr uint8_t xv = SILENTPATCH_REVISION_ID; return ch ^ xv; } constexpr char operator "" _xv(const char ch) { return xvChar(ch); } static void PrintSPCredits( float scaleX, float scaleY, const char* text, unsigned int& pos, float timeOffset, bool isHeader ) { // Original text we intercepted PrintCreditText_Hooked( scaleX, scaleY, text, pos, timeOffset, isHeader ); PrintCreditSpace( 1.5f, pos ); { char spText[] = { 'A'_xv, 'N'_xv, 'D'_xv, '\0'_xv }; for ( auto& ch : spText ) ch = xvChar(ch); PrintCreditText( scaleX, scaleY, spText, pos, timeOffset, true ); } PrintCreditSpace( 1.5f, pos ); { char spText[] = { 'A'_xv, 'd'_xv, 'r'_xv, 'i'_xv, 'a'_xv, 'n'_xv, ' '_xv, '\"'_xv, 'S'_xv, 'i'_xv, 'l'_xv, 'e'_xv, 'n'_xv, 't'_xv, '\"'_xv, ' '_xv, 'Z'_xv, 'd'_xv, 'a'_xv, 'n'_xv, 'o'_xv, 'w'_xv, 'i'_xv, 'c'_xv, 'z'_xv, '\0'_xv }; for ( auto& ch : spText ) ch = xvChar(ch); PrintCreditText( scaleX, scaleY, spText, pos, timeOffset, false ); } } } // ============= Bicycle fire fix ============= namespace BicycleFire { CPed* GetVehicleDriver( const CVehicle* vehicle ) { return vehicle->GetDriver(); } void __fastcall DoStuffToGoOnFire_NullAndPlayerCheck( CPed* ped ) { if ( ped != nullptr && ped->IsPlayer() ) { static_cast(ped)->DoStuffToGoOnFire(); } } } // ============= Keyboard latency input fix ============= namespace KeyboardInputFix { static void* NewKeyState; static void* OldKeyState; static void* TempKeyState; static size_t objSize; static void (__fastcall *orgClearSimButtonPressCheckers)(void*); void __fastcall ClearSimButtonPressCheckers(void* pThis) { memcpy( OldKeyState, NewKeyState, objSize ); memcpy( NewKeyState, TempKeyState, objSize ); orgClearSimButtonPressCheckers(pThis); } } // ============= handling.cfg name matching fix ============= namespace HandlingNameLoadFix { void strncpy_Fix( const char** destination, const char* source, size_t ) { *destination = source; } int strncmp_Fix( const char* str1, const char** str2, size_t ) { return strcmp( str1, *str2 ); } }; // ============= Firela ladder animation ============= namespace FirelaHook { static uintptr_t UpdateMovingCollisionJmp; static uintptr_t HydraulicControlJmpBack; void __declspec(naked) TestFirelaAndFlags() { __asm { push ecx // Required in 0x6B1FE4: test cl, cl mov ecx, esi call CVehicle::HasFirelaLadder pop ecx test al, al jnz TestFirelaAndFlags_UpdateMovingCollision test [esi].hFlagsLocal, FLAG_HYDRAULICS_INSTALLED jmp [HydraulicControlJmpBack] TestFirelaAndFlags_UpdateMovingCollision: jmp [UpdateMovingCollisionJmp] } } static uintptr_t FollowCarCamNoMovement; static uintptr_t FollowCarCamJmpBack; void __declspec(naked) CamControlFirela() { __asm { mov ecx, edi call CVehicle::HasFirelaLadder test al, al jnz TestFirelaAndFlags_UpdateMovingCollision mov eax, [edi].m_dwVehicleClass jmp [FollowCarCamJmpBack] TestFirelaAndFlags_UpdateMovingCollision: jmp [FollowCarCamNoMovement] } } } namespace HierarchyTypoFix { // Allow wheel_lm vs wheel_lm_dummy and miscX vs misc_X typos // Must be sorted by second parameter constexpr std::pair typosAndFixes[] = { { "boat_moving_hi", "boat_moving" }, { "misc_a", "misca" }, { "misc_b", "miscb" }, { "taillights", "tailights" }, { "taillights2", "tailights2" }, { "transmission_f", "transmision_f" }, { "transmission_r", "transmision_r" }, { "wheel_lm_dummy", "wheel_lm" }, }; static int (*orgStrcasecmp)(const char*, const char*); int strcasecmp( const char* dataName, const char* nodeName ) { /*assert( std::is_sorted(std::begin(typosAndFixes), std::end(typosAndFixes), [] (const auto& a, const auto& b) { return _stricmp( a.second, b.second ) < 0; }) );*/ const int origComp = orgStrcasecmp( dataName, nodeName ); if ( origComp == 0 ) return 0; for ( const auto& typo : typosAndFixes ) { const int nodeComp = _stricmp( typo.second, nodeName ); if ( nodeComp > 0 ) break; if ( nodeComp == 0 && _stricmp( typo.first, dataName ) == 0 ) { return 0; } } return origComp; } } // Resetting stats and variables on New Game namespace VariableResets { // Custom reset values for variables template struct TimeNextMadDriverChaseCreated_t { T m_timer; TimeNextMadDriverChaseCreated_t() : m_timer( (static_cast(Int32Rand()) / INT32_MAX) * 600.0f + 600.0f ) { } }; template struct ResetToValue_t { T m_value; ResetToValue_t() : m_value(val) { } }; using ResetToTrue_t = ResetToValue_t; using VarVariant = std::variant< bool*, int*, TimeNextMadDriverChaseCreated_t*, ResetToTrue_t* >; std::vector GameVariablesToReset; std::vector PickupDefs, CarGeneratorDefs, StuntJumpDefs; static void ReInitOurVariables() { for ( const auto& var : GameVariablesToReset ) { std::visit( []( auto&& v ) { *v = {}; }, var ); } } static std::string_view TrimWhitespace(std::string_view line) { const size_t first = line.find_first_not_of(' '); if (std::string_view::npos != first) { const size_t last = line.find_last_not_of(' '); line = line.substr(first, (last - first + 1)); } return line; } static void (*orgLoadPickup)(const char* line); static void LoadPickup_SaveLine(const char* line) { orgLoadPickup(line); PickupDefs.emplace_back(TrimWhitespace(line)); } static void (*orgLoadCarGenerator)(const char* line, int originScene); static void LoadCarGenerator_SaveLine(const char* line, int originScene) { orgLoadCarGenerator(line, originScene); CarGeneratorDefs.emplace_back(TrimWhitespace(line)); } static void (*orgLoadStuntJump)(const char* line); static void LoadStuntJump_SaveLine(const char* line) { orgLoadStuntJump(line); StuntJumpDefs.emplace_back(TrimWhitespace(line)); } static void ReloadObjectDefinitionsAfterReinit() { for (const auto& pickup : PickupDefs) { orgLoadPickup(pickup.c_str()); } for (const auto& carGenerator : CarGeneratorDefs) { orgLoadCarGenerator(carGenerator.c_str(), 0); } for (const auto& stuntJump : StuntJumpDefs) { orgLoadStuntJump(stuntJump.c_str()); } } template static void (*orgReInitGameObjectVariables)(); template void ReInitGameObjectVariables() { // First reinit "our" variables in case stock ones rely on those during resetting ReInitOurVariables(); orgReInitGameObjectVariables(); // Then after the normal restart, re-instate pickups, car generators and stunt jumps from text IPLs as they have been ReloadObjectDefinitionsAfterReinit(); } HOOK_EACH_FUNC(ReInitGameObjectVariables, orgReInitGameObjectVariables, ReInitGameObjectVariables); } namespace LightbeamFix { static bool hookedSuccessfully = false; static CVehicle* currentHeadLightBeamVehicle; void SetCurrentVehicle( CVehicle* vehicle ) { currentHeadLightBeamVehicle = vehicle; } template class RenderStateWrapper { private: static inline void* SavedState; static void PushState( RwRenderState state, void* value ) { assert( State == state ); RwRenderStateGet( state, &SavedState ); RwRenderStateSet( state, value ); } static inline const auto pPushState = &PushState; static void PopState( RwRenderState state, void* value ) { assert( State == state ); void* valueToRestore = SavedState; if constexpr ( State == rwRENDERSTATECULLMODE ) { assert( currentHeadLightBeamVehicle != nullptr ); if ( currentHeadLightBeamVehicle != nullptr && currentHeadLightBeamVehicle->IgnoresLightbeamFix() ) { valueToRestore = value; } } RwRenderStateSet( state, valueToRestore ); // Restore states R* did not restore after changing them if constexpr ( State == rwRENDERSTATEDESTBLEND ) { RenderStateWrapper::PopAnotherState(); } if constexpr ( State == rwRENDERSTATECULLMODE ) { RenderStateWrapper::PopAnotherState(); RenderStateWrapper::PopAnotherState(); } } static inline const auto pPopState = &PopState; public: static void PopAnotherState() { PopState( State, nullptr ); } static inline const uintptr_t PushStatePPtr = reinterpret_cast(&pPushState) - 0x20; static inline const uintptr_t PopStatePPtr = reinterpret_cast(&pPopState) - 0x20; }; } namespace TrueInvicibility { static bool isEnabled = false; static uintptr_t WillKillJumpBack; void __declspec(naked) ComputeWillKillPedHook() { _asm { cmp dword ptr [ebp+0Ch], WEAPONTYPE_LAST_WEAPONTYPE jl ComputeWillKillPedHook_DoNotKill cmp [isEnabled], 0 je ComputeWillKillPedHook_Kill cmp dword ptr [ebp+0Ch], WEAPONTYPE_UZI_DRIVEBY jne ComputeWillKillPedHook_Kill ComputeWillKillPedHook_DoNotKill: pop esi pop ebp pop ebx retn 0Ch ComputeWillKillPedHook_Kill: jmp [WillKillJumpBack] } } } namespace Localization { static int8_t forcedUnits = -1; // 0 - metric, 1 - imperial bool IsMetric_LocaleBased() { if ( forcedUnits != -1 ) return forcedUnits == 0; unsigned int LCData; if ( GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_IMEASURE|LOCALE_RETURN_NUMBER, reinterpret_cast(&LCData), sizeof(LCData) / sizeof(TCHAR) ) != 0 ) { return LCData == 0; } // If fails, default to metric. Hopefully never fails though return true; } // Only used in "new binary" executables bool* germanGame; bool* frenchGame; bool* nastyGame; void SetUncensoredGame() { *germanGame = false; *frenchGame = false; *nastyGame = true; } void EmptyStub() { } } // Reintroduced corona rotation namespace CoronaRotationFix { static void (*orgRenderOneXLUSprite_Rotate_Aspect)(float, float, float, float, float, uint8_t, uint8_t, uint8_t, short, float, float, uint8_t); void RenderOneXLUSprite_Rotate_Aspect_SilentPatch( float a1, float a2, float a3, float a4, float a5, uint8_t a6, uint8_t a7, uint8_t a8, short a9, float recipz, float, uint8_t a12 ) { orgRenderOneXLUSprite_Rotate_Aspect( a1, a2, a3, a4, a5, a6, a7, a8, a9, recipz, 20.0f * recipz, a12 ); } }; // ============= Static shadow alpha fix ============= namespace StaticShadowAlphaFix { static void (*orgRenderStaticShadows)(); static void RenderStaticShadows_StateFix() { RwScopedRenderState state(rwRENDERSTATEALPHATESTFUNCTION); RwRenderStateSet( rwRENDERSTATEALPHATESTFUNCTION, (void*)rwALPHATESTFUNCTIONALWAYS ); orgRenderStaticShadows(); } static void (*orgRenderStoredShadows)(); static void RenderStoredShadows_StateFix() { RwScopedRenderState state(rwRENDERSTATEALPHATESTFUNCTION); RwRenderStateSet( rwRENDERSTATEALPHATESTFUNCTION, (void*)rwALPHATESTFUNCTIONALWAYS ); orgRenderStoredShadows(); } }; // ============= Disable building pipeline for skinned objects (like parachute) ============= namespace SkinBuildingPipelineFix { static RpAtomic* (*orgCustomBuildingDNPipeline_CustomPipeAtomicSetup)(RpAtomic* atomic); static RpAtomic* CustomBuildingDNPipeline_CustomPipeAtomicSetup_Skinned(RpAtomic* atomic) { RxPipeline* pipeline; RpAtomicGetPipeline(atomic, &pipeline); if (pipeline != nullptr && pipeline->pluginId == rwID_SKINPLUGIN) { return atomic; } return orgCustomBuildingDNPipeline_CustomPipeAtomicSetup(atomic); } }; // ============= Moonphases fix ============= namespace MoonphasesFix { // TODO: Reintroduce moon phases to Steam/RGL version // Call to RenderOneXLUSprite provides all required data except the moon mask and CClock::ms_nGameClockDays static void (*orgRenderOneXLUSprite)(float, float, float, float, float, uint8_t, uint8_t, uint8_t, int16_t, float, uint8_t, uint8_t, uint8_t); // By aap static void RenderOneXLUSprite_MoonPhases( float arg1, float arg2, float arg3, float arg4, float arg5, uint8_t red, uint8_t green, uint8_t blue, int16_t mult, float arg10, uint8_t alpha, uint8_t arg12, uint8_t arg13 ) { static RwTexture* gpMoonMask = [] () { // load from file RwTexture* mask = CPNGFile::ReadFromFile("lunar.png"); if (mask == nullptr) { const HMODULE module = reinterpret_cast(&__ImageBase); // Load from memory HRSRC resource = FindResource(module, MAKEINTRESOURCE(IDB_LUNAR64), RT_RCDATA); if (resource != nullptr) { HGLOBAL loadedResource = LoadResource(module, resource); if (loadedResource != nullptr) { mask = CPNGFile::ReadFromMemory(LockResource(loadedResource), SizeofResource(module, resource)); } } } return mask; } (); RwScopedRenderState alphaTest( rwRENDERSTATEALPHATESTFUNCTION ); if ( gpMoonMask != nullptr ) { RwRenderStateSet( rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpMoonMask) ); } RwRenderStateSet( rwRENDERSTATEALPHATESTFUNCTION, (void*)rwALPHATESTFUNCTIONALWAYS ); RwRenderStateSet( rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA ); RwRenderStateSet( rwRENDERSTATEDESTBLEND, (void*)rwBLENDZERO ); RwD3D9SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA ); orgRenderOneXLUSprite( arg1, arg2, arg3, arg4, arg5, red, green, blue, mult, arg10, alpha, arg12, arg13 ); RwD3D9SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED ); } } // ============= Disallow moving cam up/down with mouse when looking back/left/right in vehicle ============= namespace FollowCarMouseCamFix { static uint32_t& camLookDirection = **AddressByVersion( 0x525526 + 2, Memory::PatternAndOffset("83 3D ? ? ? ? 03 74 06", 2) ); static void* (*orgGetPad)(int); static bool* orgUseMouse3rdPerson; static bool useMouseAndLooksForwards; static void* getPadAndSetFlag( int padNum ) { useMouseAndLooksForwards = *orgUseMouse3rdPerson && camLookDirection == 3; return orgGetPad( padNum ); } }; // ======= Tie handlebar movement to the stering animations on Quadbike, fixes odd animation interpolations at low speeds ======= namespace QuadbikeHandlebarAnims { static const float POW_CONSTANT = 0.86f; static const float SLOW_SPEED_THRESHOLD = 0.02f; __declspec(naked) void ProcessRiderAnims_FixInterp() { _asm { xor edx, edx cmp [esp+130h-100h], edx // Reverse animation jne FuncSetToZero cmp [esp+130h-0F8h], edx // Drive-by animation jne FuncSetToZero fld dword ptr [esp+130h-108h] fabs fcomp [SLOW_SPEED_THRESHOLD] fnstsw ax test ah, 5 jp FuncReturn FuncSetToZero: mov [esp+130h-118h], edx FuncReturn: fld [POW_CONSTANT] retn } } static uint32_t savedClumpAssociation; __declspec(naked) void SaveDriveByAnim_Steam() { _asm { mov eax, [ebp-14h] mov [savedClumpAssociation], eax fdiv dword ptr [ecx+18h] fstp [ebp-14h] retn } } __declspec(naked) void ProcessRiderAnims_FixInterp_Steam() { _asm { xor edx, edx cmp [ebp-28h], edx // Reverse animation jne FuncSetToZero cmp [savedClumpAssociation], edx // Drive-by animation jne FuncSetToZero fld dword ptr [ebp-24h] fabs fcomp [SLOW_SPEED_THRESHOLD] fnstsw ax test ah, 5 jp FuncReturn FuncSetToZero: mov [ebp-14h], edx FuncReturn: fld [POW_CONSTANT] retn } } } // ======= Disable the radio station change anim on boats where CJ stands upright ======= namespace UprightBoatRadioStationChange { static void* (*orgAnimManagerBlendAnimation)(RpClump*, uint32_t, uint32_t, float); void* AnimManagerBlendAnimation_SkipIfBoatDrive(RpClump* clump, uint32_t assocGroupId, uint32_t animationId, float rate) { if (RpAnimBlendClumpGetAssociation(clump, 81) != nullptr) // ANIM_STD_BOAT_DRIVE { return nullptr; } return orgAnimManagerBlendAnimation(clump, assocGroupId, animationId, rate); } }; // ============= Fix a memory leak when taking photos ============= namespace CameraMemoryLeakFix { __declspec(naked) void psGrabScreen_UnlockAndReleaseSurface() { _asm { // Preserve the function result so we don't need two ASM hooks push eax mov eax, [esp+34h-2Ch] mov edx, [eax] push eax call dword ptr [edx+38h] // IDirect3DSurface9.UnlockRect mov eax, [esp+34h-2Ch] mov edx, [eax] push eax call dword ptr [edx+8h] // IDirect3DSurface9.Release pop eax pop ebp add esp, 2Ch retn } } __declspec(naked) void psGrabScreen_UnlockAndReleaseSurface_Steam() { _asm { // Preserve the function result so we don't need two ASM hooks push eax mov eax, [ebp-4] mov edx, [eax] push eax call dword ptr [edx+38h] // IDirect3DSurface9.UnlockRect mov eax, [ebp-4] mov edx, [eax] push eax call dword ptr [edx+8h] // IDirect3DSurface9.Release pop eax pop esi mov esp, ebp pop ebp retn } } } // ============= Fix crosshair issues when sniper rifle is quipped and a photo is taken by a gang member ============= namespace CameraCrosshairFix { CWeaponInfo* (*orgGetWeaponInfo)(eWeaponType, signed char); CWeaponInfo* GetWeaponInfo_OrCamera(eWeaponType weaponType, signed char type) { return orgGetWeaponInfo(bDrawCrossHair != 2 ? weaponType : WEAPONTYPE_CAMERA, type); } } // ============= Cancel the Drive By task of biker cops when losing the wanted level ============= namespace BikerCopsDriveByFix { static void (*orgJoinCarWithRoadSystem)(CVehicle* vehicle); void JoinCarWithRoadSystem_AbortDriveByTask(CVehicle* vehicle) { orgJoinCarWithRoadSystem(vehicle); CPed* driver = vehicle->GetDriver(); if (driver != nullptr) { CPedIntelligence* driverIntelligence = driver->GetPedIntelligencePtr(); if (driverIntelligence != nullptr) { // If the driver has a sequence, it's the fourth one CTask* primaryTask = driverIntelligence->m_taskManager.m_primaryTasks[3]; if (primaryTask != nullptr && primaryTask->GetTaskType() == 244) // TASK_COMPLEX_SEQUENCE { // If the sequence contains a TASK_SIMPLE_GANG_DRIVEBY, abort it CTaskComplexSequence* taskSequence = reinterpret_cast(primaryTask); if (taskSequence->Contains(1022)) { taskSequence->MakeAbortable(driver, 1, nullptr); } } } } } } // ============= Fix miscolored racing checkpoints if no other marker was drawn before them ============= namespace RacingCheckpointsRender { static RpClump* (*orgRpClumpRender)(RpClump* clump); static RpClump* RpClumpRender_SetLitFlag(RpClump* clump) { RpClumpForAllAtomics(clump, [](RpAtomic* atomic) { RpGeometry* geometry = RpAtomicGetGeometry(atomic); RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); return atomic; }); return orgRpClumpRender(clump); } } // ============= Correct an improperly decrypted CPlayerPedData::operator= that broke gang recruiting after activating replays ============= namespace PlayerPedDataAssignment { __declspec(naked) void AssignmentOp_Hoodlum() { _asm { xor edx, [ecx+34h] and edx, 1 xor [eax+34h], edx mov esi, [eax+34h] mov edx, [ecx+34h] xor edx, esi and edx, 2 xor edx, esi mov [eax+34h], edx mov esi, [ecx+34h] xor esi, edx and esi, 4 xor esi, edx mov [eax+34h], esi mov edx, [ecx+34h] xor edx, esi and edx, 8 xor edx, esi mov [eax+34h], edx mov esi, [ecx+34h] xor esi, edx and esi, 10h xor esi, edx mov [eax+34h], esi mov edx, [ecx+34h] xor edx, esi and edx, 20h xor edx, esi mov [eax+34h], edx mov esi, [ecx+34h] xor esi, edx and esi, 40h xor esi, edx mov [eax+34h], esi mov edx, [ecx+34h] xor edx, esi and edx, 80h xor edx, esi mov [eax+34h], edx mov esi, [ecx+34h] xor esi, edx and esi, 100h xor esi, edx mov [eax+34h], esi mov edx, [ecx+34h] retn } } __declspec(naked) void AssignmentOp_Compact() { _asm { call AssignmentOp_Hoodlum xor edx, esi and edx, 200h retn } } } // ============= Spawn lapdm1 (biker cop) correctly if the script requests one with PEDTYPE_COP ============= namespace GetCorrectPedModel_Lapdm1 { __declspec(naked) void BikerCop_Retail() { _asm { cmp dword ptr [esp+4], 6 jnz BikerCop_Return mov dword ptr [eax], 1 BikerCop_Return: retn 8 } } __declspec(naked) void BikerCop_Steam() { _asm { cmp dword ptr [ebp+8], 6 jnz BikerCop_Return mov dword ptr [eax], 1 BikerCop_Return: pop ebp retn 8 } } } // ============= Only allow impounding cars and bikes (and their subclasses), as impounding helicopters, planes, boats makes no sense ============= namespace RestrictImpoundVehicleTypes { template static bool (*orgIsThisVehicleInteresting)(CVehicle* vehicle); template static bool IsThisVehicleInteresting_AndCanBeImpounded(CVehicle* vehicle) { return vehicle->CanThisVehicleBeImpounded() && orgIsThisVehicleInteresting(vehicle); } HOOK_EACH_FUNC(ShouldImpound, orgIsThisVehicleInteresting, IsThisVehicleInteresting_AndCanBeImpounded) } // ============= Fix PlayerPed replay crashes ============= // 1. Crash when starting a mocap cutscene after playing a replay wearing different clothes to the ones CJ has currently // 2. Crash when playing back a replay with a different motion group anim (fat/muscular/normal) than the current one namespace ReplayPlayerPedCrashFixes { static void (*orgRestoreStuffFromMem)(); static void RestoreStuffFromMem_RebuildPlayer() { orgRestoreStuffFromMem(); CClothes::RebuildPlayer(FindPlayerPed(), false); } static void LoadAllMotionGroupAnims() { // FLA compatibility static const int32_t animGroupIDOffset = *AddressByVersion(0x5A814C + 2, { "81 C7 ? ? ? ? 57 E8 ? ? ? ? 83 C4 0C", 2 }); RequestModel(GetAnimationBlockIndex("fat") + animGroupIDOffset, 18); RequestModel(GetAnimationBlockIndex("muscular") + animGroupIDOffset, 18); LoadAllRequestedModels(true); } static void (*orgRebuildPlayer)(CPlayerPed*, bool); static void RebuildPlayer_LoadAllMotionGroupAnims(CPlayerPed* ped, bool bForReplay) { orgRebuildPlayer(ped, bForReplay); LoadAllMotionGroupAnims(); } } // ============= Fix planes spawning in places where they crash easily ============= // CPlane::FindPlaneCreationCoors passes the XY position to CCollision::CheckCameraCollisionBuildings, // while the function accepts the sector numbers namespace FindPlaneCreationCoorsFix { static std::pair GetSectorNumbersFromPosition(int posX, int posY) { // FLA compatibility // This function is used only for 1.0 as new binaries use a divisor and not a multiplier, // so it's fine to reference addresses directly! const float& SectorMultX = **(float**)(0x41ACED + 2); const float& SectorAddX = **(float**)(0x41ACF6 + 2); const float& SectorMultY = **(float**)(0x41AD3C + 2); const float& SectorAddY = **(float**)(0x41AD42 + 2); const int SectorX = static_cast(posX * SectorMultX + SectorAddX); const int SectorY = static_cast(posY * SectorMultY + SectorAddY); return {SectorX, SectorY}; } static std::pair GetSectorNumbersFromPosition_Steam(int posX, int posY) { // FLA compatibility (in case FLA ever supports the new binaries) static const struct { const double& SectorDivX; const double& SectorAddX; const double& SectorDivY; const double& SectorAddY; } SectorRefs = []() -> decltype(SectorRefs) { auto sectorDatas = hook::pattern("DC 35 ? ? ? ? DC 05 ? ? ? ? 83 EC 08 D9 5D EC").get_one(); const double* SectorDivX = *sectorDatas.get(2); const double* SectorAddX = *sectorDatas.get(6 + 2); const double* SectorDivY = *sectorDatas.get(0x2B + 2); const double* SectorAddY = *sectorDatas.get(0x40 + 2); return {*SectorDivX, *SectorAddX, *SectorDivY, *SectorAddY}; }(); const int SectorX = static_cast(posX / SectorRefs.SectorDivX + SectorRefs.SectorAddX); const int SectorY = static_cast(posY / SectorRefs.SectorDivY + SectorRefs.SectorAddY); return {SectorX, SectorY}; } static bool (*orgCheckCameraCollisionBuildings)(int sectorX, int sectorY, void*, void*, void*, void*); static bool CheckCameraCollisionBuildings_FixParams(int posX, int posY, void* a3, void* a4, void* a5, void* a6) { auto [sectorX, sectorY] = GetSectorNumbersFromPosition(posX, posY); return orgCheckCameraCollisionBuildings(sectorX, sectorY, a3, a4, a5, a6); } static bool CheckCameraCollisionBuildings_FixParams_Steam(int posX, int posY, void* a3, void* a4, void* a5, void* a6) { auto [sectorX, sectorY] = GetSectorNumbersFromPosition_Steam(posX, posY); return orgCheckCameraCollisionBuildings(sectorX, sectorY, a3, a4, a5, a6); } } // ============= Allow hovering on the Jetpack with Keyboard + Mouse controls ============= // Does not modify any other controls, only hovering namespace JetpackKeyboardControlsHover { static void (__thiscall *orgGetLookBehindForCar)(void*); static void* ProcessControlInput_DontHover; static void* ProcessControlInput_Hover; __declspec(naked) void ProcessControlInput_HoverWithKeyboard() { _asm { mov ecx, ebp call orgGetLookBehindForCar test al, al jnz Hovering mov ecx, ebp mov byte ptr [esi+0Dh], 0 jmp ProcessControlInput_DontHover Hovering: jmp ProcessControlInput_Hover } } __declspec(naked) void ProcessControlInput_HoverWithKeyboard_Steam() { _asm { mov ecx, ebx call orgGetLookBehindForCar test al, al jnz Hovering mov ecx, ebx mov byte ptr [edi+0Dh], 0 jmp ProcessControlInput_DontHover Hovering: jmp ProcessControlInput_Hover } } } // ============= During riots, don't target the player group during missions ============= // Fixes recruited homies panicking during Los Desperados and other riot-time missions namespace RiotDontTargetPlayerGroupDuringMissions { static void* SkipTargetting; static void* DontSkipTargetting; __declspec(naked) void CheckIfInPlayerGroupAndOnAMission() { _asm { cmp byte ptr [ebp+2D0h], 1 jne NotInGroup call IsPlayerOnAMission test al, al jz NotOnAMission jmp SkipTargetting NotOnAMission: cmp byte ptr [ebp+2D0h], 1 NotInGroup: jmp DontSkipTargetting } } __declspec(naked) void CheckIfInPlayerGroupAndOnAMission_Steam() { _asm { cmp byte ptr [ebx+2D0h], 1 jne NotInGroup call IsPlayerOnAMission test al, al jz NotOnAMission jmp SkipTargetting NotOnAMission: cmp byte ptr [ebx+2D0h], 1 NotInGroup: jmp DontSkipTargetting } } } // ============= Fixed vehicles exploding twice if the driver leaves the car while it's exploding ============= namespace RemoveDriverStatusFix { __declspec(naked) void RemoveDriver_SetStatus() { // if (m_nStatus != STATUS_WRECKED) // m_nStatus = STATUS_ABANDONED; _asm { mov bl, [edi+36h] mov al, bl and bl, 0F8h cmp bl, 28h je DontSetStatus and al, 7 or al, 20h DontSetStatus: retn } } static void (__thiscall *orgPrepareVehicleForPedExit)(CTaskComplexCarSlowBeDraggedOut* task, CPed* ped); static void __fastcall PrepareVehicleForPedExit_WreckedCheck(CTaskComplexCarSlowBeDraggedOut* task, void*, CPed* ped) { if (task->m_pVehicle->GetStatus() != STATUS_WRECKED) { orgPrepareVehicleForPedExit(task, ped); } } } // ============= Fixed shooting stars rendering black ============= namespace ShootingStarsFix { static void* (*orgRwIm3DTransform)(RwIm3DVertex* pVerts, RwUInt32 numVerts, RwMatrix* ltm, RwUInt32 flags); static void* RwIm3DTransform_UnsetTexture(RwIm3DVertex* pVerts, RwUInt32 numVerts, RwMatrix* ltm, RwUInt32 flags) { RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nullptr); return orgRwIm3DTransform(pVerts, numVerts, ltm, flags); } } // ============= Enable directional lights on flying car components ============= namespace LitFlyingComponents { static void (*orgWorldAdd)(CEntity*); static void WorldAdd_SetLightObjectFlag(CEntity* entity) { entity->bLightObject = true; orgWorldAdd(entity); } } // ============= Make script randomness 16-bit, like on PS2 ============= namespace Rand16bit { template static int (*orgRand)(); template static int rand16bit() { const int bottomBits = orgRand(); const int topBit = (orgRand() & 1) << 15; return bottomBits | topBit; } HOOK_EACH_FUNC(Rand, orgRand, rand16bit); } // ============= LS-RP Mode stuff ============= namespace LSRPMode { struct IPv4 { uint8_t ip[4]; uint16_t port; friend bool operator == ( const IPv4& left, const IPv4& right ) { return std::make_tuple( left.ip[0], left.ip[1], left.ip[2], left.ip[3] ) == std::make_tuple( right.ip[0], right.ip[1], right.ip[2], right.ip[3] ) && ( left.port == right.port || left.port == 0 || right.port == 0 ); } }; std::vector serversLSRPMode = { { 149, 56, 123, 148, 7777 }, // LS-RP { 198, 27, 95, 178, 7777 }, // AD:RP }; bool ModeForced = false; void DetectPlayingOnLSRP() { IPv4 myIP = {}; // Obtain IP and check if it's LS-RP int numArgs = 0; LPWSTR* cmdLine = CommandLineToArgvW( GetCommandLineW(), &numArgs ); if ( cmdLine != nullptr ) { for ( auto it = cmdLine + 1, end = cmdLine + numArgs; it != end; ++it ) { if ( _wcsicmp( *it, L"-h" ) == 0 ) { auto ipIt = std::next( it ); if ( ipIt != end ) { swscanf_s( *ipIt, L"%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, &myIP.ip[0], &myIP.ip[1], &myIP.ip[2], &myIP.ip[3] ); it = ipIt; } continue; } if ( _wcsicmp( *it, L"-p") == 0 ) { auto portIt = std::next( it ); if ( portIt != end ) { swscanf_s( *portIt, L"%" SCNu16 , &myIP.port ); it = portIt; } continue; } } LocalFree( cmdLine ); } ModeForced = std::find( serversLSRPMode.begin(), serversLSRPMode.end(), myIP ) != serversLSRPMode.end(); } void ReadServersList(const wchar_t* pPath) { constexpr size_t SCRATCH_PAD_SIZE = 32767; WideDelimStringReader reader( SCRATCH_PAD_SIZE ); GetPrivateProfileSectionW( L"LSRPModeServers", reader.GetBuffer(), reader.GetSize(), pPath ); while ( const wchar_t* str = reader.GetString() ) { int ip[4] = {}; int port = 0; // IP is mandatory, port is optional int argsRead = swscanf_s( str, L"%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3] ); if ( argsRead == 4 ) { swscanf_s( str, L"%*d.%*d.%*d.%*d:%d", &port ); IPv4 myIP = {}; bool validIP = true; for ( size_t i = 0; i < 4; i++ ) { if ( ip[i] >= 0 && ip[i] <= UINT8_MAX ) { myIP.ip[i] = static_cast(ip[i]); } else { validIP = false; break; } } if ( port >= 0 && port <= UINT16_MAX ) { myIP.port = static_cast(port); } else { validIP = false; } if ( validIP ) { serversLSRPMode.emplace_back( myIP ); } } } } } static bool IgnoresWeaponPedsForPCFix() { // TODO: Pre-emptively add INI option to save hassle in the future return LSRPMode::ModeForced; } #ifndef NDEBUG // ============= QPC spoof for verifying high timer issues ============= namespace FakeQPC { static int64_t AddedTime; static BOOL WINAPI FakeQueryPerformanceCounter(PLARGE_INTEGER lpPerformanceCount) { const BOOL result = ::QueryPerformanceCounter( lpPerformanceCount ); lpPerformanceCount->QuadPart += AddedTime; return result; } } #endif #if MEM_VALIDATORS #include // Validator for static allocations void PutStaticValidator( uintptr_t begin, uintptr_t end ) { uint8_t* a = (uint8_t*)begin; uint8_t* b = (uint8_t*)end; std::fill( a, b, uint8_t(0xCC) ); } void* malloc_validator(size_t size) { return _malloc_dbg( size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } void* realloc_validator(void* ptr, size_t size) { return _realloc_dbg( ptr, size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } void* calloc_validator(size_t count, size_t size) { return _calloc_dbg( count, size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } void free_validator(void* ptr) { _free_dbg(ptr, _NORMAL_BLOCK); } size_t _msize_validator(void* ptr) { return _msize_dbg(ptr, _NORMAL_BLOCK); } void* _new(size_t size) { return _malloc_dbg( size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } void _delete(void* ptr) { _free_dbg(ptr, _NORMAL_BLOCK); } class CDebugMemoryMgr { public: static void* Malloc(size_t size) { return _malloc_dbg( size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } static void Free(void* ptr) { _free_dbg(ptr, _NORMAL_BLOCK); } static void* Realloc(void* ptr, size_t size) { return _realloc_dbg( ptr, size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } static void* Calloc(size_t count, size_t size) { return _calloc_dbg( count, size, _NORMAL_BLOCK, "EXE", (uintptr_t)_ReturnAddress() ); } static void* MallocAlign(size_t size, size_t align) { return _aligned_malloc_dbg( size, align, "EXE", (uintptr_t)_ReturnAddress() ); } static void AlignedFree(void* ptr) { _aligned_free_dbg(ptr); } }; void InstallMemValidator() { using namespace Memory; // TEST: Validate memory InjectHook( 0x824257, malloc_validator, HookType::Jump ); InjectHook( 0x824269, realloc_validator, HookType::Jump ); InjectHook( 0x824416, calloc_validator, HookType::Jump ); InjectHook( 0x82413F, free_validator, HookType::Jump ); InjectHook( 0x828C4A, _msize_validator, HookType::Jump ); InjectHook( 0x82119A, _new, HookType::Jump ); InjectHook( 0x8214BD, _delete, HookType::Jump ); InjectHook( 0x72F420, &CDebugMemoryMgr::Malloc, HookType::Jump ); InjectHook( 0x72F430, &CDebugMemoryMgr::Free, HookType::Jump ); InjectHook( 0x72F440, &CDebugMemoryMgr::Realloc, HookType::Jump ); InjectHook( 0x72F460, &CDebugMemoryMgr::Calloc, HookType::Jump ); InjectHook( 0x72F4C0, &CDebugMemoryMgr::MallocAlign, HookType::Jump ); InjectHook( 0x72F4F0, &CDebugMemoryMgr::AlignedFree, HookType::Jump ); PutStaticValidator( 0xAAE950, 0xB4C310 ); // CStore PutStaticValidator( 0xA9AE00, 0xA9AE58 ); // fx_c } #endif // Hooks void __declspec(naked) LightMaterialsFix() { _asm { mov [esi], edi mov ebx, [ecx] lea esi, [edx+4] mov [ebx+4], esi mov edi, [esi] mov [ebx+8], edi add esi, 4 mov [ebx+12], esi mov edi, [esi] mov [ebx+16], edi add ebx, 20 mov [ecx], ebx retn } } void __declspec(naked) UserTracksFix() { _asm { push [esp+4] call SetVolume mov ecx, [pUserTracksStuff] mov byte ptr [ecx+0Dh], 1 call InitializeUtrax retn 4 } } void __declspec(naked) UserTracksFix_Steam() { _asm { push [esp+4] call SetVolume mov ecx, [pUserTracksStuff] mov byte ptr [ecx+5], 1 call InitializeUtrax retn 4 } } static void* PlaneAtomicRendererSetup_JumpBack = AddressByVersion(0x4C7986, 0x4C7A06, 0x4D2275); static void* RenderVehicleHiDetailAlphaCB_BigVehicle = AddressByVersion(0x734370, 0x734BA0, 0x76E400); static void* RenderVehicleHiDetailCB_BigVehicle = AddressByVersion(0x733420, 0x733C50, 0x76D6C0); void __declspec(naked) PlaneAtomicRendererSetup() { static const char aStaticProp[] = "static_prop"; static const char aMovingProp[] = "moving_prop"; _asm { mov eax, [esi+4] push eax call GetFrameNodeName //push eax mov [esp+8+8], eax push 11 push offset aStaticProp push eax call strncmp add esp, 10h test eax, eax jz PlaneAtomicRendererSetup_Alpha push 11 push offset aMovingProp push [esp+12+8] call strncmp add esp, 0Ch test eax, eax jnz PlaneAtomicRendererSetup_NoAlpha PlaneAtomicRendererSetup_Alpha: push [RenderVehicleHiDetailAlphaCB_BigVehicle] jmp PlaneAtomicRendererSetup_Return PlaneAtomicRendererSetup_NoAlpha: push [RenderVehicleHiDetailCB_BigVehicle] PlaneAtomicRendererSetup_Return: jmp PlaneAtomicRendererSetup_JumpBack } } static unsigned int nCachedCRC; static void* RenderVehicleHiDetailCB = AddressByVersion(0x733240, 0x733A70, 0x76D4C0); static void* RenderVehicleHiDetailAlphaCB = AddressByVersion(0x733F80, 0x7347B0, 0x76DFE0); static void* RenderHeliRotorAlphaCB = AddressByVersion(0x7340B0, 0x7348E0, 0x76E110); static void* RenderHeliTailRotorAlphaCB = AddressByVersion(0x734170, 0x7349A0, 0x76E1E0); static void* HunterTest_JumpBack = AddressByVersion(0x4C7914, 0x4C7994, 0x4D2203); // strcmp can't be invoked from inline assembly block? static int strcmp_wrap(const char *s1, const char *s2) { return strcmp( s1, s2 ); } void __declspec(naked) HunterTest() { static const char aDoorDummy[] = "door_lf_ok"; static const char aStaticRotor[] = "static_rotor"; static const char aStaticRotor2[] = "static_rotor2"; static const char aWindscreen[] = "windscreen"; _asm { setnz al movzx di, al push 10 push offset aWindscreen push ebp call strncmp add esp, 0Ch test eax, eax jz HunterTest_RegularAlpha push offset aStaticRotor2 push ebp call strcmp_wrap add esp, 8 test eax, eax jz HunterTest_StaticRotor2AlphaSet push offset aStaticRotor push ebp call strcmp_wrap add esp, 8 test eax, eax jz HunterTest_StaticRotorAlphaSet test di, di jnz HunterTest_DoorTest push [RenderVehicleHiDetailCB] jmp HunterTest_JumpBack HunterTest_DoorTest: cmp nCachedCRC, 0x45D0B41C jnz HunterTest_RegularAlpha push offset aDoorDummy push ebp call strcmp_wrap add esp, 8 test eax, eax jnz HunterTest_RegularAlpha push RenderVehicleHiDetailAlphaCB_HunterDoor jmp HunterTest_JumpBack HunterTest_RegularAlpha: push [RenderVehicleHiDetailAlphaCB] jmp HunterTest_JumpBack HunterTest_StaticRotorAlphaSet: push [RenderHeliRotorAlphaCB] jmp HunterTest_JumpBack HunterTest_StaticRotor2AlphaSet: push [RenderHeliTailRotorAlphaCB] jmp HunterTest_JumpBack } } static void* CacheCRC32_JumpBack = AddressByVersion(0x4C7B10, 0x4C7B90, 0x4D2400); void __declspec(naked) CacheCRC32() { _asm { mov eax, [ecx+4] mov nCachedCRC, eax jmp CacheCRC32_JumpBack } } static void* const TrailerDoubleRWheelsFix_ReturnFalse = AddressByVersion(0x4C9333, 0x4C9533, 0x4D3C59); static void* const TrailerDoubleRWheelsFix_ReturnTrue = AddressByVersion(0x4C9235, 0x4C9435, 0x4D3B59); void __declspec(naked) TrailerDoubleRWheelsFix() { _asm { cmp [edi]CVehicleModelInfo.m_dwType, VEHICLE_TRAILER je TrailerDoubleRWheelsFix_DoWheels cmp eax, 2 je TrailerDoubleRWheelsFix_False cmp eax, 5 je TrailerDoubleRWheelsFix_False TrailerDoubleRWheelsFix_DoWheels: jmp TrailerDoubleRWheelsFix_ReturnTrue TrailerDoubleRWheelsFix_False: jmp TrailerDoubleRWheelsFix_ReturnFalse } } void __declspec(naked) TrailerDoubleRWheelsFix2() { _asm { add esp, 18h mov eax, [ebx] mov eax, [esi+eax+4] jmp TrailerDoubleRWheelsFix } } void __declspec(naked) TrailerDoubleRWheelsFix_Steam() { _asm { cmp [esi]CVehicleModelInfo.m_dwType, VEHICLE_TRAILER je TrailerDoubleRWheelsFix_DoWheels cmp eax, 2 je TrailerDoubleRWheelsFix_False cmp eax, 5 je TrailerDoubleRWheelsFix_False TrailerDoubleRWheelsFix_DoWheels: jmp TrailerDoubleRWheelsFix_ReturnTrue TrailerDoubleRWheelsFix_False: jmp TrailerDoubleRWheelsFix_ReturnFalse } } void __declspec(naked) TrailerDoubleRWheelsFix2_Steam() { _asm { add esp, 18h mov eax, [ebp] mov eax, [ebx+eax+4] jmp TrailerDoubleRWheelsFix_Steam } } static void* LoadFLAC_JumpBack = AddressByVersion(0x4F3743, Memory::GetVersion().version == 1 ? (*(BYTE*)0x4F3A50 == 0x6A ? 0x4F3BA3 : 0x5B6B81) : 0, 0x4FFC3F); void __declspec(naked) LoadFLAC() { _asm { jz LoadFLAC_WindowsMedia sub ebp, 2 jnz LoadFLAC_Return push esi call DecoderCtor jmp LoadFLAC_Success LoadFLAC_WindowsMedia: jmp LoadFLAC_JumpBack LoadFLAC_Success: test eax, eax mov [esp+20h+4], eax jnz LoadFLAC_Return_NoDelete LoadFLAC_Return: mov ecx, esi call CAEDataStreamOld::~CAEDataStreamOld push esi call GTAdelete add esp, 4 LoadFLAC_Return_NoDelete: mov eax, [esp+20h+4] mov ecx, [esp+20h-0Ch] pop esi pop ebp pop edi pop ebx mov fs:0, ecx add esp, 10h retn 4 } } // 1.01 securom butchered this func, might not be reliable void __declspec(naked) LoadFLAC_11() { _asm { jz LoadFLAC_WindowsMedia sub ebp, 2 jnz LoadFLAC_Return push esi call DecoderCtor jmp LoadFLAC_Success LoadFLAC_WindowsMedia: jmp LoadFLAC_JumpBack LoadFLAC_Success: test eax, eax mov [esp+20h+4], eax jnz LoadFLAC_Return_NoDelete LoadFLAC_Return: mov ecx, esi call CAEDataStreamNew::~CAEDataStreamNew push esi call GTAdelete add esp, 4 LoadFLAC_Return_NoDelete: mov eax, [esp+20h+4] mov ecx, [esp+20h-0Ch] pop esi pop ebp pop edi pop ebx mov fs:0, ecx add esp, 10h retn 4 } } void __declspec(naked) LoadFLAC_Steam() { _asm { jz LoadFLAC_WindowsMedia sub ebp, 2 jnz LoadFLAC_Return push esi call DecoderCtor jmp LoadFLAC_Success LoadFLAC_WindowsMedia: jmp LoadFLAC_JumpBack LoadFLAC_Success: test eax, eax mov [esp+20h+4], eax jnz LoadFLAC_Return_NoDelete LoadFLAC_Return: mov ecx, esi call CAEDataStreamOld::~CAEDataStreamOld push esi call GTAdelete add esp, 4 LoadFLAC_Return_NoDelete: mov eax, [esp+20h+4] mov ecx, [esp+20h-0Ch] pop ebx pop esi pop ebp pop edi mov fs:0, ecx add esp, 10h retn 4 } } void __declspec(naked) FLACInit() { _asm { mov byte ptr [ecx+0Dh], 1 jmp InitializeUtrax } } void __declspec(naked) FLACInit_Steam() { _asm { mov byte ptr [ecx+5], 1 jmp InitializeUtrax } } // 1.0 ONLY BEGINS HERE static bool bDarkVehicleThing; static RpLight** pDirect; static void* DarkVehiclesFix1_JumpBack; void __declspec(naked) DarkVehiclesFix1() { _asm { shr eax, 0Eh test al, 1 jz DarkVehiclesFix1_DontAppply mov ecx, [pDirect] mov ecx, [ecx] mov al, [ecx+2] test al, 1 jnz DarkVehiclesFix1_DontAppply mov bDarkVehicleThing, 1 jmp DarkVehiclesFix1_Return DarkVehiclesFix1_DontAppply: mov bDarkVehicleThing, 0 DarkVehiclesFix1_Return: jmp DarkVehiclesFix1_JumpBack } } void __declspec(naked) DarkVehiclesFix2() { _asm { jz DarkVehiclesFix2_MakeItDark mov al, bDarkVehicleThing test al, al jnz DarkVehiclesFix2_MakeItDark mov eax, 5D9A7Ah jmp eax DarkVehiclesFix2_MakeItDark: mov eax, 5D9B09h jmp eax } } void __declspec(naked) DarkVehiclesFix3() { _asm { jz DarkVehiclesFix3_MakeItDark mov al, bDarkVehicleThing test al, al jnz DarkVehiclesFix3_MakeItDark mov eax, 5D9B4Ah jmp eax DarkVehiclesFix3_MakeItDark: mov eax, 5D9CACh jmp eax } } void __declspec(naked) DarkVehiclesFix4() { _asm { jz DarkVehiclesFix4_MakeItDark mov al, bDarkVehicleThing test al, al jnz DarkVehiclesFix4_MakeItDark mov eax, 5D9CB8h jmp eax DarkVehiclesFix4_MakeItDark: mov eax, 5D9E0Dh jmp eax } } // 1.0 ONLY ENDS HERE __declspec(safebuffers) static int _Timers_ftol_internal( double timer, double& remainder ) { double integral; remainder = modf( timer + remainder, &integral ); return int(integral); } int __stdcall Timers_ftol_PauseMode( double timer ) { static double TimersRemainder = 0.0; return _Timers_ftol_internal( timer, TimersRemainder ); } int __stdcall Timers_ftol_NonClipped( double timer ) { static double TimersRemainder = 0.0; return _Timers_ftol_internal( timer, TimersRemainder ); } int __stdcall Timers_ftol( double timer ) { static double TimersRemainder = 0.0; return _Timers_ftol_internal( timer, TimersRemainder ); } int __stdcall Timers_ftol_SCMdelta( double timer ) { static double TimersRemainder = 0.0; return _Timers_ftol_internal( timer, TimersRemainder ); } void __declspec(naked) asmTimers_ftol_PauseMode() { _asm { sub esp, 8 fstp qword ptr [esp] call Timers_ftol_PauseMode retn } } void __declspec(naked) asmTimers_ftol_NonClipped() { _asm { sub esp, 8 fstp qword ptr [esp] call Timers_ftol_NonClipped retn } } void __declspec(naked) asmTimers_ftol() { _asm { sub esp, 8 fstp qword ptr [esp] call Timers_ftol retn } } void __declspec(naked) asmTimers_SCMdelta() { _asm { sub esp, 8 fstp qword ptr [esp] call Timers_ftol_SCMdelta retn } } void _declspec(naked) FixedCarDamage() { _asm { fldz fcomp [esp+20h+10h] fnstsw ax test ah, 5 jp FixedCarDamage_Negative movzx eax, byte ptr [edi+21h] retn FixedCarDamage_Negative: movzx eax, byte ptr [edi+24h] retn } } void _declspec(naked) FixedCarDamage_Steam() { _asm { fldz fcomp [esp+20h+10h] fnstsw ax test ah, 5 jp FixedCarDamage_Negative movzx eax, byte ptr [edi+21h] test ecx, ecx retn FixedCarDamage_Negative: movzx eax, byte ptr [edi+24h] test ecx, ecx retn } } void _declspec(naked) FixedCarDamage_Newsteam() { _asm { mov edi, [ebp+10h] fldz fcomp [ebp+14h] fnstsw ax test ah, 5 jp FixedCarDamage_Negative movzx eax, byte ptr [edi+21h] retn FixedCarDamage_Negative: movzx eax, byte ptr [edi+24h] retn } } void __declspec(naked) CdStreamThreadHighSize() { _asm { xor edx, edx shld edx, ecx, 11 shl ecx, 11 mov [esi]CdStream.overlapped.Offset, ecx // OVERLAPPED.Offset mov [esi]CdStream.overlapped.OffsetHigh, edx // OVERLAPPED.OffsetHigh mov edx, [esi]CdStream.nSectorsToRead retn } } void __declspec(naked) WeaponRangeMult_VehicleCheck() { _asm { mov eax, [edx]CPed.pedFlags test ah, 1 jz WeaponRangeMult_VehicleCheck_NotInCar mov eax, [edx]CPed.pVehicle retn WeaponRangeMult_VehicleCheck_NotInCar: xor eax, eax retn } } static const float fSteamSubtitleSizeX = 0.45f; static const float fSteamSubtitleSizeY = 0.9f; static const float fSteamRadioNamePosY = 33.0f; static const float fSteamRadioNameSizeX = 0.4f; static const float fSteamRadioNameSizeY = 0.6f; static float* orgSubtitleSizeX; static float* orgSubtitleSizeY; static float* orgRadioNamePosY; static float* orgRadioNameSizeX; static float* orgRadioNameSizeY; static void ToggleSteamTexts( bool enable ) { using namespace Memory::VP; if ( enable ) { Patch(0x58C387, &fSteamSubtitleSizeY); Patch(0x58C40F, &fSteamSubtitleSizeY); Patch(0x58C4CE, &fSteamSubtitleSizeY); Patch(0x58C39D, &fSteamSubtitleSizeX); Patch(0x58C425, &fSteamSubtitleSizeX); Patch(0x58C4E4, &fSteamSubtitleSizeX); Patch(0x4E9FD8, &fSteamRadioNamePosY); Patch(0x4E9F22, &fSteamRadioNameSizeY); Patch(0x4E9F38, &fSteamRadioNameSizeX); } else { assert( orgSubtitleSizeY != nullptr && orgSubtitleSizeX != nullptr && orgRadioNamePosY != nullptr && orgRadioNameSizeY != nullptr && orgRadioNameSizeX != nullptr ); Patch(0x58C387, orgSubtitleSizeY); Patch(0x58C40F, orgSubtitleSizeY); Patch(0x58C4CE, orgSubtitleSizeY); Patch(0x58C39D, orgSubtitleSizeX); Patch(0x58C425, orgSubtitleSizeX); Patch(0x58C4E4, orgSubtitleSizeX); Patch(0x4E9FD8, orgRadioNamePosY); Patch(0x4E9F22, orgRadioNameSizeY); Patch(0x4E9F38, orgRadioNameSizeX); } } static const double dRetailSubtitleSizeX = 0.58; static const double dRetailSubtitleSizeY = 1.2; static const double dRetailSubtitleSizeY2 = 1.22; static const double dRetailRadioNamePosY = 22.0; static const double dRetailRadioNameSizeX = 0.6; static const double dRetailRadioNameSizeY = 0.9; #pragma comment(lib, "shlwapi.lib") BOOL InjectDelayedPatches_10() { if ( !IsAlreadyRunning() ) { using namespace Memory; const HINSTANCE hInstance = GetModuleHandle( nullptr ); std::unique_ptr Protect = ScopedUnprotect::UnprotectSectionOrFullModule( hInstance, ".text" ); ScopedUnprotect::Section Protect2( hInstance, ".rdata" ); // Obtain a path to the ASI wchar_t wcModulePath[MAX_PATH]; GetModuleFileNameW(reinterpret_cast(&__ImageBase), wcModulePath, _countof(wcModulePath) - 3); // Minus max required space for extension PathRenameExtensionW(wcModulePath, L".ini"); const ModuleList moduleList; const bool bHasImVehFt = moduleList.Get(L"ImVehFt") != nullptr; const bool bSAMP = moduleList.Get(L"samp") != nullptr; const bool bSARender = moduleList.Get(L"SARender") != nullptr; const bool bOutfit = moduleList.Get(L"outfit") != nullptr; if ( bSAMP ) { LSRPMode::ReadServersList(wcModulePath); LSRPMode::DetectPlayingOnLSRP(); } const HMODULE skygfxModule = moduleList.Get( L"skygfx" ); const HMODULE modloaderModule = moduleList.Get( L"modloader" ); ReadRotorFixExceptions(wcModulePath); ReadLightbeamFixExceptions(wcModulePath); const bool bHookDoubleRwheels = ReadDoubleRearWheels(wcModulePath); const bool bHasDebugMenu = DebugMenuLoad(); #ifdef _DEBUG if ( bHasDebugMenu ) { DebugMenuAddVar( "SilentPatch", "Force LS-RP Mode", &LSRPMode::ModeForced, nullptr ); } #endif if ( GetPrivateProfileIntW(L"SilentPatch", L"SunSizeHack", -1, wcModulePath) == 1 ) { // PS2 sun - more static const float fSunMult = (1050.0f * 0.95f) / 1500.0f; Patch(0x6FC5B0, &fSunMult); if ( !bSAMP ) { ReadCall( 0x53C136, DoSunAndMoon ); InjectHook(0x53C136, SunAndMoonFarClip); Patch(0x6FC5AA, &fSunFarClip); } } if ( !bSARender ) { // Twopass rendering (experimental) Patch(0x7341D9, MovingPropellerRender); Patch(0x734127, MovingPropellerRender); Patch(0x73445E, RenderBigVehicleActomic); // Weapons rendering if ( !bOutfit ) { if ( bSAMP ) { CPed::orgGetWeaponSkillForRenderWeaponPedsForPC = &CPed::GetWeaponSkillForRenderWeaponPedsForPC_SAMP; } InjectHook(0x5E7859, RenderWeapon); InjectHook(0x732F30, RenderWeaponPedsForPC, HookType::Jump); } } if ( GetPrivateProfileIntW(L"SilentPatch", L"EnableScriptFixes", -1, wcModulePath) == 1 ) { using namespace ScriptFixes; // Gym glitch fix Patch(0x470B03, 0xCD8B); Patch(0x470B0A, 0x8B04508B); Patch(0x470B0E, 0x9000); Nop(0x470B10, 1); InjectHook(0x470B05, &CRunningScript::GetDay_GymGlitch, HookType::Call); // Basketball fix InterceptCall( 0x5D18F0, TheScriptsLoad, TheScriptsLoad_BasketballFix ); std::array wipeLocalVars = { 0x489A70, 0x4899F0 }; HookEach_SCMFixes(wipeLocalVars, InterceptCall); } if ( GetPrivateProfileIntW(L"SilentPatch", L"SkipIntroSplashes", -1, wcModulePath) == 1 ) { // Skip the damn intro splash Patch(AddressByRegion_10(0x748AA8), 0x3DEB); } { static bool bSmallSteamTexts = false; if ( bHasDebugMenu ) { orgSubtitleSizeX = *(float**)0x58C39D; orgSubtitleSizeY = *(float**)0x58C387; orgRadioNamePosY = *(float**)0x4E9FD8; orgRadioNameSizeY = *(float**)0x4E9F22; orgRadioNameSizeX = *(float**)0x4E9F38; DebugMenuAddVar( "SilentPatch", "Small Steam texts", &bSmallSteamTexts, []() { ToggleSteamTexts( bSmallSteamTexts ); } ); } if ( GetPrivateProfileIntW(L"SilentPatch", L"SmallSteamTexts", -1, wcModulePath) == 1 ) { // We're on 1.0 - make texts smaller ToggleSteamTexts( true ); bSmallSteamTexts = true; } } if ( const int INIoption = GetPrivateProfileIntW(L"SilentPatch", L"ColouredZoneNames", -1, wcModulePath); INIoption != -1 ) { // Coloured zone names bColouredZoneNames = INIoption != 0; Patch(0x58ADBE, 0x0E75); Patch(0x58ADC5, 0x0775); InjectHook(0x58ADE4, &BlendGangColour_Dynamic); if ( bHasDebugMenu ) { DebugMenuAddVar( "SilentPatch", "Coloured zone names", &bColouredZoneNames, nullptr ); } } // ImVehFt conflicts if ( !bHasImVehFt ) { // Lights InjectHook(0x4C830C, LightMaterialsFix, HookType::Call); // Flying components InjectHook(0x59F180, &CObject::Render_Stub, HookType::Jump); // Cars getting dirty // Only 1.0 and Steam InjectHook( 0x5D5DB0, RemapDirt, HookType::Jump ); InjectHook(0x4C9648, &CVehicleModelInfo::FindEditableMaterialList, HookType::Call); Patch(0x4C964D, 0x0FEBCE8B); } if ( !bHasImVehFt && !bSAMP ) { // Properly random numberplates DWORD* pVMT = *(DWORD**)0x4C75FC; Patch(&pVMT[7], &CVehicleModelInfo::Shutdown_Stub); InjectHook(0x4C9660, &CVehicleModelInfo::SetCarCustomPlate); InjectHook(0x6D6A58, &CVehicle::CustomCarPlate_TextureCreate); InjectHook(0x6D651C, &CVehicle::CustomCarPlate_BeforeRenderingStart); InjectHook(0x6FDFE0, CCustomCarPlateMgr::SetupClumpAfterVehicleUpgrade, HookType::Jump); InjectHook(0x6D0E53, &CVehicle::CustomCarPlate_AfterRenderingStop); Nop(0x6D6517, 2); Nop(0x6D0E43, 2); } // SSE conflicts if ( moduleList.Get(L"shadows") == nullptr ) { Patch(0x70665C, 0x52909090); InjectHook(0x706662, &CShadowCamera::Update); // Disable alpha test for stored shadows { using namespace StaticShadowAlphaFix; ReadCall( 0x53E0C8, orgRenderStoredShadows ); InjectHook( 0x53E0C8, RenderStoredShadows_StateFix ); } } // Bigger streamed entity linked lists // Increase only if they're not increased already if ( *(DWORD*)0x5B8E55 == 12000 ) { Patch(0x5B8E55, 15000); Patch(0x5B8EB0, 15000); } // Read CCustomCarPlateMgr::GeneratePlateText from here // to work fine with Deji's Custom Plate Format ReadCall( 0x4C9484, CCustomCarPlateMgr::GeneratePlateText ); if ( bHookDoubleRwheels ) { // Double rwheels whitelist // push ecx // push edi // call CheckDoubleRWheelsWhitelist // test al, al Patch( 0x4C9239, 0x5751 ); InjectHook( 0x4C9239+2, CheckDoubleRWheelsList, HookType::Call ); Patch( 0x4C9239+7, 0xC084 ); Nop( 0x4C9239+9, 1 ); } // Adblocker #if DISABLE_FLA_DONATION_WINDOW if ( moduleList.Get(L"$fastman92limitAdjuster") != nullptr ) { if ( *(DWORD*)0x748736 != 0xE8186A53 ) { Patch(0x748736, 0xE8186A53); InjectHook(AddressByRegion_10(0x748739), 0x619B60); } } #endif if ( *(DWORD*)0x4065BB == 0x3B0BE1C1 ) { // Handle IMGs bigger than 4GB Nop( 0x4065BB, 3 ); Nop( 0x4065C2, 1 ); InjectHook( 0x4065C2+1, CdStreamThreadHighSize, HookType::Call ); Patch( 0x406620+2, &pCdStreamSetFilePointer ); } // Fix directional light position if ( const int INIoption = GetPrivateProfileIntW(L"SilentPatch", L"DirectionalFromSun", -1, wcModulePath); INIoption != -1 ) { bUseAaronSun = INIoption != 0; ReadCall( 0x53E997, orgSetLightsWithTimeOfDayColour ); InjectHook( 0x53E997, SetLightsWithTimeOfDayColour_SilentPatch ); Patch( 0x735618 + 2, &curVecToSun.x ); Patch( 0x73561E + 2, &curVecToSun.y ); Patch( 0x735624 + 1, &curVecToSun.z ); if ( bHasDebugMenu ) { DebugMenuAddVar( "SilentPatch", "Directional from sun", &bUseAaronSun, nullptr ); #ifndef NDEBUG // Switch for fixed PC vehicle lighting static bool bFixedPCVehLight = true; DebugMenuAddVar( "SilentPatch", "Fixed PC vehicle light", &bFixedPCVehLight, []() { if ( bFixedPCVehLight ) { Memory::VP::Patch(0x5D88D1 + 6, 0); Memory::VP::Patch(0x5D88DB + 6, 0); Memory::VP::Patch(0x5D88E5 + 6, 0); Memory::VP::Patch(0x5D88F9 + 6, 0); Memory::VP::Patch(0x5D8903 + 6, 0); Memory::VP::Patch(0x5D890D + 6, 0); } else { Memory::VP::Patch(0x5D88D1 + 6, 0.25f); Memory::VP::Patch(0x5D88DB + 6, 0.25f); Memory::VP::Patch(0x5D88E5 + 6, 0.25f); Memory::VP::Patch(0x5D88F9 + 6, 0.75f); Memory::VP::Patch(0x5D8903 + 6, 0.75f); Memory::VP::Patch(0x5D890D + 6, 0.75f); } } ); #endif } } // Minimal HUD if ( const int INIoption = GetPrivateProfileIntW(L"SilentPatch", L"MinimalHud", -1, wcModulePath); INIoption != -1 ) { using namespace MinimalHud; // Fix original bugs Patch( 0x58950E, { 0x90, 0xFF, 0x74, 0x24, 0x1C } ); InjectHook( 0x58951D, &SetRGBA_FloatAlpha ); Patch( 0x58D88A, { 0x90, 0xFF, 0x74, 0x24, 0x20 + 0x10 } ); ReadCall( 0x58D8FD, orgRenderOneXLUSprite ); InjectHook( 0x58D8FD, &RenderXLUSprite_FloatAlpha ); // Re-enable if ( INIoption == 1 ) { Patch( 0x588905 + 1, 0 ); } if ( bHasDebugMenu ) { static bool bMinimalHUDEnabled = INIoption == 1; DebugMenuAddVar( "SilentPatch", "Minimal HUD", &bMinimalHUDEnabled, []() { if ( bMinimalHUDEnabled ) { Memory::VP::Patch( 0x588905 + 1, 0 ); } else { Memory::VP::Patch( 0x588905 + 1, 5 ); } // Call CHud::ReInitialise auto ReInitialise = (void(*)())0x588880; ReInitialise(); } ); } } // True invicibility - not being hurt by Police Maverick bullets anymore if ( const int INIoption = GetPrivateProfileIntW(L"SilentPatch", L"TrueInvicibility", -1, wcModulePath); INIoption != -1 && !bSAMP ) { using namespace TrueInvicibility; isEnabled = INIoption != 0; WillKillJumpBack = 0x4B3238; InjectHook( 0x4B322E, ComputeWillKillPedHook, HookType::Jump ); if ( bHasDebugMenu ) { DebugMenuAddVar( "SilentPatch", "True invicibility", &isEnabled, nullptr ); } } // Moonphases // Not taking effect with new skygfx since aap has it too now if ( !bSAMP && !ModCompat::SkygfxPatchesMoonphases( skygfxModule ) ) { using namespace MoonphasesFix; ReadCall( 0x713B74, orgRenderOneXLUSprite ); InjectHook( 0x713C4C, RenderOneXLUSprite_MoonPhases ); } FLAUtils::Init( moduleList ); // Race condition in CdStream fixed // Not taking effect with modloader if ( !ModCompat::ModloaderCdStreamRaceConditionAware( modloaderModule ) ) { // Don't patch if old FLA and enhanced IMGs are in place // For new FLA, we patch everything except CdStreamThread and then interop with FLA const bool flaBugAware = FLAUtils::CdStreamRaceConditionAware(); const bool usesEnhancedImages = FLAUtils::UsesEnhancedIMGs(); if ( !usesEnhancedImages || flaBugAware ) { ReadCall( 0x406C78, CdStreamSync::orgCdStreamInitThread ); InjectHook( 0x406C78, CdStreamSync::CdStreamInitThread ); { const uintptr_t address = ModCompat::Utils::GetFunctionAddrIfRerouted(0x406460); const uintptr_t waitForSingleObject = address + 0x1D; const uint8_t orgCode[] = { 0x8B, 0x46, 0x04, 0x85, 0xC0, 0x74, 0x10, 0xC6, 0x46, 0x0D, 0x01 }; if ( memcmp( orgCode, (void*)waitForSingleObject, sizeof(orgCode) ) == 0 ) { VP::Patch( waitForSingleObject, { 0x56, 0xFF, 0x15 } ); VP::Patch( waitForSingleObject + 3, &CdStreamSync::CdStreamSyncOnObject ); VP::Patch( waitForSingleObject + 3 + 4, { 0x5E, 0xC3 } ); { const uint8_t orgCode1[] = { 0xFF, 0x15 }; const uint8_t orgCode2[] = { 0x48, 0xF7, 0xD8 }; const uintptr_t getOverlappedResult = address + 0x5F; if ( memcmp( orgCode1, (void*)getOverlappedResult, sizeof(orgCode1) ) == 0 && memcmp( orgCode2, (void*)(getOverlappedResult + 6), sizeof(orgCode2) ) == 0 ) { VP::Patch( getOverlappedResult + 2, &CdStreamSync::pGetOverlappedResult ); VP::Patch( getOverlappedResult + 6, { 0x5E, 0xC3 } ); // pop esi / retn } } } } if ( !usesEnhancedImages ) { Patch( 0x406669, { 0x56, 0xFF, 0x15 } ); Patch( 0x406669 + 3, &CdStreamSync::CdStreamThreadOnObject ); Patch( 0x406669 + 3 + 4, { 0xEB, 0x0F } ); } Patch( 0x406910, { 0xFF, 0x15 } ); Patch( 0x406910 + 2, &CdStreamSync::CdStreamInitializeSyncObject ); Nop( 0x406910 + 6, 4 ); Nop( 0x406910 + 0x16, 2 ); Patch( 0x4063B5, { 0x56, 0x50 } ); InjectHook( 0x4063B5 + 2, CdStreamSync::CdStreamShutdownSyncObject_Stub, HookType::Call ); } } // For imfast compatibility if ( MemEquals( 0x590ADE, { 0xFF, 0x05 } ) ) { // Modulo over CLoadingScreen::m_currDisplayedSplash Nop( 0x590ADE, 1 ); InjectHook( 0x590ADE + 1, DoPCScreenChange_Mod, HookType::Call ); Patch( 0x590042 + 2, &currDisplayedSplash_ForLastSplash ); } // Lightbeam fix debug menu if ( bHasDebugMenu ) { static const char * const str[] = { "Off", "Default", "On" }; DebugMenuEntry *e = DebugMenuAddVar( "SilentPatch", "Rotors fix", &CVehicle::ms_rotorFixOverride, nullptr, 1, -1, 1, str); DebugMenuEntrySetWrap(e, true); if ( LightbeamFix::hookedSuccessfully ) { e = DebugMenuAddVar( "SilentPatch", "Lightbeam fix", &CVehicle::ms_lightbeamFixOverride, nullptr, 1, -1, 1, str); DebugMenuEntrySetWrap(e, true); } } // Locale based metric/imperial system INI/debug menu { using namespace Localization; forcedUnits = static_cast(GetPrivateProfileIntW(L"SilentPatch", L"Units", -1, wcModulePath)); if ( bHasDebugMenu ) { static const char * const str[] = { "Default", "Metric", "Imperial" }; DebugMenuEntry *e = DebugMenuAddVar( "SilentPatch", "Forced units", &forcedUnits, nullptr, 1, -1, 1, str ); DebugMenuEntrySetWrap(e, true); } } #ifndef NDEBUG if ( const int QPCDays = GetPrivateProfileIntW(L"Debug", L"AddDaysToQPC", 0, wcModulePath); QPCDays != 0 ) { using namespace FakeQPC; LARGE_INTEGER Freq; QueryPerformanceFrequency( &Freq ); AddedTime = Freq.QuadPart * QPCDays * 60 * 24; Patch( 0x8580C8, &FakeQueryPerformanceCounter ); } #endif return FALSE; } return TRUE; } BOOL InjectDelayedPatches_11() { #ifdef NDEBUG MessageBoxW( nullptr, L"You're using a 1.01 executable which is no longer supported by SilentPatch!\n\n" L"Since this EXE is used by only a few people, I recommend downgrading back to 1.0 - you gain full compatibility with mods " L"and any relevant fixes 1.01 brings are backported to 1.0 by SilentPatch anyway.\n\n" L"To downgrade to 1.0, find a 1.0 EXE online and replace your current game executable with it.", L"SilentPatch", MB_OK | MB_ICONWARNING ); #endif if ( !IsAlreadyRunning() ) { using namespace Memory; const HINSTANCE hInstance = GetModuleHandle( nullptr ); std::unique_ptr Protect = ScopedUnprotect::UnprotectSectionOrFullModule( hInstance, ".text" ); ScopedUnprotect::Section Protect2( hInstance, ".rdata" ); // Obtain a path to the ASI wchar_t wcModulePath[MAX_PATH]; GetModuleFileNameW(reinterpret_cast(&__ImageBase), wcModulePath, _countof(wcModulePath) - 3); // Minus max required space for extension PathRenameExtensionW(wcModulePath, L".ini"); const ModuleList moduleList; bool bHasImVehFt = moduleList.Get(L"ImVehFt") != nullptr; bool bSAMP = moduleList.Get(L"samp") != nullptr; bool bSARender = moduleList.Get(L"SARender") != nullptr; const bool bOutfit = moduleList.Get(L"outfit") != nullptr; if ( bSAMP ) { LSRPMode::ReadServersList(wcModulePath); LSRPMode::DetectPlayingOnLSRP(); } ReadRotorFixExceptions(wcModulePath); if ( GetPrivateProfileIntW(L"SilentPatch", L"SunSizeHack", -1, wcModulePath) == 1 ) { // PS2 sun - more static const float fSunMult = (1050.0f * 0.95f) / 1500.0f; Patch(0x6FCDE0, &fSunMult); if ( !bSAMP ) { ReadCall( 0x53C5D6, DoSunAndMoon ); InjectHook(0x53C5D6, SunAndMoonFarClip); Patch(0x6FCDDA, &fSunFarClip); } } if ( !bSARender ) { // Twopass rendering (experimental) Patch(0x734A09, MovingPropellerRender); Patch(0x734957, MovingPropellerRender); Patch(0x734C8E, RenderBigVehicleActomic); // Weapons rendering if ( !bOutfit ) { InjectHook(0x5E8079, RenderWeapon); InjectHook(0x733760, RenderWeaponPedsForPC, HookType::Jump); } } if ( GetPrivateProfileIntW(L"SilentPatch", L"EnableScriptFixes", -1, wcModulePath) == 1 ) { using namespace ScriptFixes; // Gym glitch fix Patch(0x470B83, 0xCD8B); Patch(0x470B8A, 0x8B04508B); Patch(0x470B8E, 0x9000); Nop(0x470B90, 1); InjectHook(0x470B85, &CRunningScript::GetDay_GymGlitch, HookType::Call); // Basketball fix InterceptCall( 0x5D20D0, TheScriptsLoad, TheScriptsLoad_BasketballFix ); std::array wipeLocalVars = { 0x489A70, 0x489AF0 }; HookEach_SCMFixes(wipeLocalVars, InterceptCall); } if ( GetPrivateProfileIntW(L"SilentPatch", L"SkipIntroSplashes", -1, wcModulePath) == 1 ) { // Skip the damn intro splash Patch(AddressByRegion_11(0x749388), 0x62EB); } if ( GetPrivateProfileIntW(L"SilentPatch", L"SmallSteamTexts", -1, wcModulePath) == 1 ) { // We're on 1.01 - make texts smaller Patch(0x58CB57, &fSteamSubtitleSizeY); Patch(0x58CBDF, &fSteamSubtitleSizeY); Patch(0x58CC9E, &fSteamSubtitleSizeY); Patch(0x58CB6D, &fSteamSubtitleSizeX); Patch(0x58CBF5, &fSteamSubtitleSizeX); Patch(0x58CCB4, &fSteamSubtitleSizeX); Patch(0x4EA428, &fSteamRadioNamePosY); Patch(0x4EA372, &fSteamRadioNameSizeY); Patch(0x4EA388, &fSteamRadioNameSizeX); } if ( int INIoption = GetPrivateProfileIntW(L"SilentPatch", L"ColouredZoneNames", -1, wcModulePath); INIoption == 1 ) { // Coloured zone names Patch(0x58B58E, 0x0E75); Patch(0x58B595, 0x0775); InjectHook(0x58B5B4, &BlendGangColour); } else if ( INIoption == 0 ) { Patch(0x58B57E, 0xEB); } // ImVehFt conflicts if ( !bHasImVehFt ) { // Lights InjectHook(0x4C838C, LightMaterialsFix, HookType::Call); // Flying components InjectHook(0x59F950, &CObject::Render_Stub, HookType::Jump); } if ( !bHasImVehFt && !bSAMP ) { // Properly random numberplates DWORD* pVMT = *(DWORD**)0x4C767C; Patch(&pVMT[7], &CVehicleModelInfo::Shutdown_Stub); InjectHook(0x4C984D, &CVehicleModelInfo::SetCarCustomPlate); InjectHook(0x6D7288, &CVehicle::CustomCarPlate_TextureCreate); InjectHook(0x6D6D4C, &CVehicle::CustomCarPlate_BeforeRenderingStart); InjectHook(0x6FE810, CCustomCarPlateMgr::SetupClumpAfterVehicleUpgrade, HookType::Jump); Nop(0x6D6D47, 2); } // SSE conflicts if ( moduleList.Get(L"shadows") == nullptr ) { Patch(0x706E8C, 0x52909090); InjectHook(0x706E92, &CShadowCamera::Update); } // Bigger streamed entity linked lists // Increase only if they're not increased already if ( *(DWORD*)0x5B9635 == 12000 ) { Patch(0x5B9635, 15000); Patch(0x5B9690, 15000); } // Read CCustomCarPlateMgr::GeneratePlateText from here // to work fine with Deji's Custom Plate Format // Albeit 1.01 obfuscates this function CCustomCarPlateMgr::GeneratePlateText = (decltype(CCustomCarPlateMgr::GeneratePlateText))0x6FDDE0; FLAUtils::Init( moduleList ); return FALSE; } return TRUE; } BOOL InjectDelayedPatches_Steam() { #ifdef NDEBUG { const int messageResult = MessageBoxW( nullptr, L"You're using a 3.0 executable which is no longer supported by SilentPatch!\n\n" L"Since this is an old Steam EXE, by now you should have either downgraded to 1.0 or started using an up to date version. It is recommended to " L"verify your game's cache on Steam and then downgrade it to 1.0. Do you want to download San Andreas Downgrader now?\n\n" L"Pressing Yes will close the game and open your web browser. Press No to proceed to the game anyway.", L"SilentPatch", MB_YESNO | MB_ICONWARNING ); if ( messageResult == IDYES ) { ShellExecuteW( nullptr, L"open", L"http://gtaforums.com/topic/753764-/", nullptr, nullptr, SW_SHOWNORMAL ); return TRUE; } } #endif if ( !IsAlreadyRunning() ) { using namespace Memory; const HINSTANCE hInstance = GetModuleHandle( nullptr ); std::unique_ptr Protect = ScopedUnprotect::UnprotectSectionOrFullModule( hInstance, ".text" ); ScopedUnprotect::Section Protect2( hInstance, ".rdata" ); // Obtain a path to the ASI wchar_t wcModulePath[MAX_PATH]; GetModuleFileNameW(reinterpret_cast(&__ImageBase), wcModulePath, _countof(wcModulePath) - 3); // Minus max required space for extension PathRenameExtensionW(wcModulePath, L".ini"); const ModuleList moduleList; bool bHasImVehFt = moduleList.Get(L"ImVehFt") != nullptr; bool bSAMP = moduleList.Get(L"samp") != nullptr; bool bSARender = moduleList.Get(L"SARender") != nullptr; const bool bOutfit = moduleList.Get(L"outfit") != nullptr; if ( bSAMP ) { LSRPMode::ReadServersList(wcModulePath); LSRPMode::DetectPlayingOnLSRP(); } ReadRotorFixExceptions(wcModulePath); if ( GetPrivateProfileIntW(L"SilentPatch", L"SunSizeHack", -1, wcModulePath) == 1 ) { // PS2 sun - more static const double dSunMult = (1050.0 * 0.95) / 1500.0; Patch(0x734DF0, &dSunMult); if ( !bSAMP ) { ReadCall( 0x54E0B6, DoSunAndMoon ); InjectHook(0x54E0B6, SunAndMoonFarClip); Patch(0x734DEA, &fSunFarClip); } } if ( !bSARender ) { // Twopass rendering (experimental) Patch(0x76E230, MovingPropellerRender); Patch(0x76E160, MovingPropellerRender); Patch(0x76E4F0, RenderBigVehicleActomic); // Weapons rendering if ( !bOutfit ) { InjectHook(0x604DD9, RenderWeapon); InjectHook(0x76D170, RenderWeaponPedsForPC, HookType::Jump); } } if ( GetPrivateProfileIntW(L"SilentPatch", L"EnableScriptFixes", -1, wcModulePath) == 1 ) { using namespace ScriptFixes; // Gym glitch fix Patch(0x476C2A, 0xCD8B); Patch(0x476C31, 0x408B088B); Patch(0x476C35, 0x9004); Nop(0x476C37, 1); InjectHook(0x476C2C, &CRunningScript::GetDay_GymGlitch, HookType::Call); // Basketball fix InterceptCall( 0x5EE017, TheScriptsLoad, TheScriptsLoad_BasketballFix ); std::array wipeLocalVars = { 0x4907AE, 0x49072E }; HookEach_SCMFixes(wipeLocalVars, InterceptCall); } if ( GetPrivateProfileIntW(L"SilentPatch", L"SmallSteamTexts", -1, wcModulePath) == 0 ) { // We're on Steam - make texts bigger Patch(0x59A719, &dRetailSubtitleSizeY); Patch(0x59A7B7, &dRetailSubtitleSizeY2); Patch(0x59A8A1, &dRetailSubtitleSizeY2); Patch(0x59A737, &dRetailSubtitleSizeX); Patch(0x59A7D5, &dRetailSubtitleSizeX); Patch(0x59A8BF, &dRetailSubtitleSizeX); Patch(0x4F5A71, &dRetailRadioNamePosY); Patch(0x4F59A1, &dRetailRadioNameSizeY); Patch(0x4F59BF, &dRetailRadioNameSizeX); } if ( int INIoption = GetPrivateProfileIntW(L"SilentPatch", L"ColouredZoneNames", -1, wcModulePath); INIoption == 1 ) { // Coloured zone names Patch(0x598F65, 0x0C75); Patch(0x598F6B, 0x0675); InjectHook(0x598F87, &BlendGangColour); } else if ( INIoption == 0 ) { Patch(0x598F56, 0xEB); } // ImVehFt conflicts if ( !bHasImVehFt ) { // Lights InjectHook(0x4D2C06, LightMaterialsFix, HookType::Call); // Flying components InjectHook(0x5B80E0, &CObject::Render_Stub, HookType::Jump); // Cars getting dirty // Only 1.0 and Steam InjectHook( 0x5F2580, RemapDirt, HookType::Jump ); InjectHook(0x4D3F4D, &CVehicleModelInfo::FindEditableMaterialList, HookType::Call); Patch(0x4D3F52, 0x0FEBCE8B); } if ( !bHasImVehFt && !bSAMP ) { // Properly random numberplates DWORD* pVMT = *(DWORD**)0x4D1E9A; Patch(&pVMT[7], &CVehicleModelInfo::Shutdown_Stub); InjectHook(0x4D3F65, &CVehicleModelInfo::SetCarCustomPlate); InjectHook(0x711F28, &CVehicle::CustomCarPlate_TextureCreate); InjectHook(0x71194D, &CVehicle::CustomCarPlate_BeforeRenderingStart); InjectHook(0x736BD0, CCustomCarPlateMgr::SetupClumpAfterVehicleUpgrade, HookType::Jump); Nop(0x711948, 2); } // SSE conflicts if ( moduleList.Get(L"shadows") == nullptr ) { Patch(0x74A864, 0x52909090); InjectHook(0x74A86A, &CShadowCamera::Update); } // Bigger streamed entity linked lists // Increase only if they're not increased already if ( *(DWORD*)0x5D5780 == 12000 ) { Patch(0x5D5720, 1250); Patch(0x5D5780, 15000); } // Read CCustomCarPlateMgr::GeneratePlateText from here // to work fine with Deji's Custom Plate Format ReadCall( 0x4D3DA4, CCustomCarPlateMgr::GeneratePlateText ); FLAUtils::Init( moduleList ); return FALSE; } return TRUE; } BOOL InjectDelayedPatches_Newsteam() { if ( !IsAlreadyRunning() ) { using namespace Memory; using namespace hook; const HINSTANCE hInstance = GetModuleHandle( nullptr ); std::unique_ptr Protect = ScopedUnprotect::UnprotectSectionOrFullModule( hInstance, ".text" ); ScopedUnprotect::Section Protect2( hInstance, ".rdata" ); // Race condition in CdStream fixed // Not taking effect with modloader //if ( !ModCompat::ModloaderCdStreamRaceConditionAware( modloaderModule ) ) { // Don't patch if old FLA and enhanced IMGs are in place // For new FLA, we patch everything except CdStreamThread and then interop with FLA constexpr bool flaBugAware = false; constexpr bool usesEnhancedImages = false; if constexpr ( !usesEnhancedImages || flaBugAware ) { void* initThread = get_pattern( "74 14 81 25 ? ? ? ? ? ? ? ? C7 05", 0x16 ); ReadCall( initThread, CdStreamSync::orgCdStreamInitThread ); InjectHook( initThread, CdStreamSync::CdStreamInitThread ); auto cdStreamSync = pattern( "8B 0D ? ? ? ? 8D 04 40 03 C0" ).get_one(); // 0x4064E6 Patch( cdStreamSync.get( 0x18 ), { 0x56, 0xFF, 0x15 } ); Patch( cdStreamSync.get( 0x18 + 3 ), &CdStreamSync::CdStreamSyncOnObject ); Patch( cdStreamSync.get( 0x18 + 3 + 4 ), { 0x5E, 0x5D, 0xC3 } ); // pop ebp / retn Patch( cdStreamSync.get( 0x5E + 2 ), &CdStreamSync::pGetOverlappedResult ); Patch( cdStreamSync.get( 0x5E + 6 ), { 0x5E, 0x5D, 0xC3 } ); // pop esi / pop ebp / retn if constexpr ( !usesEnhancedImages ) { auto cdStreamThread = pattern( "C7 46 04 00 00 00 00 8A 4E 0D" ).get_one(); Patch( cdStreamThread.get(), { 0x56, 0xFF, 0x15 } ); Patch( cdStreamThread.get( 3 ), &CdStreamSync::CdStreamThreadOnObject ); Patch( cdStreamThread.get( 3 + 4 ), { 0xEB, 0x17 } ); } auto cdStreamInitThread = pattern( "6A 00 6A 02 6A 00 6A 00 FF D3" ).get_one(); Patch( cdStreamInitThread.get(), { 0xFF, 0x15 } ); Patch( cdStreamInitThread.get( 2 ), &CdStreamSync::CdStreamInitializeSyncObject ); Nop( cdStreamInitThread.get( 6 ), 4 ); Nop( cdStreamInitThread.get( 0x16 ), 2 ); auto cdStreamShutdown = pattern( "8B 4C 07 14" ).get_one(); Patch( cdStreamShutdown.get(), { 0x56, 0x50 } ); InjectHook( cdStreamShutdown.get( 2 ), CdStreamSync::CdStreamShutdownSyncObject_Stub, HookType::Call ); } } return FALSE; } return TRUE; } static char aNoDesktopMode[64]; void Patch_SA_10(HINSTANCE hInstance) { using namespace Memory; #if MEM_VALIDATORS InstallMemValidator(); #endif // IsAlreadyRunning needs to be read relatively late - the later, the better { const uintptr_t pIsAlreadyRunning = AddressByRegion_10(0x74872D); ReadCall( pIsAlreadyRunning, IsAlreadyRunning ); InjectHook(pIsAlreadyRunning, InjectDelayedPatches_10); } // Newsteam crash fix pDirect = *(RpLight***)0x5BA573; DarkVehiclesFix1_JumpBack = AddressByRegion_10(0x756D90); // (Hopefully) more precise frame limiter { uintptr_t pAddress = AddressByRegion_10(0x748D9B); ReadCall( pAddress, RsEventHandler ); InjectHook(pAddress, NewFrameRender); InjectHook(AddressByRegion_10(0x748D1F), GetTimeSinceLastFrame); } // Set CAEDataStream to use an old structure CAEDataStream::SetStructType(false); //Patch(0x5D7265, 0xEB); // Heli rotors InjectHook(0x6CAB70, &CPlane::Render_Stub, HookType::Jump); InjectHook(0x6C4400, &CHeli::Render_Stub, HookType::Jump); // Boats /*Patch(0x4C79DF, 0x19); Patch(0x733A87, EXPAND_BOAT_ALPHA_ATOMIC_LISTS * sizeof(AlphaObjectInfo)); Patch(0x733AD7, EXPAND_BOAT_ALPHA_ATOMIC_LISTS * sizeof(AlphaObjectInfo));*/ // Fixed strafing? Hopefully /*static const float fStrafeCheck = 0.1f; Patch(0x61E0C2, &fStrafeCheck); Nop(0x61E0CA, 6);*/ // RefFix static const float fRefZVal = 1.0f; static const float* const pRefFal = &fRefZVal; Patch(0x6FB97A, &pRefFal); Patch(0x6FB9A0, 0); // Plane rotors InjectHook(0x4C7981, PlaneAtomicRendererSetup, HookType::Jump); // DOUBLE_RWHEELS Patch(0x4C9290, 0xE281); Patch(0x4C9292, ~(rwMATRIXTYPEMASK|rwMATRIXINTERNALIDENTITY)); // A fix for DOUBLE_RWHEELS trailers InjectHook(0x4C9223, TrailerDoubleRWheelsFix, HookType::Jump); InjectHook(0x4C92F4, TrailerDoubleRWheelsFix2, HookType::Jump); // No framedelay Patch(0x53E923, 0x43EB); Patch(0x53E99F, 0x10); Nop(0x53E9A5, 1); // Disable re-initialization of DirectInput mouse device by the game Patch(0x576CCC, 0xEB); Patch(0x576EBA, 0xEB); Patch(0x576F8A, 0xEB); // Make sure DirectInput mouse device is set non-exclusive (may not be needed?) Patch(AddressByRegion_10(0x7469A0), 0x9090C030); // Hunter interior & static_rotor for helis InjectHook(0x4C78F2, HunterTest, HookType::Jump); InjectHook(0x4C9618, CacheCRC32); // Fixed blown up car rendering // ONLY 1.0 InjectHook(0x5D993F, DarkVehiclesFix1); InjectHook(0x5D9A74, DarkVehiclesFix2, HookType::Jump); InjectHook(0x5D9B44, DarkVehiclesFix3, HookType::Jump); InjectHook(0x5D9CB2, DarkVehiclesFix4, HookType::Jump); // Bindable NUM5 // Only 1.0 and Steam Nop(0x57DC55, 2); // TEMP //Patch(0x733B05, 40); //Patch(0x733B55, 40); //Patch(0x5B3ADD, 4); // Lightbeam fix // We need to check for presence of old lightbeam fix - first validate everything old SP did if ( MemEquals( 0x6A2E95, { 0xFF, 0x52, 0x20 } ) && MemEquals( 0x6E0F63, { 0xA1 } ) && MemEquals( 0x6E0F7C, { 0x8B, 0x15 } ) && MemEquals( 0x6E0F95, { 0x8B, 0x0D } ) && MemEquals( 0x6E0FAF, { 0xA1 } ) && MemEquals( 0x6E13D5, { 0xA1 } ) && MemEquals( 0x6E13ED, { 0x8B, 0x15 } ) && MemEquals( 0x6E141F, { 0xA1 } ) ) { using namespace LightbeamFix; std::array doHeadLightBeam = { 0x6A2EDA, 0x6A2EF2, 0x6BDE80 }; CVehicle::HookEach_DoHeadLightBeam(doHeadLightBeam, InterceptCall); Patch( 0x6E0F37 + 2, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0F63 + 1, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0F6F + 2, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0F7C + 2, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0F89 + 1, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0F95 + 2, &RenderStateWrapper::PushStatePPtr ); // rwRENDERSTATETEXTURERASTER not saved Patch( 0x6E0FAF + 1, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0FBB + 2, &RenderStateWrapper::PushStatePPtr ); Patch( 0x6E0FCB + 2, &RenderStateWrapper::PushStatePPtr ); // rwRENDERSTATETEXTURERASTER not saved Patch( 0x6E13E0 + 2, &RenderStateWrapper::PopStatePPtr ); Patch( 0x6E13ED + 2, &RenderStateWrapper::PopStatePPtr ); Patch( 0x6E13FA + 1, &RenderStateWrapper::PopStatePPtr ); Patch( 0x6E1406 + 2, &RenderStateWrapper::PopStatePPtr ); Patch( 0x6E1413 + 2, &RenderStateWrapper::PopStatePPtr ); Patch( 0x6E141F + 1, &RenderStateWrapper::PopStatePPtr ); // Debug override registered in delayed patches hookedSuccessfully = true; } // PS2 SUN!!!!!!!!!!!!!!!!! Nop(0x6FB17C, 3); #if defined EXPAND_ALPHA_ENTITY_LISTS // Bigger alpha entity lists Patch(0x733B05, EXPAND_ALPHA_ENTITY_LISTS * 20); Patch(0x733B55, EXPAND_ALPHA_ENTITY_LISTS * 20); #endif // Unlocked widescreen resolutions //Patch(0x745B71, 0x9090687D); Patch(0x745B81, 0x9090587D); Patch(0x74596C, 0x9090127D); Nop(0x745970, 2); //Nop(0x745B75, 2); Nop(0x745B85, 2); Nop(0x7459E1, 2); // Heap corruption fix Nop(0x5C25D3, 5); // User Tracks fix ReadCall( 0x4D9B66, SetVolume ); InjectHook(0x4D9B66, UserTracksFix); InjectHook(0x4D9BB5, 0x4F2FD0); // FLAC support InjectHook(0x4F373D, LoadFLAC, HookType::Jump); InjectHook(0x57BEFE, FLACInit); InjectHook(0x4F3787, CAEWaveDecoderInit); Patch(0x4F376A, 0x18EB); //Patch(0x4F378F, sizeof(CAEWaveDecoder)); Patch(0x4F3210, UserTrackExtensions); Patch(0x4F3241, &UserTrackExtensions->Codec); Patch(0x4F35E7, &UserTrackExtensions[1].Codec); Patch(0x4F322D, sizeof(UserTrackExtensions)); // Impound garages working correctly InjectHook(0x425179, 0x448990); // CGarages::IsPointWithinAnyGarage InjectHook(0x425369, 0x448990); // CGarages::IsPointWithinAnyGarage InjectHook(0x425411, 0x448990); // CGarages::IsPointWithinAnyGarage // Impounding after busted works Nop(0x443292, 5); // Mouse rotates an airbone car only with Steer with Mouse option enabled bool* bEnableMouseSteering = *(bool**)0x6AD7AD; // CVehicle::m_bEnableMouseSteering Patch(0x6B4EC0, bEnableMouseSteering); Patch(0x6CE827, bEnableMouseSteering); // Patched CAutomobile::Fix // misc_x parts don't get reset (Bandito fix), Towtruck's bouncing panel is not reset Patch(0x6A34C9, 0x5EEB); Patch(0x6A3555, 0x5E5FCF8B); Patch(0x6A3559, 0x448B5B5D); Patch(0x6A355D, 0x89644824); Patch(0x6A3561, 5); Patch(0x6A3565, 0x54C48300); InjectHook(0x6A3569, &CAutomobile::Fix_SilentPatch, HookType::Jump); // Patched CPlane::Fix // Doors don't get reset (they can't get damaged anyway), bouncing panels DO reset // but not on Vortex Patch(0x6CABD0, 0xEB); Patch(0x6CAC05, 0x5E5FCF8B); InjectHook(0x6CAC09, &CPlane::Fix_SilentPatch, HookType::Jump); // Weapon icon fix (crosshairs mess up rwRENDERSTATEZWRITEENABLE) // Only 1.0 and 1.01, Steam somehow fixed it (not the same way though) Nop(0x58E210, 3); Nop(0x58EAB7, 3); Nop(0x58EAE1, 3); // Zones fix // Only 1.0 and Steam InjectHook(0x572130, GetCurrentZoneLockedOrUnlocked, HookType::Jump); // Bilinear filtering for license plates //Patch(0x6FD528, rwFILTERLINEAR); Patch(0x6FDF47, rwFILTERLINEAR); // -//- Roadsign maganer //Patch(0x6FE147, rwFILTERLINEAR); // Bilinear filtering with mipmaps for weapon icons Patch(0x58D7DA, rwFILTERMIPLINEAR); // Directional multiplier value from timecyc.dat properly using floats Patch(0x5BBFC9, 0x14EB); // Directional multiplier defaults to 1.0f Patch( 0x5BBB04, { 0xC7, 0x84, 0x24, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x90 } ); // All lights get casted at vehicles Patch(0x5D9A88, 8); Patch(0x5D9A91, 8); Patch(0x5D9F1F, 8); // 6 extra directionals on Medium and higher // push eax // call GetMaxExtraDirectionals // add esp, 4 // mov ebx, eax // nop Patch( 0x735881, 0x50 ); InjectHook( 0x735881 + 1, GetMaxExtraDirectionals, HookType::Call ); Patch( 0x735881 + 6, { 0x83, 0xC4, 0x04, 0x8B, 0xD8 } ); Nop( 0x735881 + 11, 3 ); // Default resolution to native resolution const auto [width, height] = GetDesktopResolution(); sprintf_s(aNoDesktopMode, "Cannot find %ux%ux32 video mode", width, height); if (width != 0 && height != 0) { Patch(0x746363, width); Patch(0x746368, height); Patch(0x7463C8, aNoDesktopMode); } // Corrected Map screen 1px issue Patch(0x575DE7, -0.5f); Patch(0x575DA7, -0.5f); Patch(0x575DAF, -0.5f); Patch(0x575D5C, -0.5f); Patch(0x575CDA, -0.5f); Patch(0x575D0C, -0.5f); // Cars drive on water cheat Patch(&(*(DWORD**)0x438513)[34], 0xE5FC92C3); // No DirectPlay dependency // mov eax, 0x900 Patch(AddressByRegion_10(0x74754A), 0xB8); Patch(AddressByRegion_10(0x74754B), 0x900); // SHGetFolderPath on User Files InjectHook(0x744FB0, GetMyDocumentsPathSA, HookType::Jump); // Fixed muzzleflash not showing from last bullet // nop \ test al, al \ jz Nop(0x61ECDC, 6); Patch(0x61ECE2, { 0x84, 0xC0, 0x74 }); // Proper randomizations InjectHook(0x44E82E, Int32Rand); // Missing ped paths InjectHook(0x44ECEE, Int32Rand); // Missing ped paths InjectHook(0x666EA0, Int32Rand); // Prostitutes // Help boxes showing with big message // Game seems to assume they can show together Nop(0x58BA8F, 6); // Fixed lens flare Patch(0x70F45A, 0); // TODO: Is this needed? Patch(0x6FB621, 0xC3); // nop CSprite::FlushSpriteBuffer // Add CSprite::FlushSpriteBuffer, jmp loc_6FB605 at the bottom of the function Patch(0x6FB600, 0x21); InjectHook(0x6FB622, 0x70CF20, HookType::Call); Patch(0x6FB627, 0xDCEB); // nop / mov eax, offset FlushLensSwitchZ Patch(0x6FB476, 0xB990); Patch(0x6FB478, &FlushLensSwitchZ); Patch(0x6FB480, 0xD1FF); Nop(0x6FB482, 1); // nop / mov ecx, offset InitBufferSwitchZ Patch(0x6FAF28, 0xB990); Patch(0x6FAF2A, &InitBufferSwitchZ); Patch(0x6FAF32, 0xD1FF); Nop(0x6FAF34, 1); // Y axis sensitivity fix // By ThirteenAG float* sens = *(float**)0x50F03C; Patch(0x50EB70 + 0x4D6 + 0x2, sens); Patch(0x50F970 + 0x1B6 + 0x2, sens); Patch(0x5105C0 + 0x666 + 0x2, sens); Patch(0x511B50 + 0x2B8 + 0x2, sens); Patch(0x521500 + 0xD8C + 0x2, sens); // Don't lock mouse Y axis during fadeins Patch(0x50FBB4, 0x27EB); Patch(0x510512, 0xE990); InjectHook(0x524071, 0x524139, HookType::Jump); // Fixed mirrors crash // TODO: Change when short jumps are supported // test eax, eax / je 0727203 / add esp, 4 Patch( 0x7271CB, { 0x85, 0xC0, 0x74, 0x34, 0x83, 0xC4, 0x04 } ); // Mirrors depth fix & bumped quality InjectHook(0x72701D, CreateMirrorBuffers); // Fixed MSAA options { using namespace MSAAFixes; Patch(0x57D126, 0xEB); Nop(0x57D0E8, 2); Patch(AddressByRegion_10(0x7F6C9B), 0xEB); Patch(AddressByRegion_10(0x7F60C6), 0xEB); Patch(AddressByRegion_10(0x7F6683), { 0x90, 0xE9 }); std::array getMaxMultiSamplingLevels = { 0x57D136, 0x57D0EA }; HookEach_GetMaxMultiSamplingLevels(getMaxMultiSamplingLevels, InterceptCall); std::array setOrChangeMultiSamplingLevels = { 0x5744FD, 0x57D162, 0x57D2A6, 0x746350 }; HookEach_SetOrChangeMultiSamplingLevels(setOrChangeMultiSamplingLevels, InterceptCall); Nop(0x57A0FC, 1); InjectHook(0x57A0FD, MSAAText, HookType::Call); } // Fixed car collisions - car you're hitting gets proper damage now InjectHook(0x5428EA, FixedCarDamage, HookType::Call); // Car explosion crash with multimonitor // Unitialized collision data breaking stencil shadows { using namespace UnitializedCollisionDataFix; VP::InterceptCall(ModCompat::Utils::GetFunctionAddrIfRerouted(0x40F870) + 0x63, orgMemMgrMalloc, CollisionData_MallocAndInit); std::array newAndInit = { ModCompat::Utils::GetFunctionAddrIfRerouted(0x40F740) + 0xC, ModCompat::Utils::GetFunctionAddrIfRerouted(0x40F810) + 0xD, }; HookEach_CollisionDataNew(newAndInit, InterceptCall); } // Crash when entering advanced display options on a dual monitor machine after: // - starting game on primary monitor in maximum resolution, exiting, // starting again in maximum resolution on secondary monitor. // Secondary monitor maximum resolution had to be greater than maximum resolution of primary monitor. // Not in 1.01 ReadCall( 0x745B1E, orgGetNumVideoModes ); InjectHook(0x745B1E, GetNumVideoModes_Store); InjectHook(0x745A81, GetNumVideoModes_Retrieve); // Fixed escalators crash ReadCall( 0x7185B5, orgEscalatorsUpdate ); InjectHook(0x7185B5, UpdateEscalators); InjectHook(0x71791F, &CEscalator::SwitchOffNoRemove); // Don't allocate constant memory for stencil shadows every frame InjectHook(0x711DD5, StencilShadowAlloc, HookType::Call); Nop(0x711E0D, 3); Patch(0x711DDA, { 0xEB, 0x2C }); Patch(0x711E5F, { 0x5F, 0x5D, 0xC3 }); // pop edi, pop ebp, ret // "Streaming memory bug" fix InjectHook(0x4C51A9, GTARtAnimInterpolatorSetCurrentAnim); // Fixed ammo for melee weapons in cheats Patch(0x43890B+1, 1); // knife Patch(0x4389F8+1, 1); // knife Patch(0x438B9F+1, 1); // chainsaw Patch(0x438C58+1, 1); // chainsaw Patch(0x4395C8+1, 1); // parachute Patch(0x439F1F, 0x53); // katana Patch(0x439F20, 0x016A); // Fixed police scanner names char* pScannerNames = *(char**)0x4E72D4; strcpy_s(pScannerNames + (8*113), 8, "WESTP"); strcpy_s(pScannerNames + (8*134), 8, "????"); // AI accuracy issue Nop(0x73B3AE, 1); InjectHook( 0x73B3AE + 1, WeaponRangeMult_VehicleCheck, HookType::Call ); // New timers fix InjectHook( 0x561C32, asmTimers_ftol_PauseMode ); InjectHook( 0x561902, asmTimers_ftol_NonClipped ); InjectHook( 0x56191A, asmTimers_ftol ); InjectHook( 0x46A036, asmTimers_SCMdelta ); // Don't catch WM_SYSKEYDOWN and WM_SYSKEYUP (fixes Alt+F4) InjectHook( AddressByRegion_10(0x748220), AddressByRegion_10(0x748446), HookType::Jump ); Patch( AddressByRegion_10(0x7481E3), 0x5C ); // esi -> ebx Patch( AddressByRegion_10(0x7481EA), 0x53 ); // esi -> ebx Patch( AddressByRegion_10(0x74820D), 0xFB ); // esi -> ebx Patch( AddressByRegion_10(0x7481EF), 0x54-0x3C ); // use stack space for new lParam Patch( AddressByRegion_10(0x748200), 0x4C-0x3C ); // use stack space for new lParam Patch( AddressByRegion_10(0x748214), 0x4C-0x3C ); // use stack space for new lParam InjectHook( AddressByRegion_10(0x74826A), AddressByRegion_10(0x748446), HookType::Jump ); Patch( AddressByRegion_10(0x74822D), 0x5C ); // esi -> ebx Patch( AddressByRegion_10(0x748234), 0x53 ); // esi -> ebx Patch( AddressByRegion_10(0x748257), 0xFB ); // esi -> ebx Patch( AddressByRegion_10(0x748239), 0x54-0x3C ); // use stack space for new lParam Patch( AddressByRegion_10(0x74824A), 0x4C-0x3C ); // use stack space for new lParam Patch( AddressByRegion_10(0x74825E), 0x4C-0x3C ); // use stack space for new lParam // FuckCarCompletely not fixing panels Nop(0x6C268D, 3); // 014C cargen counter fix (by spaceeinstein) Patch( 0x06F3E2C + 1, 0xBF ); // movzx ecx, ax -> movsx ecx, ax Patch( 0x6F3E32, 0x74 ); // jge -> jz // Linear filtering on script sprites ReadCall( 0x58C092, orgDrawScriptSpritesAndRectangles ); InjectHook( 0x58C092, DrawScriptSpritesAndRectangles ); // Properly initialize all CVehicleModelInfo fields ReadCall( 0x4C75E4, orgVehicleModelInfoCtor ); InjectHook( 0x4C75E4, VehicleModelInfoCtor ); // Animated Phoenix hood scoop { auto* automobilePreRender = (*(decltype(CAutomobile::orgAutomobilePreRender<0>)**)(0x6B0AD2 + 2)) + 17; CAutomobile::orgAutomobilePreRender<0> = *automobilePreRender; Patch(automobilePreRender, &CAutomobile::PreRender_SilentPatch<0>); std::array preRender = { 0x6C7E7A, 0x6CEAEC, 0x6CFADC }; CAutomobile::HookEach_PreRender(preRender, InterceptCall); } // Extra animations for planes auto* planePreRender = (*(decltype(CPlane::orgPlanePreRender)**)(0x6C8E5A + 2)) + 17; CPlane::orgPlanePreRender = *planePreRender; Patch(planePreRender, &CPlane::PreRender_Stub); // Stop BF Injection/Bandito/Hotknife rotating engine components when engine is off Patch(0x6AC2BE + 2, &CAutomobile::ms_engineCompSpeed); Patch(0x6ACB91 + 2, &CAutomobile::ms_engineCompSpeed); // Make freeing temp objects more aggressive to fix vending crash InjectHook( 0x5A1840, CObject::TryToFreeUpTempObjects_SilentPatch, HookType::Jump ); // Remove FILE_FLAG_NO_BUFFERING from CdStreams Patch( 0x406BC6, 0xEB ); // Proper metric-imperial conversion constants static const float METERS_TO_FEET = 3.280839895f; Patch( 0x55942F + 2, &METERS_TO_FEET ); Patch( 0x55AA96 + 2, &METERS_TO_FEET ); // Fixed impounding of random vehicles (because CVehicle::~CVehicle doesn't remove cars from apCarsToKeep) ReadCall( 0x6E2B6E, orgRecordVehicleDeleted ); InjectHook( 0x6E2B6E, RecordVehicleDeleted_AndRemoveFromVehicleList ); // Don't include an extra D3DLIGHT on vehicles since we fixed directional already // By aap Patch(0x5D88D1 + 6, 0); Patch(0x5D88DB + 6, 0); Patch(0x5D88E5 + 6, 0); Patch(0x5D88F9 + 6, 0); Patch(0x5D8903 + 6, 0); Patch(0x5D890D + 6, 0); // Fixed CAEAudioUtility timers - not typecasting to float so we're not losing precision after X days of PC uptime // Also fixed integer division by zero Patch( 0x5B9868 + 2, &pAudioUtilsFrequency ); InjectHook( 0x5B9886, AudioUtilsGetStartTime ); InjectHook( 0x4D9E80, AudioUtilsGetCurrentTimeInMs, HookType::Jump ); // Car generators placed in interiors visible everywhere InjectHook( 0x6F3B30, &CEntity::SetPositionAndAreaCode ); // Fixed bomb ownership/bombs saving for bikes { std::array restoreCar = { ModCompat::Utils::GetFunctionAddrIfRerouted(0x448550) + 0x1A, ModCompat::Utils::GetFunctionAddrIfRerouted(0x4485C0) + 0x1B, }; CStoredCar::HookEach_RestoreCar(restoreCar, VP::InterceptCall); } // unnamed CdStream semaphore Patch( 0x406945, { 0x6A, 0x00 } ); // push 0 \ nop Nop( 0x406945 + 2, 3 ); // Correct streaming when using RC vehicles InjectHook( 0x55574B, FindPlayerEntityWithRC ); InjectHook( 0x5557C3, FindPlayerVehicle_RCWrap ); // TODO: Verify this fix, might be causing crashes atm and too risky to include #if 0 // Fixed CPlayerInfo assignment operator InjectHook( 0x45DEF0, &CPlayerInfo::operator=, HookType::Jump ); #endif // Fixed triangle above recruitable peds' heads Patch( 0x60BC52 + 2, 8 ); // GANG2 // Credits =) ReadCall( 0x5AF87A, Credits::PrintCreditText ); ReadCall( 0x5AF8A4, Credits::PrintCreditText_Hooked ); InjectHook( 0x5AF8A4, Credits::PrintSPCredits ); // Fixed ammo from SCM { ReadCall( 0x47D335, CPed::orgGiveWeapon ); InjectHook( 0x47D335, &CPed::GiveWeapon_SP ); } // Fixed bicycle on fire - instead of CJ being set on fire, bicycle's driver is { using namespace BicycleFire; Patch( 0x53A984, { 0x90, 0x57 } ); // nop \ push edi Patch( 0x53A9A7, { 0x90, 0x57 } ); // nop \ push edi InjectHook( 0x53A984 + 2, GetVehicleDriver ); InjectHook( 0x53A9A7 + 2, GetVehicleDriver ); ReadCall( 0x53A990, CPlayerPed::orgDoStuffToGoOnFire ); InjectHook( 0x53A990, DoStuffToGoOnFire_NullAndPlayerCheck ); ReadCall( 0x53A9B7, CFireManager::orgStartFire ); InjectHook( 0x53A9B7, &CFireManager::StartFire_NullEntityCheck ); } // Decreased keyboard input latency { using namespace KeyboardInputFix; NewKeyState = *(void**)( 0x541E21 + 1 ); OldKeyState = *(void**)( 0x541E26 + 1 ); TempKeyState = *(void**)( 0x541E32 + 1 ); objSize = *(uint32_t*)( 0x541E1C + 1 ) * 4; ReadCall( 0x541DEB, orgClearSimButtonPressCheckers ); // Only hook if this call takes to somewhere in gta_sa.exe, else bail out since it's been tampered with if ( hInstance == ModCompat::Utils::GetModuleHandleFromAddress(orgClearSimButtonPressCheckers) ) { InjectHook( 0x541DEB, ClearSimButtonPressCheckers ); Nop( 0x541E2B, 2 ); Nop( 0x541E3C, 2 ); } } // Fixed handling.cfg name matching (names don't need unique prefixes anymore) { using namespace HandlingNameLoadFix; InjectHook( 0x6F4F58, strncpy_Fix ); InjectHook( 0x6F4F64, strncmp_Fix ); } // Firela animations { using namespace FirelaHook; UpdateMovingCollisionJmp = 0x6B200F; HydraulicControlJmpBack = 0x6B1FBF + 10; InjectHook( 0x6B1FBF, TestFirelaAndFlags, HookType::Jump ); FollowCarCamNoMovement = 0x52551E; FollowCarCamJmpBack = 0x5254F6 + 6; InjectHook( 0x5254F6, CamControlFirela, HookType::Jump ); } // Double artict3 trailer { auto* trailerTowBarPos = (*(decltype(CTrailer::orgGetTowBarPos)**)(0x6D03FD + 2)) + 60; CTrailer::orgGetTowBarPos = *trailerTowBarPos; Patch(trailerTowBarPos, &CTrailer::GetTowBarPos_Stub); } // DFT-30 wheel, Sweeper brushes and other typos in hierarchy { using namespace HierarchyTypoFix; InterceptCall(0x4C5311, orgStrcasecmp, strcasecmp); } // Tug tow bar (misc_b instead of misc_a Nop( 0x6AF2CC, 1 ); InjectHook( 0x6AF2CC + 1, &CAutomobile::GetTowBarFrame, HookType::Call ); // Play passenger's voice lines when killing peds with car, not only when hitting them damages player's vehicle InterceptCall(0x5F05CA, CEntity::orgGetColModel, &CVehicle::PlayPedHitSample_GetColModel); // Prevent samples from playing where they used to, so passengers don't comment on gently pushing peds InterceptCall(0x6A8298, CPed::orgSay, &CPed::Say_SampleBlackList); // Reset variables on New Game { using namespace VariableResets; std::array reInitGameObjectVariables = { 0x53C6DB, 0x53C76D }; HookEach_ReInitGameObjectVariables(reInitGameObjectVariables, InterceptCall); InterceptCall(0x5B89E4, orgLoadPickup, LoadPickup_SaveLine); InterceptCall(0x5B89EE, orgLoadCarGenerator, LoadCarGenerator_SaveLine); InterceptCall(0x5B89F9, orgLoadStuntJump, LoadStuntJump_SaveLine); // Variables to reset GameVariablesToReset.emplace_back( *(bool**)(0x63E8D8+1) ); // CPlayerPed::bHasDisplayedPlayerQuitEnterCarHelpText GameVariablesToReset.emplace_back( *(bool**)(0x44AC97+1) ); // CGarages::RespraysAreFree GameVariablesToReset.emplace_back( *(bool**)(0x44B49D+1) ); // CGarages::BombsAreFree GameVariablesToReset.emplace_back( *(int**)(0x42131F + 2) ); // CCarCtrl::LastTimeFireTruckCreated GameVariablesToReset.emplace_back( *(int**)(0x421319 + 2) ); // CCarCtrl::LastTimeAmbulanceCreated GameVariablesToReset.emplace_back( *(int**)(0x55C843 + 1) ); // CStats::m_CycleSkillCounter GameVariablesToReset.emplace_back( *(int**)(0x55CA39 + 1) ); // CStats::m_SwimUnderWaterCounter GameVariablesToReset.emplace_back( *(int**)(0x55CF3E + 2) ); // CStats::m_WeaponCounter GameVariablesToReset.emplace_back( *(int**)(0x55CF2A + 2) ); // CStats::m_LastWeaponTypeFired GameVariablesToReset.emplace_back( *(int**)(0x55CFC1 + 1) ); // CStats::m_DeathCounter GameVariablesToReset.emplace_back( *(int**)(0x55C5E5 + 1) ); // CStats::m_MaxHealthCounter GameVariablesToReset.emplace_back( *(int**)(0x55D043 + 1) ); // CStats::m_AddToHealthCounter // Non-zero inits still need to be done GameVariablesToReset.emplace_back( *(TimeNextMadDriverChaseCreated_t**)(0x421369 + 2) ); // CCarCtrl::TimeNextMadDriverChaseCreated GameVariablesToReset.emplace_back( *(ResetToTrue_t**)(0x4758A4 + 2) ); // CGameLogic::bPenaltyForDeathApplies GameVariablesToReset.emplace_back( *(ResetToTrue_t**)(0x4758C4 + 1) ); // CGameLogic::bPenaltyForArrestApplies } // Don't clean the car BEFORE Pay 'n Spray doors close, as it gets cleaned later again anyway! Nop( 0x44ACDC, 6 ); // Locale based metric/imperial system { using namespace Localization; InjectHook( 0x56D220, IsMetric_LocaleBased, HookType::Jump ); } // Fix paintjobs vanishing after opening/closing garage without rendering the car first InjectHook( 0x6D0B70, &CVehicle::GetRemapIndex, HookType::Jump ); // Re-introduce corona rotation on PC, like it is in III/VC/SA PS2 { using namespace CoronaRotationFix; // Remove *= 20.0f from recipz to retrieve the original value for later Nop( 0x6FB277, 6 ); ReadCall( 0x6FB2E6, orgRenderOneXLUSprite_Rotate_Aspect ); InjectHook( 0x6FB2E6, RenderOneXLUSprite_Rotate_Aspect_SilentPatch ); } // Fixed static shadows not rendering under fire and pickups { using namespace StaticShadowAlphaFix; ReadCall( 0x53E0C3, orgRenderStaticShadows ); InjectHook( 0x53E0C3, RenderStaticShadows_StateFix ); // Stored shadows conflict with SSE and are patched only when it's not installed } // Disable building pipeline for skinned objects (like parachute) { using namespace SkinBuildingPipelineFix; InterceptCall(0x5D7F1E, orgCustomBuildingDNPipeline_CustomPipeAtomicSetup, CustomBuildingDNPipeline_CustomPipeAtomicSetup_Skinned); } // Reset requested extras if created vehicle has no extras // Fixes eg. lightless taxis InjectHook( 0x4C97B1, CVehicleModelInfo::ResetCompsForNoExtras, HookType::Call ); Nop( 0x4C97B1 + 5, 9 ); // Allow extra6 to be picked with component rule 4 (any) Patch( 0x4C8010 + 4, 6 ); // Disallow moving cam up/down with mouse when looking back/left/right in vehicle { using namespace FollowCarMouseCamFix; orgUseMouse3rdPerson = *(bool**)(0x525615 + 1); Patch( 0x525615 + 1, &useMouseAndLooksForwards ); ReadCall( 0x5245E4, orgGetPad ); InjectHook( 0x5245E4, getPadAndSetFlag ); } // Display stats in kg's as floats (they pass a float and intend to display an integer) Patch( 0x55A954 + 1, "%.0fkgs" ); Patch( 0x5593E4 + 1, "%.0fkgs" ); // Add wind animations when driving a Quadbike // By Wesser InjectHook(0x5E69BC, &CVehicle::IsOpenTopCarOrQuadbike, HookType::Call); Nop(0x5E69BC + 5, 3); // Tie handlebar movement to the stering animations on Quadbike, fixes odd animation interpolations at low speeds // By Wesser Nop(0x6B7932, 1); InjectHook(0x6B7932+1, &QuadbikeHandlebarAnims::ProcessRiderAnims_FixInterp, HookType::Call); // Disable the radio station change anim on boats where CJ stands upright // By Wesser { using namespace UprightBoatRadioStationChange; InterceptCall(0x6DF4F4, orgAnimManagerBlendAnimation, AnimManagerBlendAnimation_SkipIfBoatDrive); } // Fix a memory leak when taking photos { using namespace CameraMemoryLeakFix; InjectHook(0x7453CE, psGrabScreen_UnlockAndReleaseSurface, HookType::Jump); InjectHook(0x7453D6, psGrabScreen_UnlockAndReleaseSurface, HookType::Jump); } // Fix crosshair issues when sniper rifle is quipped and a photo is taken by a gang member // By Wesser { using namespace CameraCrosshairFix; InterceptCall(0x58E842, orgGetWeaponInfo, GetWeaponInfo_OrCamera); } // Cancel the Drive By task of biker cops when losing the wanted level { using namespace BikerCopsDriveByFix; // ModCompat::Utils::GetFunctionAddrIfRerouted won't work here, as the decrypted function is still // slightly obfuscated compared to the compact EXE deobfuscation bool HoodlumPatched = false; if (*reinterpret_cast(0x41BFA0) == 0xE9) { uintptr_t backToCruisingIfNoWantedLevel_Obfuscated; ReadCall(0x41BFA0, backToCruisingIfNoWantedLevel_Obfuscated); if (ModCompat::Utils::GetModuleHandleFromAddress(backToCruisingIfNoWantedLevel_Obfuscated) == hInstance) { VP::InterceptCall(backToCruisingIfNoWantedLevel_Obfuscated + 0x86, orgJoinCarWithRoadSystem, JoinCarWithRoadSystem_AbortDriveByTask); HoodlumPatched = true; } } if (!HoodlumPatched) { InterceptCall(0x41C00E, orgJoinCarWithRoadSystem, JoinCarWithRoadSystem_AbortDriveByTask); } } // Fix miscolored racing checkpoints if no other marker was drawn before them { using namespace RacingCheckpointsRender; InterceptCall(0x721520, orgRpClumpRender, RpClumpRender_SetLitFlag); } // Correct an improperly decrypted CPlayerPedData::operator= that broke gang recruiting after activating replays // Only broken in the HOODLUM EXE and the compact EXE that carried over the bug // By Wesser { using namespace PlayerPedDataAssignment; uintptr_t placeToPatch = ModCompat::Utils::GetFunctionAddrIfRerouted(0x45C4B0) + 0x5D; // If we're overwriting actual meaningful instructions and not NOPs, use a different wrapper if (MemEquals(placeToPatch, { 0x90, 0x90, 0x90, 0x90, 0x90 })) { InjectHook(placeToPatch, AssignmentOp_Hoodlum, HookType::Call); } else { InjectHook(placeToPatch, AssignmentOp_Compact, HookType::Call); Nop(placeToPatch + 5, 3); } } // Delay destroying of cigarettes/bottles held by NPCs so it does not potentially corrupt the moving list // CWorld::Process processes all entries in the moving list, calling ProcessControl on them. // CPlayerPed::ProcessControl handles the gang recruitment which in turn can result in homies dropping cigarettes or bottles. // When this happens, they are destroyed -immediately-. If those props are in the moving list right after the PlayerPed, // this corrupts a pre-cached node->next pointer and references an already freed entity. // To fix this, queue the entity for a delayed destruction instead of destroying immediately, // and let it destroy itself in CWorld::Process later. // or [esi+1Ch], 800h // bRemoveFromWorld // (The entity reference is already cleared for us, no need to do it) // jmp 5E03EC Patch(0x5E03D4, { 0x81, 0x4E, 0x1C, 0x00, 0x08, 0x00, 0x00, 0xEB, 0x0F }); // Spawn lapdm1 (biker cop) correctly if the script requests one with PEDTYPE_COP // By Wesser { using namespace GetCorrectPedModel_Lapdm1; Patch(0x464FC8, &BikerCop_Retail); } // Only allow impounding cars and bikes (and their subclasses), as impounding helicopters, planes, boats makes no sense { using namespace RestrictImpoundVehicleTypes; std::array isThisVehicleInteresting = { 0x566794, 0x56A378 }; HookEach_ShouldImpound(isThisVehicleInteresting, InterceptCall); } // Fix PlayerPed replay crashes // 1. Crash when starting a mocap cutscene after playing a replay wearing different clothes to the ones CJ has currently // 2. Crash when playing back a replay with a different motion group anim (fat/muscular/normal) than the current one { using namespace ReplayPlayerPedCrashFixes; InterceptCall(0x45F060, orgRestoreStuffFromMem, RestoreStuffFromMem_RebuildPlayer); bool HoodlumPatched = false; if (*reinterpret_cast(0x45CEA0) == 0xE9) { uintptr_t DealWithNewPedPacket_Obfuscated; ReadCall(0x45CEA0, DealWithNewPedPacket_Obfuscated); if (ModCompat::Utils::GetModuleHandleFromAddress(DealWithNewPedPacket_Obfuscated) == hInstance) { InterceptCall(DealWithNewPedPacket_Obfuscated + 0xF8, orgRebuildPlayer, RebuildPlayer_LoadAllMotionGroupAnims); HoodlumPatched = true; } } if (!HoodlumPatched) { InterceptCall(0x45CF87, orgRebuildPlayer, RebuildPlayer_LoadAllMotionGroupAnims); } } // Fix planes spawning in places where they crash easily { using namespace FindPlaneCreationCoorsFix; InterceptCall(0x6CD2B8, orgCheckCameraCollisionBuildings, CheckCameraCollisionBuildings_FixParams); } // Allow hovering on the Jetpack with Keyboard + Mouse controls // Does not modify any other controls, only hovering { using namespace JetpackKeyboardControlsHover; ProcessControlInput_DontHover = (void*)0x67ED33; ProcessControlInput_Hover = (void*)0x67EDAF; Nop(0x67ED2D, 1); InjectHook(0x67ED2D + 1, &ProcessControlInput_HoverWithKeyboard, HookType::Jump); ReadCall(0x67EDA6, orgGetLookBehindForCar); } // During riots, don't target the player group during missions // Fixes recruited homies panicking during Los Desperados and other riot-time missions { using namespace RiotDontTargetPlayerGroupDuringMissions; DontSkipTargetting = (void*)0x6CD54C; SkipTargetting = (void*)0x6CD7F4; InjectHook(0x6CD545, CheckIfInPlayerGroupAndOnAMission, HookType::Jump); } // Rescale light switching randomness in CVehicle::GetVehicleLightsStatus for PC the randomness range // The original randomness was 50000 out of 65535, which is impossible to hit with PC's 32767 range { static const float LightStatusRandomnessThreshold = 1.0f / 25000.0f; Patch(0x6D5612 + 2, &LightStatusRandomnessThreshold); } // Fixed vehicles exploding twice if the driver leaves the car while it's exploding { using namespace RemoveDriverStatusFix; Nop(0x6D1955, 2); InjectHook(0x6D1955 + 2, RemoveDriver_SetStatus, HookType::Call); InterceptCall(0x64C8CE, orgPrepareVehicleForPedExit, PrepareVehicleForPedExit_WreckedCheck); // CVehicle::RemoveDriver already sets the status to STATUS_ABANDONED, these are redundant Nop(0x48628D, 3); Nop(0x647E21, 3); } // Fixed falling stars rendering black { using namespace ShootingStarsFix; InterceptCall(0x714610, orgRwIm3DTransform, RwIm3DTransform_UnsetTexture); } // Enable directional lights on flying car components { using namespace LitFlyingComponents; InterceptCall(0x6A8BBE, orgWorldAdd, WorldAdd_SetLightObjectFlag); } // Fix the logic behind exploding cars losing wheels // Right now, they lose one wheel at random according to the damage manager, but they always lose the front left wheel visually. // This change matches the visuals to the physics // Also make it possible for the rear right wheel to be randomly picked { std::array spawnFlyingComponent = { 0x6B38CA, 0x6B3CCB, 0x6C6EBE, 0x6CCEF9 }; CAutomobile::HookEach_SpawnFlyingComponent(spawnFlyingComponent, InterceptCall); Nop(0x6B38E4, 5); Nop(0x6B3CF1, 5); Nop(0x6C6ED8, 5); Nop(0x6CCF17, 5); static const float fRandomness = -4.0f; Patch(0x6C25F5 + 2, &fRandomness); } // Make script randomness 16-bit, like on PS2 { using namespace Rand16bit; std::array rands = { 0x4674FE, 0x467533 }; HookEach_Rand(rands, InterceptCall); } #if FULL_PRECISION_D3D // Test - full precision D3D device Patch( 0x7F672B+1, *(uint8_t*)(0x7F672B+1) | D3DCREATE_FPU_PRESERVE ); Patch( 0x7F6751+1, *(uint8_t*)(0x7F6751+1) | D3DCREATE_FPU_PRESERVE ); Patch( 0x7F6755+1, *(uint8_t*)(0x7F6755+1) | D3DCREATE_FPU_PRESERVE ); Patch( 0x7F6759+1, *(uint8_t*)(0x7F6759+1) | D3DCREATE_FPU_PRESERVE ); #endif } void Patch_SA_11() { using namespace Memory; // IsAlreadyRunning needs to be read relatively late - the later, the better int pIsAlreadyRunning = AddressByRegion_11(0x749000); ReadCall( pIsAlreadyRunning, IsAlreadyRunning ); InjectHook(pIsAlreadyRunning, InjectDelayedPatches_11); // (Hopefully) more precise frame limiter int pAddress = AddressByRegion_11(0x7496A0); ReadCall( pAddress, RsEventHandler ); InjectHook(pAddress, NewFrameRender); InjectHook(AddressByRegion_11(0x749624), GetTimeSinceLastFrame); // Set CAEDataStream to use a NEW structure CAEDataStream::SetStructType(true); // Heli rotors InjectHook(0x6CB390, &CPlane::Render_Stub, HookType::Jump); InjectHook(0x6C4C20, &CHeli::Render_Stub, HookType::Jump); // RefFix static const float fRefZVal = 1.0f; static const float* const pRefFal = &fRefZVal; Patch(0x6FC1AA, &pRefFal); Patch(0x6FC1D0, 0); // Plane rotors InjectHook(0x4C7A01, PlaneAtomicRendererSetup, HookType::Jump); // DOUBLE_RWHEELS Patch(0x4C9490, 0xE281); Patch(0x4C9492, ~(rwMATRIXTYPEMASK|rwMATRIXINTERNALIDENTITY)); // A fix for DOUBLE_RWHEELS trailers InjectHook(0x4C9423, TrailerDoubleRWheelsFix, HookType::Jump); InjectHook(0x4C94F4, TrailerDoubleRWheelsFix2, HookType::Jump); // No framedelay Patch(0x53EDC3, 0x43EB); Patch(0x53EE3F, 0x10); Nop(0x53EE45, 1); // Disable re-initialization of DirectInput mouse device by the game Patch(0x57723C, 0xEB); Patch(0x57742A, 0xEB); Patch(0x5774FA, 0xEB); // Make sure DirectInput mouse device is set non-exclusive (may not be needed?) Patch(AddressByRegion_11(0x747270), 0x9090C030); // Hunter interior & static_rotor for helis InjectHook(0x4C7972, HunterTest, HookType::Jump); InjectHook(0x4C9818, CacheCRC32); // Lightbeam fix // Removed in Build 30 because the fix has been revisited /* Nop(0x6A36B5, 3); Patch(0x6E1793, 0x0AEB); Patch(0x6E17AC, 0x0BEB); Patch(0x6E17C5, 0x0BEB); Patch(0x6E17DF, 0x1AEB); Patch(0x6E1C05, 0x09EB); Patch(0x6E1C1D, 0x17EB); Patch(0x6E1C4F, 0x0AEB); Patch(0x6E1810, 0x28); Patch(0x6E1C5D, 0x18); Patch(0x6E180B, 0xC8-0x7C); InjectHook(0x6A3717, ResetAlphaFuncRefAfterRender, HookType::Jump); */ // PS2 SUN!!!!!!!!!!!!!!!!! Nop(0x6FB9AC, 3); // Unlocked widescreen resolutions Patch(0x74619C, 0x9090127D); Nop(0x7461A0, 2); Nop(0x746222, 2); if ( *(BYTE*)0x746333 == 0xE9 ) { // securom'd EXE // I better check if it's an address I want to patch, I don't want to break the game if ( *(DWORD*)0x14E7387 == 0x00E48C0F ) { VP::Patch(0x14E7387, 0x90905D7D); VP::Nop(0x14E738B, 2); } } else { // Sadly, this func is different in 1.01 - so I don't know the original offset } // Heap corruption fix Patch(0x4A9D50, 0xC3); // User Tracks fix ReadCall( 0x4DA057, SetVolume ); InjectHook(0x4DA057, UserTracksFix); InjectHook(0x4DA0A5, 0x4F3430); // FLAC support InjectHook(0x57C566, FLACInit); if ( *(BYTE*)0x4F3A50 == 0x6A ) { InjectHook(0x4F3A50 + 0x14D, LoadFLAC_11, HookType::Jump); InjectHook(0x4F3A50 + 0x197, CAEWaveDecoderInit); Patch(0x4F3A50 + 0x17A, 0x18EB); Patch(0x4F3650 + 0x20, UserTrackExtensions); Patch(0x4F3650 + 0x51, &UserTrackExtensions->Codec); Patch(0x4F3A10 + 0x37, &UserTrackExtensions[1].Codec); Patch(0x4F3650 + 0x3D, sizeof(UserTrackExtensions)); } else { // securom'd EXE InjectHook(0x5B6B7B, LoadFLAC_11, HookType::Jump); InjectHook(0x5B6BFB, CAEWaveDecoderInit, HookType::Jump); Patch(0x5B6BCB, 0x26EB); if ( *(DWORD*)0x14E4954 == 0x05C70A75 ) VP::Patch(0x14E4958, &UserTrackExtensions[1].Codec); // Deobfuscating an opcode Patch(0x4EBD25, 0xBF); Patch(0x4EBD26, UserTrackExtensions); Patch(0x4EBDD4, &UserTrackExtensions->Codec); Patch(0x4EBD2A, 0x72EB); Patch(0x4EBDC0, sizeof(UserTrackExtensions)); } // Impound garages working correctly InjectHook(0x4251F9, 0x448A10); InjectHook(0x4253E9, 0x448A10); InjectHook(0x425491, 0x448A10); // Impounding after busted works Nop(0x443312, 5); // Mouse rotates an airbone car only with Steer with Mouse option enabled bool* bEnableMouseSteering = *(bool**)0x6ADFCD; // CVehicle::m_bEnableMouseSteering Patch(0x6B56E0, bEnableMouseSteering); Patch(0x6CF047, bEnableMouseSteering); // Patched CAutomobile::Fix // misc_x parts don't get reset (Bandito fix), Towtruck's bouncing panel is not reset Patch(0x6A3CE9, 0x5EEB); Patch(0x6A3D75, 0x5E5FCF8B); Patch(0x6A3D79, 0x448B5B5D); Patch(0x6A3D7D, 0x89644824); Patch(0x6A3D81, 5); Patch(0x6A3D85, 0x54C48300); InjectHook(0x6A3D89, &CAutomobile::Fix_SilentPatch, HookType::Jump); // Patched CPlane::Fix // Doors don't get reset (they can't get damaged anyway), bouncing panels DO reset // but not on Vortex Patch(0x6CB3F0, 0xEB); Patch(0x6CB425, 0x5E5FCF8B); InjectHook(0x6CB429, &CPlane::Fix_SilentPatch, HookType::Jump); // Weapon icon fix (crosshairs mess up rwRENDERSTATEZWRITEENABLE) // Only 1.0 and 1.01, Steam somehow fixed it (not the same way though) Nop(0x58E9E0, 3); Nop(0x58F287, 3); Nop(0x58F2B1, 3); // CGarages::RespraysAreFree resetting on new game Patch(0x448C58, 0x8966); Patch(0x448C5A, 0x0D); Patch(0x448C5B, *(bool**)0x44AD18); Patch(0x448C5F, 0xC3); // Bilinear filtering for license plates //Patch(0x6FD528, rwFILTERLINEAR); Patch(0x6FE777, rwFILTERLINEAR); // -//- Roadsign maganer //Patch(0x6FE147, rwFILTERLINEAR); // Bilinear filtering with mipmaps for weapon icons Patch(0x58DFAA, rwFILTERMIPLINEAR); // Illumination value from timecyc.dat properly using floats Patch(0x5BC7A9, 0x14EB); // Illumination defaults to 1.0 Patch(0x5BC2E4, 0xCC2484C7); Patch(0x5BC2E8, 0x00000000); Patch(0x5BC2EC, 0x903F8000); // All lights get casted at vehicles Patch(0x5DA297, 8); Patch(0x5DA2A0, 8); Patch(0x5DA73F, 8); // 6 extra directionals on Medium and higher // push eax // call GetMaxExtraDirectionals // add esp, 4 // mov ebx, eax // nop Patch( 0x7360B1, 0x50 ); InjectHook( 0x7360B1 + 1, GetMaxExtraDirectionals, HookType::Call ); Patch( 0x7360B1 + 6, { 0x83, 0xC4, 0x04, 0x8B, 0xD8 } ); Nop( 0x7360B1 + 11, 3 ); // Default resolution to native resolution const auto [width, height] = GetDesktopResolution(); sprintf_s(aNoDesktopMode, "Cannot find %ux%ux32 video mode", width, height); if (width != 0 && height != 0) { Patch(0x746BE3, width); Patch(0x746BE8, height); Patch(0x746C48, aNoDesktopMode); } // Corrected Map screen 1px issue Patch(0x576357, -0.5f); Patch(0x576317, -0.5f); Patch(0x57631F, -0.5f); Patch(0x5762CC, -0.5f); Patch(0x57624A, -0.5f); Patch(0x57627C, -0.5f); // Cars drive on water cheat Patch(&(*(DWORD**)0x438593)[34], 0xE5FC92C3); // No DirectPlay dependency Patch(AddressByRegion_11(0x747E1A), 0xB8); Patch(AddressByRegion_11(0x747E1B), 0x900); // SHGetFolderPath on User Files InjectHook(0x7457E0, GetMyDocumentsPathSA, HookType::Jump); // Fixed muzzleflash not showing from last bullet // nop \ test al, al \ jz Nop(0x61F4FC, 6); Patch(0x61F502, { 0x84, 0xC0, 0x74 }); // Proper randomizations InjectHook(0x44E8AE, Int32Rand); // Missing ped paths InjectHook(0x44ED6E, Int32Rand); // Missing ped paths InjectHook(0x6676C0, Int32Rand); // Prostitutes // Help boxes showing with big message // Game seems to assume they can show together Nop(0x58C25F, 6); // Fixed lens flare Patch(0x70FC8A, 0); Patch(0x6FBE51, 0xC3); Patch(0x6FBE30, 0x21); InjectHook(0x6FBE52, 0x70D750, HookType::Call); Patch(0x6FBE57, 0xDCEB); Patch(0x6FBCA6, 0xB990); Patch(0x6FBCA8, &FlushLensSwitchZ); Patch(0x6FBCB0, 0xD1FF); Nop(0x6FBCB2, 1); Patch(0x6FB758, 0xB990); Patch(0x6FB75A, &InitBufferSwitchZ); Patch(0x6FB762, 0xD1FF); Nop(0x6FB764, 1); // Y axis sensitivity fix float* sens = *(float**)0x50F4DC; Patch(0x50F4E6 + 0x2, sens); Patch(0x50FFC6 + 0x2, sens); Patch(0x5110C6 + 0x2, sens); Patch(0x5122A8 + 0x2, sens); Patch(0x52272C + 0x2, sens); // Don't lock mouse Y axis during fadeins Patch(0x510054, 0x27EB); Patch(0x5109B2, 0xE990); InjectHook(0x524511, 0x5245D9, HookType::Jump); // Fixed mirrors crash Patch( 0x7279FB, { 0x85, 0xC0, 0x74, 0x34, 0x83, 0xC4, 0x04 } ); // Mirrors depth fix & bumped quality InjectHook(0x72784D, CreateMirrorBuffers); // Fixed MSAA options { using namespace MSAAFixes; Patch(0x57D906, 0xEB); Nop(0x57D8C8, 2); Patch(AddressByRegion_11(0x7F759B), 0xEB); Patch(AddressByRegion_11(0x7F69C6), 0xEB); Patch(AddressByRegion_11(0x7F6F83), { 0x90, 0xE9 }); std::array getMaxMultiSamplingLevels = { 0x57D916, 0x57D8CA }; HookEach_GetMaxMultiSamplingLevels(getMaxMultiSamplingLevels, InterceptCall); std::array setOrChangeMultiSamplingLevels = { 0x574A6D, 0x57D942, 0x57DA86, 0x746BD0 }; HookEach_SetOrChangeMultiSamplingLevels(setOrChangeMultiSamplingLevels, InterceptCall); Nop(0x57A66C, 1); InjectHook(0x57A66D, MSAAText, HookType::Call); } // Fixed car collisions - car you're hitting gets proper damage now InjectHook(0x542D8A, FixedCarDamage, HookType::Call); // Car explosion crash with multimonitor // Unitialized collision data breaking stencil shadows // FUCK THIS IN 1.01 // Fixed escalators crash // FUCK THIS IN 1.01 // Don't allocate constant memory for stencil shadows every frame // FUCK THIS IN 1.01 // Fixed police scanner names char* pScannerNames = *(char**)0x4E7714; strcpy_s(pScannerNames + (8*113), 8, "WESTP"); strcpy_s(pScannerNames + (8*134), 8, "????"); // 1.01 ONLY // I'm not sure what was this new audio code supposed to do, but it leaks memory // and due to this I have to make extra effort if I want FLAC to work on 1.01 Patch(0x4E124C, 0x4DEBC78B); } void Patch_SA_Steam() { using namespace Memory; // IsAlreadyRunning needs to be read relatively late - the later, the better ReadCall( 0x7826ED, IsAlreadyRunning ); InjectHook(0x7826ED, InjectDelayedPatches_Steam); // (Hopefully) more precise frame limiter ReadCall( 0x782D25, RsEventHandler ); InjectHook(0x782D25, NewFrameRender); InjectHook(0x782CA8, GetTimeSinceLastFrame); // Set CAEDataStream to use an old structure CAEDataStream::SetStructType(false); // Heli rotors InjectHook(0x700620, &CPlane::Render_Stub, HookType::Jump); InjectHook(0x6F9550, &CHeli::Render_Stub, HookType::Jump); // RefFix static const float fRefZVal = 1.0f; static const float* const pRefFal = &fRefZVal; Patch(0x733FF0, &pRefFal); Patch(0x73401A, 0); // Plane rotors InjectHook(0x4D2270, PlaneAtomicRendererSetup, HookType::Jump); // DOUBLE_RWHEELS Patch(0x4D3B9D, 0x6781); Patch(0x4D3BA0, ~(rwMATRIXTYPEMASK|rwMATRIXINTERNALIDENTITY)); // A fix for DOUBLE_RWHEELS trailers InjectHook(0x4D3B47, TrailerDoubleRWheelsFix_Steam, HookType::Jump); InjectHook(0x4D3C1A, TrailerDoubleRWheelsFix2_Steam, HookType::Jump); // No framedelay Patch(0x551113, 0x46EB); Patch(0x551195, 0xC); Nop(0x551197, 1); // Disable re-initialization of DirectInput mouse device by the game Patch(0x58C0E5, 0xEB); Patch(0x58C2CF, 0xEB); Patch(0x58C3B3, 0xEB); // Make sure DirectInput mouse device is set non-exclusive (may not be needed?) Patch(0x7807D0, 0x9090C030); // Hunter interior & static_rotor for helis InjectHook(0x4D21E1, HunterTest, HookType::Jump); InjectHook(0x4D3F1D, CacheCRC32); // Bindable NUM5 // Only 1.0 and Steam Nop(0x59363B, 2); // Lightbeam fix // Removed in Build 30 because the fix has been revisited /* Patch(0x6CFEF9, 0x10EB); Nop(0x6CFF0F, 3); Patch(0x71D1F5, 0x0DEB); Patch(0x71D213, 0x0CEB); Patch(0x71D230, 0x0DEB); Patch(0x71D24D, 0x1FEB); Patch(0x71D72F, 0x0BEB); Patch(0x71D74B, 0x1BEB); Patch(0x71D785, 0x0CEB); Patch(0x71D284, 0x28); Patch(0x71D795, 0x18); Patch(0x71D27F, 0xD0-0x9C); //InjectHook(0x6A2EDA, CullTest); InjectHook(0x6CFF69, ResetAlphaFuncRefAfterRender_Steam, HookType::Jump); */ // PS2 SUN!!!!!!!!!!!!!!!!! Nop(0x73362F, 2); // Unlocked widescreen resolutions //Patch(0x77F9F0, 0x6E7D); Patch(0x77F9FC, 0x627D); Patch(0x77F80B, 0x9090127D); Nop(0x77F80F, 2); Nop(0x77F880, 2); // Heap corruption fix Nop(0x5D88AE, 5); // User Tracks fix SetVolume = reinterpret_cast(0x4E2750); Patch(0x4E4A28, 0xBA); Patch(0x4E4A29, UserTracksFix_Steam); InjectHook(0x4E4A8B, 0x4FF2B0); // FLAC support InjectHook(0x4FFC39, LoadFLAC_Steam, HookType::Jump); InjectHook(0x591814, FLACInit_Steam); InjectHook(0x4FFC83, CAEWaveDecoderInit); Patch(0x4FFC66, 0x18EB); Patch(0x4FF4F0, UserTrackExtensions); Patch(0x4FF523, &UserTrackExtensions->Codec); Patch(0x4FFAB6, &UserTrackExtensions[1].Codec); Patch(0x4FF50F, sizeof(UserTrackExtensions)); // Impound garages working correctly InjectHook(0x426B48, 0x44C950); InjectHook(0x426D16, 0x44C950); InjectHook(0x426DC5, 0x44C950); // Impounding after busted works Nop(0x446F58, 5); // Mouse rotates an airbone car only with Steer with Mouse option enabled bool* bEnableMouseSteering = *(bool**)0x6DB76D; // CVehicle::m_bEnableMouseSteering Patch(0x6E3199, bEnableMouseSteering); Patch(0x7046AB, bEnableMouseSteering); // Patched CAutomobile::Fix // misc_x parts don't get reset (Bandito fix), Towtruck's bouncing panel is not reset Patch(0x6D05B3, 0x6BEBED31); Patch(0x6D0649, 0x5E5FCF8B); Patch(0x6D064D, 0x448B5B5D); Patch(0x6D0651, 0x89644824); Patch(0x6D0655, 5); Patch(0x6D0659, 0x54C48300); InjectHook(0x6D065D, &CAutomobile::Fix_SilentPatch, HookType::Jump); // Patched CPlane::Fix // Doors don't get reset (they can't get damaged anyway), bouncing panels DO reset // but not on Vortex Patch(0x700681, 0xEB); Patch(0x7006B6, 0x5E5FCF8B); InjectHook(0x7006BA, &CPlane::Fix_SilentPatch, HookType::Jump); // Zones fix InjectHook(0x587080, GetCurrentZoneLockedOrUnlocked_Steam, HookType::Jump); // CGarages::RespraysAreFree resetting on new game Patch(0x44CB55, 0xC766); Patch(0x44CB57, 0x05); Patch(0x44CB58, *(bool**)0x44EEBA); Patch(0x44CB5C, 0x0000); // Bilinear filtering for license plates //Patch(0x6FD528, rwFILTERLINEAR); Patch(0x736B30, rwFILTERLINEAR); // -//- Roadsign maganer //Patch(0x6FE147, rwFILTERLINEAR); // Bilinear filtering with mipmaps for weapon icons Patch(0x59BD9C, rwFILTERMIPLINEAR); // Illumination value from timecyc.dat properly using floats Patch(0x5DAF6B, 0x2CEB); // Illumination defaults to 1.0 Patch(0x5DA8D4, 0xD82484C7); Patch(0x5DA8D8, 0x00000000); Patch(0x5DA8DC, 0x903F8000); // All lights get casted at vehicles Patch(0x5F61C7, 8); Patch(0x5F61D0, 8); Patch(0x5F666D, 8); // 6 extra directionals on Medium and higher // push dword ptr [CGame::currArea] // call GetMaxExtraDirectionals // add esp, 4 // mov ebx, eax // nop Patch( 0x768046, { 0xFF, 0x35 } ); InjectHook( 0x768046 + 6, GetMaxExtraDirectionals, HookType::Call ); Patch( 0x768046 + 11, { 0x83, 0xC4, 0x04, 0x8B, 0xD8 } ); Nop( 0x768046 + 16, 1 ); // Default resolution to native resolution const auto [width, height] = GetDesktopResolution(); sprintf_s(aNoDesktopMode, "Cannot find %ux%ux32 video mode", width, height); if (width != 0 && height != 0) { Patch(0x780219, width); Patch(0x78021E, height); Patch(0x78027E, aNoDesktopMode); } // Corrected Map screen 1px issue /*Patch(0x575DE7, -5.0f); Patch(0x575DA7, -5.0f); Patch(0x575DAF, -5.0f); Patch(0x575D5C, -5.0f); Patch(0x575CDA, -5.0f); Patch(0x575D0C, -5.0f);*/ InjectHook(0x58B0F8, DrawRect_HalfPixel_Steam); InjectHook(0x58B146, DrawRect_HalfPixel_Steam); InjectHook(0x58B193, DrawRect_HalfPixel_Steam); InjectHook(0x58B1E1, DrawRect_HalfPixel_Steam); // Cars drive on water cheat Patch(&(*(DWORD**)0x43B793)[34], 0xE5FC92C3); // No DirectPlay dependency Patch(0x781456, 0xB8); Patch(0x781457, 0x900); // SHGetFolderPath on User Files InjectHook(0x77EDC0, GetMyDocumentsPathSA, HookType::Jump); // Fixed muzzleflash not showing from last bullet // REMOVED - the fix pointed at some unrelated instruction anyway? I think it never worked // Proper randomizations InjectHook(0x452CCF, Int32Rand); // Missing ped paths InjectHook(0x45322C, Int32Rand); // Missing ped paths InjectHook(0x690263, Int32Rand); // Prostitutes // Help boxes showing with big message // Game seems to assume they can show together Nop(0x599CD3, 6); // Fixed lens flare Nop(0x733C65, 5); Patch(0x733C4E, 0x26); InjectHook(0x733C75, 0x7591E0, HookType::Call); Patch(0x733C7A, 0xDBEB); Nop(0x733A5A, 4); Patch(0x733A5E, 0xB8); Patch(0x733A5F, &FlushLensSwitchZ); Patch(0x7333B0, 0xB9909090); Patch(0x7333B4, &InitBufferSwitchZ); // Y axis sensitivity fix float* sens = *(float**)0x51D4FA; Patch(0x51D508 + 0x2, sens); Patch(0x51E25A + 0x2, sens); Patch(0x51F459 + 0x2, sens); Patch(0x52086A + 0x2, sens); Patch(0x532B9B + 0x2, sens); // Don't lock mouse Y axis during fadeins Patch(0x51E192, 0x2BEB); Patch(0x51ED38, 0xE990); InjectHook(0x534D3E, 0x534DF7, HookType::Jump); // Fixed mirrors crash Patch( 0x75903A, { 0x85, 0xC0, 0x74, 0x34, 0x83, 0xC4, 0x04 } ); // Mirrors depth fix & bumped quality InjectHook(0x758E91, CreateMirrorBuffers); // Fixed MSAA options { using namespace MSAAFixes; Patch(0x592BBB, 0xEB); Nop(0x592B7F, 2); Patch(0x830C5B, 0xEB); Patch(0x830086, 0xEB); Patch(0x830643, { 0x90, 0xE9 }); std::array getMaxMultiSamplingLevels = { 0x592BCF, 0x592B81 }; HookEach_GetMaxMultiSamplingLevels(getMaxMultiSamplingLevels, InterceptCall); std::array setOrChangeMultiSamplingLevels = { 0x5897CD, 0x592BFB, 0x592D2E, 0x780206 }; HookEach_SetOrChangeMultiSamplingLevels(setOrChangeMultiSamplingLevels, InterceptCall); Patch(0x58F88C, { 0x90, 0xBA }); Patch(0x58F88E, MSAAText); } // Fixed car collisions - car you're hitting gets proper damage now Nop(0x555AB8, 2); InjectHook(0x555AC0, FixedCarDamage_Steam, HookType::Call); // Car explosion crash with multimonitor // Unitialized collision data breaking stencil shadows { using namespace UnitializedCollisionDataFix; InterceptCall(0x41A216, orgMemMgrMalloc, CollisionData_MallocAndInit); std::array newAndInit = { 0x41A07C, 0x41A159 }; HookEach_CollisionDataNew(newAndInit, InterceptCall); } // Crash when entering advanced display options on a dual monitor machine after: // - starting game on primary monitor in maximum resolution, exiting, // starting again in maximum resolution on secondary monitor. // Secondary monitor maximum resolution had to be greater than maximum resolution of primary monitor. // Not in 1.01 ReadCall( 0x77F99E, orgGetNumVideoModes ); InjectHook(0x77F99E, GetNumVideoModes_Store); InjectHook(0x77F901, GetNumVideoModes_Retrieve); // Fixed escalators crash ReadCall( 0x739975, orgEscalatorsUpdate ); InjectHook(0x739975, UpdateEscalators); InjectHook(0x738BBD, &CEscalator::SwitchOffNoRemove); // Don't allocate constant memory for stencil shadows every frame InjectHook(0x760795, StencilShadowAlloc, HookType::Call); Nop(0x7607CD, 3); Patch(0x76079A, { 0xEB, 0x2C }); Patch(0x76082C, { 0x5F, 0x5D, 0xC3 }); // pop edi, pop ebp, ret // "Streaming memory bug" fix InjectHook(0x4CF9E8, GTARtAnimInterpolatorSetCurrentAnim); // Fixed ammo for melee weapons in cheats Patch(0x43BB8B+1, 1); // knife Patch(0x43BC78+1, 1); // knife Patch(0x43BE1F+1, 1); // chainsaw Patch(0x43BED8+1, 1); // chainsaw Patch(0x43C868+1, 1); // parachute Patch(0x43D24C, 0x53); // katana Patch(0x43D24D, 0x016A); // AI accuracy issue Nop(0x7738F5, 1); InjectHook( 0x7738F5+1, WeaponRangeMult_VehicleCheck, HookType::Call ); // Don't catch WM_SYSKEYDOWN and WM_SYSKEYUP (fixes Alt+F4) InjectHook( 0x7821E5, 0x7823FE, HookType::Jump ); Patch( 0x7821A7 + 1, 0x5C ); // esi -> ebx Patch( 0x7821AF, 0x53 ); // esi -> ebx Patch( 0x7821D1 + 1, 0xFB ); // esi -> ebx Patch( 0x7821B1 + 3, 0x54-0x2C ); // use stack space for new lParam Patch( 0x7821C2 + 3, 0x4C-0x2C ); // use stack space for new lParam Patch( 0x7821D6 + 3, 0x4C-0x2C ); // use stack space for new lParam InjectHook( 0x78222F, 0x7823FE, HookType::Jump ); Patch( 0x7821F1 + 1, 0x5C ); // esi -> ebx Patch( 0x7821F9, 0x53 ); // esi -> ebx Patch( 0x78221B + 1, 0xFB ); // esi -> ebx Patch( 0x7821FB + 3, 0x54-0x2C ); // use stack space for new lParam Patch( 0x78220C + 3, 0x4C-0x2C ); // use stack space for new lParam Patch( 0x782220 + 3, 0x4C-0x2C ); // use stack space for new lParam // FuckCarCompletely not fixing panels Nop(0x6F5EC1, 3); // 014C cargen counter fix (by spaceeinstein) Patch( 0x6F566D + 1, 0xBF ); // movzx eax, word ptr [ebp+1Ah] -> movsx eax, word ptr [ebp+1Ah] Patch( 0x6F567E + 1, 0xBF ); // movzx ecx, ax -> movsx ecx, ax Patch( 0x6F3E32, 0x74 ); // jge -> jz // Linear filtering on script sprites ReadCall( 0x59A3F2, orgDrawScriptSpritesAndRectangles ); InjectHook( 0x59A3F2, DrawScriptSpritesAndRectangles ); // Fixed police scanner names char* pScannerNames = *(char**)0x4F2B83; strcpy_s(pScannerNames + (8*113), 8, "WESTP"); strcpy_s(pScannerNames + (8*134), 8, "????"); // STEAM ONLY // Proper aspect ratios - why Rockstar, why? // Steam aspect ratios were additionally divided by 1.1, producing a squashed image static const float f43 = 4.0f/3.0f, f54 = 5.0f/4.0f, f169 = 16.0f/9.0f; Patch(0x73822B, &f169); Patch(0x738247, &f54); Patch(0x73825A, &f43); // No IMG size check Nop(0x406CD0, 7); Nop(0x406D00, 7); // Unlock 1.0/1.01 saves loading InjectHook(0x5EDFD9, 0x5EE0FA, HookType::Jump); } void Patch_SA_NewBinaries_Common(HINSTANCE hInstance) { using namespace Memory; using namespace hook; { void* isAlreadyRunning = get_pattern( "85 C0 74 08 33 C0 8B E5 5D C2 10 00", -5 ); ReadCall( isAlreadyRunning, IsAlreadyRunning ); InjectHook(isAlreadyRunning, InjectDelayedPatches_Newsteam); } // (Hopefully) more precise frame limiter { void* rsEventHandler = get_pattern( "83 C4 08 39 3D ? ? ? ? 75 23", -5 ); void* getTimeSinceLastFrame = get_pattern( "EB 7F E8 ? ? ? ? 89 45 08", 2 ); ReadCall( rsEventHandler, RsEventHandler ); InjectHook( rsEventHandler, NewFrameRender ); InjectHook( getTimeSinceLastFrame, GetTimeSinceLastFrame ); } // No framedelay { // TODO: Simplify with transactional patching auto framedelay_jmpSrc = pattern( "83 EC 08 E8 ? ? ? ? E8" ).count(1); auto framedelay_jmpDest = pattern( "33 D2 8B C6 F7 F1 A3" ).count(1); auto popEsi = pattern( "83 C4 04 83 7D 08 00 5E" ).count(1); if ( framedelay_jmpSrc.size() == 1 && framedelay_jmpDest.size() == 1 && popEsi.size() == 1 ) { // TODO: Let this place long or short jump, whatever it prefers InjectHook( framedelay_jmpSrc.get_first( 3 ), framedelay_jmpDest.get_first( 11 ), HookType::Jump ); auto popEsiMatch = popEsi.get_one(); Patch( popEsiMatch.get( 3 + 2 ), 0x4); Nop( popEsiMatch.get( 3 + 4 ), 1 ); } } // Unlock 1.0/1.01 saves loading { auto sizeCheck = pattern( "0F 84 ? ? ? ? 8D 45 FC" ).count(1); if ( sizeCheck.size() == 1 ) { Patch( sizeCheck.get_first(), { 0x90, 0xE9 } ); // nop / jmp } } // Old .set files working again { void* setFileSave = get_pattern( "C6 45 FD 5F", 0xE + 1 ); auto setCheckVersion1 = pattern( "83 7D F8 07" ).count(1); auto setCheckVersion2 = pattern( "83 C4 18 83 7D FC 07" ).count(1); if ( setCheckVersion1.size() == 1 && setCheckVersion2.size() == 1 ) { static const DWORD dwSetVersion = 6; Patch( setFileSave, &dwSetVersion ); Patch( setCheckVersion1.get_first( 3 ), dwSetVersion ); Patch( setCheckVersion2.get_first( 3 + 3 ), dwSetVersion ); } } // Disable re-initialization of DirectInput mouse device by the game { void* reinitMouse1 = get_pattern( "84 C0 ? 0F E8 ? ? ? ? 6A 01 E8", 2 ); auto reinitMouse2 = pattern( "84 C0 ? 0E E8 ? ? ? ? 53 E8" ).count(2); Patch( reinitMouse1, 0xEB ); reinitMouse2.for_each_result( []( pattern_match match ) { Patch( match.get( 2 ), 0xEB ); }); void* diInitMouse = get_pattern( "6A 00 83 C1 1C", -3 ); // Make sure DirectInput mouse device is set non-exclusive (may not be needed?) // nop / mov al, 1 Patch( diInitMouse, { 0x90, 0xB0, 0x01 } ); } // Unlocked widescreen resolutions { // Assume anybody could have changed those, so bail out if ANYTHING goes wrong // However, all those patches are independent so try one by one auto wsRes_jmpSrc = pattern( "81 F9 E0 01 00 00 7C").count(1); auto wsRes1_jmpDest = pattern( "8B 45 EC 0F AF C2" ).count(1); auto wsRes2 = pattern( "0F 8C ? ? ? ? 81 7D ? ? ? ? ? 0F 8C" ).count(1); auto wsRes3 = pattern( "7A 4D EB 02" ).count(1); if ( wsRes_jmpSrc.size() == 1 && wsRes1_jmpDest.size() == 1 ) { auto wsRes_jmpSrcMatch = wsRes_jmpSrc.get_one(); auto wsRes1_jmpDestMatch = wsRes1_jmpDest.get_one(); const uintptr_t jumpSource = reinterpret_cast(wsRes_jmpSrcMatch.get( 6 + 2 )); const uintptr_t jumpDestination = reinterpret_cast(wsRes1_jmpDestMatch.get() ); const ptrdiff_t dist = jumpDestination - jumpSource; // Can only do a short jump if ( INT8_MIN <= dist && dist <= INT8_MAX ) { // jnl 00B19C33 Patch( wsRes_jmpSrcMatch.get( 6 ), { 0x7D, static_cast(dist) } ); } } if ( wsRes2.size() == 1 ) { auto wsRes2Match = wsRes2.get_one(); Nop( wsRes2Match.get(), 4 ); Patch( wsRes2Match.get( 4 ), { 0x7D, 0xD } ); } if ( wsRes3.size() == 1 ) { Nop( wsRes3.get_first(), 2 ); } } // Default resolution to native resolution { auto resolution = pattern( "BB 20 03 00 00" ).count(1); // Another instance could overwrite it so check first if ( resolution.size() == 1 ) { void* cannotFindResMessage = get_pattern( "6A 00 68 ? ? ? ? 68 ? ? ? ? 6A 00", 7 + 1 ); RECT desktop; GetWindowRect(GetDesktopWindow(), &desktop); sprintf_s(aNoDesktopMode, "Cannot find %dx%dx32 video mode", desktop.right, desktop.bottom); auto resolutionMatch = resolution.get_one(); Patch( resolutionMatch.get( 1 ), desktop.right ); Patch( resolutionMatch.get( 5 + 1 ), desktop.bottom ); Patch( cannotFindResMessage, aNoDesktopMode ); } } // No DirectPlay dependency { auto getDXversion = pattern( "50 68 ? ? ? ? A3" ).get_one(); // mov eax, 0x900 Patch( getDXversion.get( -5 ), 0xB8 ); Patch( getDXversion.get( -5 + 1 ), 0x900 ); } // SHGetFolderPath on User Files { void* getDocumentsPath = get_pattern( "8D 45 FC 50 68 19 00 02 00", -6 ); InjectHook( getDocumentsPath, GetMyDocumentsPathSA, HookType::Jump ); } // Fixed muzzleflash not showing from last bullet { auto weaponStateCheck = pattern("83 BC 8E A4 05 00 00 01").get_one(); Nop(weaponStateCheck.get(-16), 22); Patch(weaponStateCheck.get(6), { 0x84, 0xC0, 0x74 }); } // Proper randomizations { pattern( "C1 F8 06 99" ).count(2).for_each_result( []( pattern_match match ) { InjectHook( match.get( -5 ), Int32Rand ); // Missing ped paths }); void* prostitutesRand = get_pattern( "8B F8 32 C0", -5 ); InjectHook( prostitutesRand, Int32Rand ); // Prostitutes } // Help boxes showing with big message // Game seems to assume they can show together { void* showingBigMessage = get_pattern( "38 1D ? ? ? ? 0F 85 ? ? ? ? 38 1D ? ? ? ? 0F 85 ? ? ? ? 38 1D", 6 ); Nop( showingBigMessage, 6 ); } // Fixed lens flare { auto coronasRenderEpilogue = pattern( "83 C7 3C FF 4D BC" ).get_one(); auto flushLensSwitchZ = pattern( "6A 01 6A 06 FF D0 83 C4 08" ).get_one(); auto initBufferSwitchZ = pattern( "6A 01 6A 06 FF D1 8B 75 A8" ).get_one(); // TODO: This will break badly if applied multiple times, think of something! void* flushSpriteBuffer; ReadCall( coronasRenderEpilogue.get( 0xC ), flushSpriteBuffer ); Nop( coronasRenderEpilogue.get( 0xC ), 5); // nop CSprite::FlushSpriteBuffer // Add CSprite::FlushSpriteBuffer, jmp loc_7300EC at the bottom of the function Patch( coronasRenderEpilogue.get( -0xA + 1 ), 0x20 ); InjectHook( coronasRenderEpilogue.get( 0x18 ), flushSpriteBuffer, HookType::Call ); // TODO: Short jumps Patch( coronasRenderEpilogue.get( 0x18 + 5 ), { 0xEB, 0xE1 }); // nop / mov eax, offset FlushLensSwitchZ Nop( flushLensSwitchZ.get( -9 ), 4 ); Patch( flushLensSwitchZ.get( -9 + 4 ), 0xB8 ); Patch( flushLensSwitchZ.get( -9 + 5 ), &FlushLensSwitchZ ); // nop / mov ecx, offset InitBufferSwitchZ Nop( initBufferSwitchZ.get( -8 ), 3 ); Patch( initBufferSwitchZ.get( -8 + 3 ), 0xB9 ); Patch( initBufferSwitchZ.get( -8 + 4 ), &InitBufferSwitchZ ); } // Y axis sensitivity fix { auto horizontalSens = pattern( "D9 05 ? ? ? ? D8 4D 0C D8 C9" ).get_one(); float* sens = *horizontalSens.get( 2 ); // Build a pattern for finding all instances of fld CCamera::m_fMouseAccelVertical const uint8_t mask[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; uint8_t bytes[6] = { 0xD9, 0x05 }; float* vertSens = *horizontalSens.get( 0xE + 2 ); memcpy( bytes + 2, &vertSens, sizeof(vertSens) ); pattern( {bytes, _countof(bytes)}, {mask, _countof(mask)} ).count(4).for_each_result( [sens]( pattern_match match ) { Patch( match.get( 2 ), sens ); } ); void* mulVerticalSens = get_pattern( "D8 0D ? ? ? ? D8 C9 D9 5D F4", 2 ); Patch( mulVerticalSens, sens ); } // Don't lock mouse Y axis during fadeins { void* followPedWithMouse = get_pattern( "D9 5D 08 A0", -7 ); void* followPedWithMouse2 = get_pattern( "66 83 3D ? ? ? ? ? 0F 85 ? ? ? ? 80 3D", -6 ); void* followPedSA = get_pattern( "D9 5D F0 74 14", 3 ); void* folowPedSA_dest = get_pattern( "D9 87 AC 00 00 00 D8 45 F0" ); // TODO: Change when short jumps are supported Patch( followPedWithMouse, { 0xEB, 0x29 } ); Patch( followPedWithMouse2, { 0x90, 0xE9 } ); InjectHook( followPedSA, folowPedSA_dest, HookType::Jump ); } // Fixed mirrors crash { // TODO: Change when short jumps are supported void* beforeMainRender = get_pattern( "8B 15 ? ? ? ? 83 C4 0C 52", 0xF ); Patch( beforeMainRender, { 0x85, 0xC0, 0x74, 0x34, 0x83, 0xC4, 0x04 } ); } // Mirrors depth fix & bumped quality { void* createBuffers = get_pattern( "7B 0A C7 05 ? ? ? ? 01 00 00 00", 0xC ); InjectHook( createBuffers, CreateMirrorBuffers ); } // Fixed MSAA options { using namespace MSAAFixes; // TODO: Remove wildcards in patterns once transactional patching is implemented auto func1 = pattern( "83 BE C8 00 00 00 04 ? ? E8" ).get_one(); void* func2 = get_pattern( "05 A3 ? ? ? ? 59", -1 ); void* func3 = get_pattern( "05 A3 ? ? ? ? 8B C7", -1 ); void* func4 = get_pattern( "0F 8C ? ? ? ? 8B 44 24 0C", 0x18 ); Patch( func1.get( 0x4A ), 0xEB ); // jmp Nop( func1.get( 7 ), 2 ); // nop a jmp Patch(func2, 0xEB); // jmp Patch(func3, 0xEB); // jmp Patch(func4, { 0x90, 0xE9 }); // jmp auto getMaxMultisamplingLevels = pattern( "5F 89 86 C8 00 00 00 8A 45 FF" ).get_one(); void* changeMultiSamplingLevels = get_pattern( "8B 8E D0 00 00 00 51", -5 ); void* changeMultiSamplingLevels2 = get_pattern( "8B 96 D0 00 00 00 52", -5 ); void* setMultiSamplingLevels = get_pattern( "83 C4 04 8B C7 5F 5E 5B 8B E5 5D C3 BB", -5 ); std::array getMaxMultiSamplingLevels = { getMaxMultisamplingLevels.get( -5 ), func1.get( 7 + 2 ), }; HookEach_GetMaxMultiSamplingLevels(getMaxMultiSamplingLevels, InterceptCall); std::array setOrChangeMultiSamplingLevels = { changeMultiSamplingLevels, getMaxMultisamplingLevels.get( -5 + 0x30 ), changeMultiSamplingLevels2, setMultiSamplingLevels }; HookEach_SetOrChangeMultiSamplingLevels(setOrChangeMultiSamplingLevels, InterceptCall); auto msaaText = pattern( "48 50 68 ? ? ? ? 53" ).count(1); // Only so newsteam r1 doesn't crash if ( msaaText.size() == 1 ) // transactional patching will obsolete this { auto msaaTextMatch = msaaText.get_one(); // nop / mov edx, offset MSAAText Patch( msaaTextMatch.get( -6 ), { 0x90, 0xBA } ); Patch( msaaTextMatch.get( -6 + 2 ), MSAAText ); } } // Fixed car collisions - car you're hitting gets proper damage now { auto fixedCarDamage = pattern( "8B 7D 10 0F B6 47 21" ).get_one(); Nop( fixedCarDamage.get(), 2 ); InjectHook( fixedCarDamage.get( 2 ), FixedCarDamage_Newsteam, HookType::Call ); } // Car explosion crash with multimonitor // Unitialized collision data breaking stencil shadows { using namespace UnitializedCollisionDataFix; void* memMgrAlloc = get_pattern( "E8 ? ? ? ? 66 8B 55 08 8B 4D 10" ); std::array newAlloc = { get_pattern( "33 C9 83 C4 04 3B C1 74 36", -5 ), get_pattern( "33 C9 83 C4 04 3B C1 74 37", -5 ), }; InterceptCall(memMgrAlloc, orgMemMgrMalloc, CollisionData_MallocAndInit); HookEach_CollisionDataNew(newAlloc, InterceptCall); } // Crash when entering advanced display options on a dual monitor machine after: // - starting game on primary monitor in maximum resolution, exiting, // starting again in maximum resolution on secondary monitor. // Secondary monitor maximum resolution had to be greater than maximum resolution of primary monitor. // Not in 1.01 { void* storeVideoModes = get_pattern( "6A 00 8B F8 6A 04", -5 ); void* retrieveVideoModes = get_pattern( "57 E8 ? ? ? ? 83 3D", 1 ); ReadCall( storeVideoModes, orgGetNumVideoModes ); InjectHook( storeVideoModes, GetNumVideoModes_Store ); InjectHook( retrieveVideoModes, GetNumVideoModes_Retrieve ); } // Fixed escalators crash { orgEscalatorsUpdate = static_cast(get_pattern( "80 3D ? ? ? ? ? 74 23 56" )); // TODO: Simplify when transactional patching is implemented auto updateEscalators = pattern( "80 3D ? ? ? ? ? 74 22 56" ).count(1); auto removeEscalatorsForEntity = pattern( "80 7E F5 00 74 56" ).count(1); if ( updateEscalators.size() == 1 && removeEscalatorsForEntity.size() == 1 ) { InjectHook( updateEscalators.get_first(), UpdateEscalators, HookType::Jump ); // lea ecx, [esi-84] / call CEscalator::SwitchOffNoRemove / jmp loc_734C0A auto removeEscalatorsMatch = removeEscalatorsForEntity.get_one(); // TODO: Change when short jmps are supported Patch( removeEscalatorsMatch.get(), { 0x8D, 0x8E } ); Patch( removeEscalatorsMatch.get( 2 ), -0x84 ); InjectHook( removeEscalatorsMatch.get( 6 ), &CEscalator::SwitchOffNoRemove, HookType::Call ); Patch( removeEscalatorsMatch.get( 6 + 5 ), { 0xEB, 0x4F } ); } } // Don't allocate constant memory for stencil shadows every frame { // TODO: Simplify when transactional patching is implemented auto shadowAlloc = pattern( "83 C4 08 6A 00 68 00 60 00 00" ).count(1); auto shadowFree = pattern( "A2 ? ? ? ? A1 ? ? ? ? 50 E8" ).count(1); if ( shadowAlloc.size() == 1 && shadowFree.size() == 1 ) { auto allocMatch = shadowAlloc.get_one(); InjectHook( allocMatch.get( 3 ), StencilShadowAlloc, HookType::Call) ; Patch( allocMatch.get( 3 + 5 ), { 0xEB, 0x2C } ); Nop( allocMatch.get( 0x3B ), 3 ); Patch( shadowFree.get_first( 5 ), { 0x5F, 0x5E, 0x5B, 0x5D, 0xC3 } ); // pop edi, pop esi, pop ebx, pop ebp, retn } } // "Streaming memory bug" fix { void* animInterpolator = get_pattern( "83 C4 1C C7 03 00 30 00 00 5B", -5 ); InjectHook(animInterpolator, GTARtAnimInterpolatorSetCurrentAnim); } // Fixed ammo for melee weapons in cheats { // TODO: Remove wildcards in patterns once transactional patching is implemented void* knifeAmmo1 = get_pattern( "6A ? 6A 04 6A FF", 1 ); void* knifeAmmo2 = get_pattern( "6A 01 6A ? 6A 04 6A 01", 2 + 1 ); void* chainsawAmmo1 = get_pattern( "6A ? 6A 09 6A FF", 1 ); void* chainsawAmmo2 = get_pattern( "6A ? 6A 09 6A 01", 1 ); void* parachuteAmmo = get_pattern( "6A ? 6A 2E 6A FF", 1 ); void* katanaAmmo = get_pattern( "83 C4 0C ? ? ? 6A 08 6A FF", 3 ); Patch(knifeAmmo1, 1); // knife Patch(knifeAmmo2, 1); // knife Patch(chainsawAmmo1, 1); // chainsaw Patch(chainsawAmmo2, 1); // chainsaw Patch(parachuteAmmo, 1); // parachute // push ebx / push 1 Patch( katanaAmmo, { 0x53, 0x6A, 0x01 } ); // katana } // Proper aspect ratios { auto calculateAr = pattern( "74 13 D9 05 ? ? ? ? D9 1D" ).get_one(); // 0x734247; has two matches but both from the same function static const float f43 = 4.0f/3.0f, f54 = 5.0f/4.0f, f169 = 16.0f/9.0f; Patch(calculateAr.get( 2 + 2 ), &f169); Patch(calculateAr.get( 0x1E + 2 ), &f54); Patch(calculateAr.get( 0x31 + 2 ), &f43); } // 6 extra directionals on Medium and higher // push dword ptr [CGame::currArea] // call GetMaxExtraDirectionals // add esp, 4 // mov ebx, eax // nop { auto maxdirs_addr = pattern( "83 3D ? ? ? ? 00 8D 5E 05 74 05 BB 06 00 00 00" ).get_one(); Patch( maxdirs_addr.get(), { 0xFF, 0x35 } ); InjectHook( maxdirs_addr.get(6), GetMaxExtraDirectionals, HookType::Call ); Patch( maxdirs_addr.get(11), { 0x83, 0xC4, 0x04, 0x8B, 0xD8 } ); Nop( maxdirs_addr.get(16), 1 ); } // AI accuracy issue { auto match = pattern( "8B 82 8C 05 00 00 85 C0 74 09" ).get_one(); // 0x76DEA7 in newsteam r1 Nop(match.get(0), 1); InjectHook( match.get(1), WeaponRangeMult_VehicleCheck, HookType::Call ); } // Don't catch WM_SYSKEYDOWN and WM_SYSKEYUP (fixes Alt+F4) { auto patternie = pattern( "8B 75 10 8B ? 14 56" ).count(2); // 0x77C588 and 0x77C5CC in newsteam r2 auto defproc = get_pattern( "8B ? 14 8B ? 10 8B ? 08 ? ? 56" ); patternie.for_each_result( [&]( pattern_match match ) { InjectHook( match.get(0x39), defproc, HookType::Jump ); Patch( match.get(1), 0x5D ); // esi -> ebx Patch( match.get(6), 0x53 ); // esi -> ebx Patch( match.get(0x26 + 1), 0xFB ); // esi -> ebx Patch( match.get(8 + 2), -8 ); // use stack space for new lParam Patch( match.get(0x18 + 2), -8 ); // use stack space for new lParam Patch( match.get(0x2B + 2), -8 ); // use stack space for new lParam } ); } // Reset variables on New Game { using namespace VariableResets; { auto loadPickup = get_pattern("E8 ? ? ? ? EB 1B 6A 00"); auto loadCarGenerator = get_pattern("E8 ? ? ? ? 83 C4 08 EB 11"); auto loadStuntJump = get_pattern("50 E8 ? ? ? ? EB 06", 1); std::array reInitGameObjectVariables = { get_pattern( "E8 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ? E8 ? ? ? ? 38 1D" ), get_pattern( "E8 ? ? ? ? 89 1D ? ? ? ? E8 ? ? ? ? 5E" ) }; HookEach_ReInitGameObjectVariables(reInitGameObjectVariables, InterceptCall); InterceptCall(loadPickup, orgLoadPickup, LoadPickup_SaveLine); InterceptCall(loadCarGenerator, orgLoadCarGenerator, LoadCarGenerator_SaveLine); InterceptCall(loadStuntJump, orgLoadStuntJump, LoadStuntJump_SaveLine); } // Variables to reset { auto timers_init = pattern( "89 45 FC DB 45 FC C6 05 ? ? ? ? 01" ).get_one(); GameVariablesToReset.emplace_back( *timers_init.get(-17 + 2) ); GameVariablesToReset.emplace_back( *timers_init.get(-11 + 2) ); GameVariablesToReset.emplace_back( *timers_init.get*>(0x41 + 2) ); } GameVariablesToReset.emplace_back( *get_pattern( "A2 ? ? ? ? E9 ? ? ? ? 6A 01 8B CE", 1 ) ); // CGameLogic::bPenaltyForDeathApplies GameVariablesToReset.emplace_back( *get_pattern( "88 0D ? ? ? ? E9 ? ? ? ? 6A 05", 2 ) ); // CGameLogic::bPenaltyForArrestApplies } // FuckCarCompletely not fixing panels { void* panel_addr = get_pattern( "C6 46 04 FA 5E 5B", -3 ); Nop(panel_addr, 3); } // 014C cargen counter fix (by spaceeinstein) { auto do_processing = pattern( "B8 C3 2E 57 06 F7 EE C1 FA 06" ).get_one(); Patch( do_processing.get(27 + 1), 0xBF ); // movzx eax, word ptr [edi+1Ah] -> movsx eax, word ptr [edi+1Ah] Patch( do_processing.get(41), 0x74 ); // jge -> jz } // Linear filtering on script sprites { void* drawScriptSprites = get_pattern( "81 EC 94 01 00 00 53 56 57 50", 10 ); ReadCall( drawScriptSprites, orgDrawScriptSpritesAndRectangles ); InjectHook( drawScriptSprites, DrawScriptSpritesAndRectangles ); } // Animated Phoenix hood scoop // TODO // Extra animations for planes // TODO // Fixed animations for boats // TODO // Stop BF Injection/Bandito/Hotknife rotating engine components when engine is off // TODO // Make freeing temp objects more aggressive to fix vending crash InjectHook( get_pattern( "57 8B 78 08 89 45 FC 85 FF 74 5B", -9 ), CObject::TryToFreeUpTempObjects_SilentPatch, HookType::Jump ); // Remove FILE_FLAG_NO_BUFFERING from CdStreams Patch( get_pattern( "81 F9 00 08 00 00 ? 05", 6 ), 0xEB ); // Proper metric-imperial conversion constants static const double METERS_TO_FEET_DIV = 1.0 / 3.280839895; Patch( get_pattern( "83 EC 08 DC 35 ? ? ? ? DD 1C 24", 3 + 2 ), &METERS_TO_FEET_DIV ); Patch( get_pattern( "51 DC 35 ? ? ? ? DD 1C 24", 1 + 2 ), &METERS_TO_FEET_DIV ); // Fixed impounding of random vehicles (because CVehicle::~CVehicle doesn't remove cars from apCarsToKeep) { void* recordVehicleDeleted = get_pattern( "E8 ? ? ? ? 33 C0 66 89 86" ); ReadCall( recordVehicleDeleted, orgRecordVehicleDeleted ); InjectHook( recordVehicleDeleted, RecordVehicleDeleted_AndRemoveFromVehicleList ); } // Don't include an extra D3DLIGHT on vehicles since we fixed directional already // TODO when timecyc.dat illumination is fixed // Fixed CAEAudioUtility timers - not typecasting to float so we're not losing precision after X days of PC uptime // Also fixed integer division by zero { auto staticInitialize = pattern( "FF 15 ? ? ? ? 5F 5E 85 C0" ).get_one(); Patch( staticInitialize.get( 2 ), &pAudioUtilsFrequency ); InjectHook( staticInitialize.get( 0x1E ), AudioUtilsGetStartTime ); InjectHook( get_pattern( "50 FF 15 ? ? ? ? DF 6D F8", -9 ), AudioUtilsGetCurrentTimeInMs, HookType::Jump ); } // Car generators placed in interiors visible everywhere InjectHook( get_pattern( "E8 ? ? ? ? 0F B6 57 0A" ), &CEntity::SetPositionAndAreaCode ); // Fixed bomb ownership/bombs saving for bikes { std::array restoreCar = { get_pattern( "8D 4E EE E8", 3 ), get_pattern( "8D 4F EE E8", 3 ) }; CStoredCar::HookEach_RestoreCar(restoreCar, InterceptCall); } // unnamed CdStream semaphore { auto semaName = pattern( "52 6A 40 FF 15" ).get_one(); Patch( semaName.get( 9 ), { 0x6A, 0x00 } ); // push 0 \ nop Nop( semaName.get( 9 + 2 ), 3 ); } // Correct streaming when using RC vehicles InjectHook( get_pattern( "88 1D ? ? ? ? E8 ? ? ? ? 8B F0 83 C4 04 3B F3", 6 ), FindPlayerEntityWithRC ); InjectHook( get_pattern( "E8 ? ? ? ? 83 C4 08 85 C0 74 07 C6 05" ), FindPlayerVehicle_RCWrap ); // Fixed triangle above recruitable peds' heads Patch( get_pattern( "83 BE 98 05 00 00 ? D9 45 DC", 6 ), 8 ); // GANG2 // Credits =) { auto renderCredits = pattern( "83 C4 18 E8 ? ? ? ? 80 3D" ).get_one(); ReadCall( renderCredits.get( -58 ), Credits::PrintCreditText ); ReadCall( renderCredits.get( -5 ), Credits::PrintCreditText_Hooked ); InjectHook( renderCredits.get( -5 ), Credits::PrintSPCredits ); } // Fixed ammo from SCM { void* giveWeapon = get_pattern( "8B CE E8 ? ? ? ? 8B CE 8B D8", 2 ); ReadCall( giveWeapon, CPed::orgGiveWeapon ); InjectHook( giveWeapon, &CPed::GiveWeapon_SP ); } // Fixed bicycle on fire - instead of CJ being set on fire, bicycle's driver is { using namespace BicycleFire; auto doStuffToGoOnFire = pattern( "83 BF 94 05 00 00 0A 75 6D 6A" ).get_one(); // 0x0054A6BE constexpr ptrdiff_t START_FIRE_OFFSET = 0x31; Patch( doStuffToGoOnFire.get( 9 ), { 0x90, 0x57 } ); // nop \ push edi Patch( doStuffToGoOnFire.get( START_FIRE_OFFSET ), { 0x90, 0x57 } ); // nop \ push edi InjectHook( doStuffToGoOnFire.get( 9 + 2 ), GetVehicleDriver ); InjectHook( doStuffToGoOnFire.get( START_FIRE_OFFSET + 2 ), GetVehicleDriver ); ReadCall( doStuffToGoOnFire.get( 0x15 ), CPlayerPed::orgDoStuffToGoOnFire ); InjectHook( doStuffToGoOnFire.get( 0x15 ), DoStuffToGoOnFire_NullAndPlayerCheck ); ReadCall( doStuffToGoOnFire.get( START_FIRE_OFFSET + 0x10 ), CFireManager::orgStartFire ); InjectHook( doStuffToGoOnFire.get( START_FIRE_OFFSET + 0x10 ), &CFireManager::StartFire_NullEntityCheck ); } // Decreased keyboard input latency { using namespace KeyboardInputFix; auto updatePads = pattern( "E8 ? ? ? ? B9 ? ? ? ? BE" ).get_one(); // 0x552DB7 NewKeyState = *updatePads.get( 10 + 1 ); OldKeyState = *updatePads.get( 15 + 1 ); TempKeyState = *updatePads.get( 27 + 1 ); objSize = *updatePads.get( 5 + 1 ) * 4; ReadCall( updatePads.get( -44 ), orgClearSimButtonPressCheckers ); InjectHook( updatePads.get( -44 ), ClearSimButtonPressCheckers ); Nop( updatePads.get( 20 ), 2 ); Nop( updatePads.get( 37 ), 2 ); } // Fixed handling.cfg name matching (names don't need unique prefixes anymore) { using namespace HandlingNameLoadFix; auto findExactWord = pattern( "8B 55 08 56 8D 4D EC" ).get_one(); // 0x6F849B InjectHook( findExactWord.get( -5 ), strncpy_Fix ); InjectHook( findExactWord.get( 9 ), strncmp_Fix ); } // No censorships (not set nor loaded from savegame) { using namespace Localization; auto loadCensorshipValues = pattern( "0F B6 8E ED 00 00 00 88 0D" ).get_one(); void* initialiseLanguage1 = get_pattern( "E8 ? ? ? ? 8B 35 ? ? ? ? FF D6" ); auto initialiseLanguage2 = pattern( "E8 ? ? ? ? 83 FB 07" ).get_one(); germanGame = *loadCensorshipValues.get( 7 + 2 ); frenchGame = *loadCensorshipValues.get( 0x14 + 2 ); nastyGame = *loadCensorshipValues.get( 0x21 + 1 ); // Don't load censorship values Nop( loadCensorshipValues.get( 7 ), 6 ); Nop( loadCensorshipValues.get( 0x14 ), 6 ); Nop( loadCensorshipValues.get( 0x21 ), 5 ); // Unified censorship levels for all regions InjectHook( initialiseLanguage1, EmptyStub ); void* setNormalGame; void* setGermanGame; void* setFrenchGame; ReadCall( initialiseLanguage2.get(), setNormalGame ); ReadCall( initialiseLanguage2.get( 0x15 ), setGermanGame ); ReadCall( initialiseLanguage2.get( 0x2A ), setFrenchGame ); InjectHook( setNormalGame, SetUncensoredGame, HookType::Jump ); InjectHook( setGermanGame, SetUncensoredGame, HookType::Jump ); InjectHook( setFrenchGame, SetUncensoredGame, HookType::Jump ); } // Default Steer with Mouse to disabled, like in older executables not based on xbox { // mov _ZN8CVehicle22m_bEnableMouseSteeringE, bl -> // mov _ZN8CVehicle22m_bEnableMouseSteeringE, al void* setDefaultPreferences = get_pattern( "89 86 AD 00 00 00 66 89 86 B1 00 00 00", -0xC ); Patch( setDefaultPreferences, { 0x90, 0xA2 } ); } // Re-introduce corona rotation on PC, like it is in III/VC/SA PS2 { using namespace CoronaRotationFix; auto mulRecipz = get_pattern( "D9 5D FC D9 45 FC 89 45 FC D9 1C 24 53", -6 ); auto renderOneXLUSprite = get_pattern( "E8 ? ? ? ? 83 C4 30 EB 02 DD D8 8A 47 FA" ); // Remove *= 20.0f from recipz to retrieve the original value for later Nop( mulRecipz, 6 ); ReadCall( renderOneXLUSprite, orgRenderOneXLUSprite_Rotate_Aspect ); InjectHook( renderOneXLUSprite, RenderOneXLUSprite_Rotate_Aspect_SilentPatch ); } // Fixed static shadows not rendering under fire and pickups { using namespace StaticShadowAlphaFix; auto renderStaticShadows = pattern( "52 E8 ? ? ? ? E8 ? ? ? ? E8" ).get_one(); ReadCall( renderStaticShadows.get( 1 + 5 + 5 ), orgRenderStaticShadows ); InjectHook( renderStaticShadows.get( 1 + 5 + 5 ), RenderStaticShadows_StateFix ); ReadCall( renderStaticShadows.get( 1 + 5 + 5 + 5 ), orgRenderStoredShadows ); InjectHook( renderStaticShadows.get( 1 + 5 + 5 + 5 ), RenderStoredShadows_StateFix ); } // Disable building pipeline for skinned objects (like parachute) { using namespace SkinBuildingPipelineFix; auto setupAtomic = get_pattern("74 0D 57 E8 ? ? ? ? 83 C4 04 5F 5E 5D C3", 3); InterceptCall(setupAtomic, orgCustomBuildingDNPipeline_CustomPipeAtomicSetup, CustomBuildingDNPipeline_CustomPipeAtomicSetup_Skinned); } // Reset requested extras if created vehicle has no extras // Fixes eg. lightless taxis { auto resetComps = pattern( "6A 00 68 ? ? ? ? 57 E8 ? ? ? ? 83 C4 0C 8B C7" ).get_one(); InjectHook( resetComps.get( -9 ), CVehicleModelInfo::ResetCompsForNoExtras, HookType::Call ); Nop( resetComps.get( -9 + 5 ), 4 ); } // Allow extra6 to be picked with component rule 4 (any) { void* extra6 = get_pattern( "6A 00 E8 ? ? ? ? 83 C4 08 5E", -2 + 1 ); Patch( extra6, 6 ); } // Disallow moving cam up/down with mouse when looking back/left/right in vehicle { using namespace FollowCarMouseCamFix; bool** useMouse3rdPerson = get_pattern( "80 3D ? ? ? ? 00 C6 45 1B 00", 2 ); auto getPad = get_pattern( "89 45 B8 E8 ? ? ? ? 89 45 FC", 3 ); orgUseMouse3rdPerson = *useMouse3rdPerson; Patch( useMouse3rdPerson, &useMouseAndLooksForwards ); ReadCall( getPad, orgGetPad ); InjectHook( getPad, getPadAndSetFlag ); } // Add wind animations when driving a Quadbike // By Wesser { auto isOpenTopCar = pattern("8B 11 8B 82 9C 00 00 00 FF D0").get_one(); InjectHook(isOpenTopCar.get(), &CVehicle::IsOpenTopCarOrQuadbike, HookType::Call); Nop(isOpenTopCar.get(5), 5); } // Tie handlebar movement to the stering animations on Quadbike, fixes odd animation interpolations at low speeds // By Wesser { auto processRiderAnims = pattern("DD 05 ? ? ? ? D9 05 ? ? ? ? E8 ? ? ? ? D9 5D F0 80 7D 0B 00").get_one(); // Compiler reordered variables compared to the older versions, so they need to be preserved auto saveDriveByAnim = pattern("D8 71 18 D9 5D EC").get_one(); Nop(processRiderAnims.get(), 1); InjectHook(processRiderAnims.get(1), &QuadbikeHandlebarAnims::ProcessRiderAnims_FixInterp_Steam, HookType::Call); Nop(saveDriveByAnim.get(), 1); InjectHook(saveDriveByAnim.get(1), &QuadbikeHandlebarAnims::SaveDriveByAnim_Steam, HookType::Call); } // Disable the radio station change anim on boats where CJ stands upright // By Wesser { using namespace UprightBoatRadioStationChange; auto blendAnimation = get_pattern("E8 ? ? ? ? 83 C4 10 85 C0 0F 85 ? ? ? ? D9 47 48"); InterceptCall(blendAnimation, orgAnimManagerBlendAnimation, AnimManagerBlendAnimation_SkipIfBoatDrive); } // Fix a memory leak when taking photos { using namespace CameraMemoryLeakFix; auto psGrabScreen = pattern("8B C6 5E 8B E5 5D C3 33 C0").get_one(); InjectHook(psGrabScreen.get(2), psGrabScreen_UnlockAndReleaseSurface_Steam, HookType::Jump); InjectHook(psGrabScreen.get(7 + 2), psGrabScreen_UnlockAndReleaseSurface_Steam, HookType::Jump); } // Fix crosshair issues when sniper rifle is quipped and a photo is taken by a gang member // By Wesser { using namespace CameraCrosshairFix; auto getWeaponInfo = get_pattern("E8 ? ? ? ? 8B 40 0C 83 C4 08 85 C0"); InterceptCall(getWeaponInfo, orgGetWeaponInfo, GetWeaponInfo_OrCamera); } // Cancel the Drive By task of biker cops when losing the wanted level { using namespace BikerCopsDriveByFix; auto backToCruisingIfNoWantedLevel = get_pattern("56 E8 ? ? ? ? 80 A6 ? ? ? ? ? 83 C4 04", 1); InterceptCall(backToCruisingIfNoWantedLevel, orgJoinCarWithRoadSystem, JoinCarWithRoadSystem_AbortDriveByTask); } // Fix miscolored racing checkpoints if no other marker was drawn before them { using namespace RacingCheckpointsRender; auto clumpRender = get_pattern("E8 ? ? ? ? DD 05 ? ? ? ? 83 C4 14"); InterceptCall(clumpRender, orgRpClumpRender, RpClumpRender_SetLitFlag); } // Delay destroying of cigarettes/bottles held by NPCs so it does not potentially corrupt the moving list { // CWorld::Process processes all entries in the moving list, calling ProcessControl on them. // CPlayerPed::ProcessControl handles the gang recruitment which in turn can result in homies dropping cigarettes or bottles. // When this happens, they are destroyed -immediately-. If those props are in the moving list right after the PlayerPed, // this corrupts a pre-cached node->next pointer and references an already freed entity. // To fix this, queue the entity for a delayed destruction instead of destroying immediately, // and let it destroy itself in CWorld::Process later. // or [esi+1Ch], 800h // bRemoveFromWorld // (The entity reference is already cleared for us, no need to do it) // jmp 5E03EC auto dropEntity = get_pattern("74 1C 8B 16 8B 42 20", 2); Patch(dropEntity, { 0x81, 0x4E, 0x1C, 0x00, 0x08, 0x00, 0x00, 0xEB, 0x13 }); } // Spawn lapdm1 (biker cop) correctly if the script requests one with PEDTYPE_COP // By Wesser { using namespace GetCorrectPedModel_Lapdm1; auto jumpTablePtr = *get_pattern("FF 24 8D ? ? ? ? 83 7D 08 06", 3); // Only patch if someone else hasn't relocated it if (ModCompat::Utils::GetModuleHandleFromAddress(jumpTablePtr) == hInstance) { Patch(jumpTablePtr+4, &BikerCop_Steam); } } // Only allow impounding cars and bikes (and their subclasses), as impounding helicopters, planes, boats makes no sense { using namespace RestrictImpoundVehicleTypes; auto isThisVehicleInteresting_pattern = pattern("56 E8 ? ? ? ? 83 C4 04 84 C0 74 09 56 E8 ? ? ? ? 83 C4 04 56").count(2); std::array isThisVehicleInteresting = { isThisVehicleInteresting_pattern.get(0).get(1), isThisVehicleInteresting_pattern.get(1).get(1), }; HookEach_ShouldImpound(isThisVehicleInteresting, InterceptCall); } // Fix PlayerPed replay crashes // 1. Crash when starting a mocap cutscene after playing a replay wearing different clothes to the ones CJ has currently // 2. Crash when playing back a replay with a different motion group anim (fat/muscular/normal) than the current one { using namespace ReplayPlayerPedCrashFixes; auto restoreStuffFromMem = get_pattern("E8 ? ? ? ? 80 3D ? ? ? ? ? C6 05 ? ? ? ? ? 74 3A D9 05"); auto rebuildPlayer = get_pattern("E8 ? ? ? ? 6A 01 56 E8 ? ? ? ? 83 C4 10 EB 53", 8); InterceptCall(restoreStuffFromMem, orgRestoreStuffFromMem, RestoreStuffFromMem_RebuildPlayer); InterceptCall(rebuildPlayer, orgRebuildPlayer, RebuildPlayer_LoadAllMotionGroupAnims); } // Fix planes spawning in places where they crash easily { using namespace FindPlaneCreationCoorsFix; auto findPlaneCreationCoors = get_pattern("E8 ? ? ? ? 83 C4 18 84 C0 74 09"); InterceptCall(findPlaneCreationCoors, orgCheckCameraCollisionBuildings, CheckCameraCollisionBuildings_FixParams_Steam); } // Allow hovering on the Jetpack with Keyboard + Mouse controls // Does not modify any other controls, only hovering { using namespace JetpackKeyboardControlsHover; auto processControl_CheckHover = pattern("0F 85 ? ? ? ? 8B CB C6 47 0D 00").get_one(); auto processControl_DoHover = pattern("E8 ? ? ? ? 84 C0 74 10 D9 EE").get_one(); ProcessControlInput_DontHover = processControl_CheckHover.get(12); ProcessControlInput_Hover = processControl_DoHover.get(9); Nop(processControl_CheckHover.get(6), 1); InjectHook(processControl_CheckHover.get(6 + 1), &ProcessControlInput_HoverWithKeyboard_Steam, HookType::Jump); ReadCall(processControl_DoHover.get(), orgGetLookBehindForCar); } // During riots, don't target the player group during missions // Fixes recruited homies panicking during Los Desperados and other riot-time missions { using namespace RiotDontTargetPlayerGroupDuringMissions; auto targettingCheck = pattern("80 BB D0 02 00 00 01").get_one(); auto skipTargetting = get_pattern("A1 ? ? ? ? A3 ? ? ? ? C7 05 ? ? ? ? ? ? ? ? 8B 4D F4"); DontSkipTargetting = targettingCheck.get(7); SkipTargetting = skipTargetting; InjectHook(targettingCheck.get(), CheckIfInPlayerGroupAndOnAMission_Steam, HookType::Jump); } // Rescale light switching randomness in CVehicle::GetVehicleLightsStatus for PC the randomness range // The original randomness was 50000 out of 65535, which is impossible to hit with PC's 32767 range { auto getVehicleLightsStatus = get_pattern("DC 35 ? ? ? ? D9 05 ? ? ? ? D8 D9", 2); static const double LightStatusRandomnessThreshold = 25000.0; Patch(getVehicleLightsStatus, &LightStatusRandomnessThreshold); } // Fixed vehicles exploding twice if the driver leaves the car while it's exploding { using namespace RemoveDriverStatusFix; auto removeDriver = pattern("8A 47 36 24 07 0C 20 80 7D 08 00").get_one(); auto removeThisPed = get_pattern("80 C9 20 88 48 36 8B 96", 3); auto taskSimpleCarSetPedOut = get_pattern("80 C9 20 88 48 36 8B 86", 3); auto prepareVehicleForPedExit = get_pattern("57 E8 ? ? ? ? 57 8B CE E8 ? ? ? ? 57", 1); Nop(removeDriver.get(), 2); InjectHook(removeDriver.get(2), RemoveDriver_SetStatus, HookType::Call); InterceptCall(prepareVehicleForPedExit, orgPrepareVehicleForPedExit, PrepareVehicleForPedExit_WreckedCheck); // CVehicle::RemoveDriver already sets the status to STATUS_ABANDONED, these are redundant Nop(removeThisPed, 3); Nop(taskSimpleCarSetPedOut, 3); } // Fixed falling stars rendering black { using namespace ShootingStarsFix; auto rwIm3dTransform = get_pattern("E8 ? ? ? ? 83 C4 10 85 C0 74 16 6A 02 68 ? ? ? ? 6A 02"); InterceptCall(rwIm3dTransform, orgRwIm3DTransform, RwIm3DTransform_UnsetTexture); } // Enable directional lights on flying car components { using namespace LitFlyingComponents; auto worldAdd = get_pattern("53 E8 ? ? ? ? 8B 4D F4 83 C4 04 5F 5E 8B C3", 1); InterceptCall(worldAdd, orgWorldAdd, WorldAdd_SetLightObjectFlag); } // Fix the logic behind exploding cars losing wheels // Right now, they lose one wheel at random according to the damage manager, but they always lose the front left wheel visually. // This change matches the visuals to the physics // Also make it possible for the rear right wheel to be randomly picked { auto automobileBlowUp = pattern("E8 ? ? ? ? 8B 8E ? ? ? ? 8D 45 08").get_one(); auto automobileBlowUpCutscene = pattern("E8 ? ? ? ? 80 7D 10 00 C7 45").get_one(); auto heliBlowUp = pattern("E8 ? ? ? ? 8B 86 ? ? ? ? 8D 55 08").get_one(); auto planeBlowUp = pattern("E8 ? ? ? ? 8B 86 ? ? ? ? 85 C0 74 24").get_one(); auto wheelDetachRandomness = get_pattern("DC 0D ? ? ? ? E8 ? ? ? ? 8B CE", 2); std::array spawnFlyingComponent = { automobileBlowUp.get(), automobileBlowUpCutscene.get(), heliBlowUp.get(), planeBlowUp.get(), }; CAutomobile::HookEach_SpawnFlyingComponent(spawnFlyingComponent, InterceptCall); Nop(automobileBlowUp.get(0x1C), 5); Nop(automobileBlowUpCutscene.get(0x22), 5); Nop(heliBlowUp.get(0x1C), 5); Nop(planeBlowUp.get(0x20), 5); static const double fRandomness = -4.0; Patch(wheelDetachRandomness, &fRandomness); } // Make script randomness 16-bit, like on PS2 { using namespace Rand16bit; std::array rands = { get_pattern("E8 ? ? ? ? 89 45 08 DB 45 08 32 C0"), get_pattern("E8 ? ? ? ? 89 06 32 C0"), }; HookEach_Rand(rands, InterceptCall); } } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { UNREFERENCED_PARAMETER(lpvReserved); if ( fdwReason == DLL_PROCESS_ATTACH ) { const HINSTANCE hInstance = GetModuleHandle( nullptr ); std::unique_ptr Protect = ScopedUnprotect::UnprotectSectionOrFullModule( hInstance, ".text" ); ScopedUnprotect::Section Protect2( hInstance, ".rdata" ); const int8_t version = Memory::GetVersion().version; if ( version == 0 ) Patch_SA_10(hInstance); else if ( version == 1 ) Patch_SA_11(); // Not supported anymore else if ( version == 2 ) Patch_SA_Steam(); // Not supported anymore else { // TODO: // Add r1 low violence check to MemoryMgr.GTA via // if ( *(DWORD*)DynBaseAddress(0x49F810) == 0x64EC8B55 ) { normal } else { low violence } Patch_SA_NewBinaries_Common(hInstance); } } return TRUE; } extern "C" __declspec(dllexport) uint32_t GetBuildNumber() { return (SILENTPATCH_REVISION_ID << 8) | SILENTPATCH_BUILD_ID; }