diff --git a/SilentPatch/Common.cpp b/SilentPatch/Common.cpp index ee0cd1c..875eea2 100644 --- a/SilentPatch/Common.cpp +++ b/SilentPatch/Common.cpp @@ -5,6 +5,7 @@ #include "Utils/HookEach.hpp" #include "StoredCar.h" #include "SVF.h" +#include "ParseUtils.hpp" #include "Utils/DelimStringReader.h" @@ -136,9 +137,11 @@ namespace ExtraCompSpecularity GetPrivateProfileSectionW(L"ExtraCompSpecularityExceptions", reader.GetBuffer(), reader.GetSize(), pPath); while (const wchar_t* str = reader.GetString()) { - int32_t toList = wcstol(str, nullptr, 0); - if ( toList > 0 ) - SVF::RegisterFeature(toList, SVF::Feature::_INTERNAL_NO_SPECULARITY_ON_EXTRAS); + auto modelID = ParseUtils::TryParseInt(str); + if (modelID) + SVF::RegisterFeature(*modelID, SVF::Feature::_INTERNAL_NO_SPECULARITY_ON_EXTRAS); + else + SVF::RegisterFeature(ParseUtils::ParseString(str), SVF::Feature::_INTERNAL_NO_SPECULARITY_ON_EXTRAS); } } diff --git a/SilentPatch/ParseUtils.cpp b/SilentPatch/ParseUtils.cpp new file mode 100644 index 0000000..1988733 --- /dev/null +++ b/SilentPatch/ParseUtils.cpp @@ -0,0 +1,37 @@ +#include "ParseUtils.hpp" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +static std::string WcharToUTF8(std::wstring_view str) +{ + std::string result; + + const int count = WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), nullptr, 0, nullptr, nullptr); + if (count != 0) + { + result.resize(count); + WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), result.data(), count, nullptr, nullptr); + } + + return result; +} + +std::optional ParseUtils::TryParseInt(const wchar_t* str) +{ + std::optional result; + + wchar_t* end; + const int32_t val = wcstol(str, &end, 0); + if (*end == '\0') + { + result.emplace(val); + } + return result; +} + +std::string ParseUtils::ParseString(const wchar_t* str) +{ + return WcharToUTF8(str); +} diff --git a/SilentPatch/ParseUtils.hpp b/SilentPatch/ParseUtils.hpp new file mode 100644 index 0000000..10dde4b --- /dev/null +++ b/SilentPatch/ParseUtils.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +namespace ParseUtils +{ + std::optional TryParseInt(const wchar_t* str); + std::string ParseString(const wchar_t* str); +}; diff --git a/SilentPatch/SVF.cpp b/SilentPatch/SVF.cpp index 181c5b6..9e5376c 100644 --- a/SilentPatch/SVF.cpp +++ b/SilentPatch/SVF.cpp @@ -84,6 +84,26 @@ namespace SVF #endif }; + static std::multimap > specialVehFeaturesByName; + static void* (*GetModelInfoCB)(const char* name, int* outIndex); + static bool bModelNamesReady = false; + + void _resolveFeatureNamesInternal() + { + if (bModelNamesReady && GetModelInfoCB != nullptr && !specialVehFeaturesByName.empty()) + { + for (const auto& feature : specialVehFeaturesByName) + { + int32_t index; + if (GetModelInfoCB(feature.first.c_str(), &index) != nullptr) + { + specialVehFeatures.emplace(index, feature.second); + } + } + specialVehFeaturesByName.clear(); + } + } + int32_t RegisterFeature( int32_t modelID, Feature feature ) { if ( feature == Feature::NO_FEATURE ) return -1; @@ -93,6 +113,15 @@ namespace SVF return cookie; } + int32_t RegisterFeature( std::string modelName, Feature feature ) + { + if ( feature == Feature::NO_FEATURE ) return -1; + + const int32_t cookie = _getCookie(); + specialVehFeaturesByName.emplace( std::move(modelName), std::make_tuple(feature, cookie) ); + return cookie; + } + void DeleteFeature( int32_t cookie ) { for ( auto it = specialVehFeatures.begin(); it != specialVehFeatures.end(); ++it ) @@ -103,6 +132,15 @@ namespace SVF return; } } + + for ( auto it = specialVehFeaturesByName.begin(); it != specialVehFeaturesByName.end(); ++it ) + { + if ( std::get(it->second) == cookie ) + { + specialVehFeaturesByName.erase( it ); + return; + } + } } void DisableStockVehiclesForFeature( Feature feature ) @@ -119,10 +157,23 @@ namespace SVF ++it; } } + + for ( auto it = specialVehFeaturesByName.begin(); it != specialVehFeaturesByName.end(); ) + { + if ( std::get(it->second) == feature && std::get(it->second) <= highestStockCookie ) + { + it = specialVehFeaturesByName.erase( it ); + } + else + { + ++it; + } + } } bool ModelHasFeature( int32_t modelID, Feature feature ) { + _resolveFeatureNamesInternal(); auto results = specialVehFeatures.equal_range( modelID ); return std::find_if( results.first, results.second, [feature] ( const auto& e ) { return std::get(e.second) == feature; @@ -131,6 +182,7 @@ namespace SVF std::function ForAllModelFeatures( int32_t modelID, std::function pred ) { + _resolveFeatureNamesInternal(); auto results = specialVehFeatures.equal_range( modelID ); for ( auto it = results.first; it != results.second; ++it ) { @@ -141,6 +193,16 @@ namespace SVF } return std::move(pred); } + + void RegisterGetModelInfoCB(void*(*func)(const char*, int*)) + { + GetModelInfoCB = func; + } + + void MarkModelNamesReady() + { + bModelNamesReady = true; + } } // Returns "feature cookie" on success, -1 on failure @@ -152,6 +214,12 @@ __declspec(dllexport) int32_t RegisterSpecialVehicleFeature( int32_t modelID, co return SVF::RegisterFeature( modelID, SVF::GetFeatureFromName(featureName) ); } +__declspec(dllexport) int32_t RegisterSpecialVehicleFeatureByName( const char* modelName, const char* featureName ) +{ + if ( featureName == nullptr || modelName == nullptr ) return -1; + return SVF::RegisterFeature( modelName, SVF::GetFeatureFromName(featureName) ); +} + __declspec(dllexport) void DeleteSpecialVehicleFeature( int32_t cookie ) { if ( cookie == -1 ) return; diff --git a/SilentPatch/SVF.h b/SilentPatch/SVF.h index bb99708..560aaef 100644 --- a/SilentPatch/SVF.h +++ b/SilentPatch/SVF.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace SVF { @@ -49,8 +50,12 @@ namespace SVF }; int32_t RegisterFeature( int32_t modelID, Feature feature ); + int32_t RegisterFeature( std::string modelName, Feature feature ); void DeleteFeature( int32_t cookie ); void DisableStockVehiclesForFeature( Feature feature ); bool ModelHasFeature( int32_t modelID, Feature feature ); std::function ForAllModelFeatures( int32_t modelID, std::function pred ); + + void RegisterGetModelInfoCB(void*(*func)(const char*, int*)); + void MarkModelNamesReady(); }; \ No newline at end of file diff --git a/SilentPatchIII/SilentPatchIII.cpp b/SilentPatchIII/SilentPatchIII.cpp index 56fc348..f98f128 100644 --- a/SilentPatchIII/SilentPatchIII.cpp +++ b/SilentPatchIII/SilentPatchIII.cpp @@ -839,6 +839,17 @@ namespace SitInBoat } +namespace SVFReadyHook +{ + static void (*orgInitialiseObjectData)(const char*); + static void InitialiseObjectData_ReadySVF(const char* path) + { + orgInitialiseObjectData(path); + SVF::MarkModelNamesReady(); + } +} + + void InjectDelayedPatches_III_Common( bool bHasDebugMenu, const wchar_t* wcModulePath ) { using namespace Memory; @@ -995,6 +1006,21 @@ void InjectDelayedPatches_III_Common( bool bHasDebugMenu, const wchar_t* wcModul } TXN_CATCH(); + + // Register CBaseModelInfo::GetModelInfo for SVF so we can resolve model names + try + { + using namespace SVFReadyHook; + + auto initialiseObjectData = get_pattern("E8 ? ? ? ? B3 01 59 8D 6D 04"); + auto getModelInfo = (void*(*)(const char*, int*))get_pattern("31 FF 8D 84 20 00 00 00 00 8B 04 BD", -7); + + InterceptCall(initialiseObjectData, orgInitialiseObjectData, InitialiseObjectData_ReadySVF); + SVF::RegisterGetModelInfoCB(getModelInfo); + } + TXN_CATCH(); + + FLAUtils::Init(moduleList); } diff --git a/SilentPatchIII/SilentPatchIII.vcxproj b/SilentPatchIII/SilentPatchIII.vcxproj index cc97395..5c5df47 100644 --- a/SilentPatchIII/SilentPatchIII.vcxproj +++ b/SilentPatchIII/SilentPatchIII.vcxproj @@ -30,6 +30,11 @@ NotUsing NotUsing + + NotUsing + NotUsing + NotUsing + NotUsing NotUsing @@ -66,6 +71,7 @@ + diff --git a/SilentPatchIII/SilentPatchIII.vcxproj.filters b/SilentPatchIII/SilentPatchIII.vcxproj.filters index 37f3274..2bbb958 100644 --- a/SilentPatchIII/SilentPatchIII.vcxproj.filters +++ b/SilentPatchIII/SilentPatchIII.vcxproj.filters @@ -57,6 +57,9 @@ Source Files + + Source Files + @@ -104,6 +107,9 @@ Header Files + + Header Files + diff --git a/SilentPatchSA/SilentPatchSA.cpp b/SilentPatchSA/SilentPatchSA.cpp index 33d5524..781eb1e 100644 --- a/SilentPatchSA/SilentPatchSA.cpp +++ b/SilentPatchSA/SilentPatchSA.cpp @@ -29,6 +29,7 @@ #include "Utils/HookEach.hpp" #include "Desktop.h" +#include "SVF.h" #include "debugmenu_public.h" #include "resource.h" @@ -2659,6 +2660,16 @@ static bool IgnoresWeaponPedsForPCFix() } +namespace SVFReadyHook +{ + static void (*orgMatchAllModelStrings)(); + static void MatchAllModelStrings_ReadySVF() + { + orgMatchAllModelStrings(); + SVF::MarkModelNamesReady(); + } +} + #ifndef NDEBUG // ============= QPC spoof for verifying high timer issues ============= @@ -3894,6 +3905,16 @@ BOOL InjectDelayedPatches_10() } } + // Register CBaseModelInfo::GetModelInfo for SVF so we can resolve model names + { + using namespace SVFReadyHook; + + auto func = (void*(*)(const char*, int*))0x4C5940; + + InterceptCall(0x5B922F, orgMatchAllModelStrings, MatchAllModelStrings_ReadySVF); + SVF::RegisterGetModelInfoCB(func); + } + #ifndef NDEBUG if ( const int QPCDays = GetPrivateProfileIntW(L"Debug", L"AddDaysToQPC", 0, wcModulePath); QPCDays != 0 ) { diff --git a/SilentPatchSA/SilentPatchSA.vcxproj b/SilentPatchSA/SilentPatchSA.vcxproj index c278440..d83ffda 100644 --- a/SilentPatchSA/SilentPatchSA.vcxproj +++ b/SilentPatchSA/SilentPatchSA.vcxproj @@ -207,6 +207,11 @@ copy /y "$(TargetPath)" "H:\Rockstar Games\Grand Theft Auto San Andreas\SilentPa NotUsing NotUsing + + NotUsing + NotUsing + NotUsing + NotUsing NotUsing @@ -246,6 +251,7 @@ copy /y "$(TargetPath)" "H:\Rockstar Games\Grand Theft Auto San Andreas\SilentPa + diff --git a/SilentPatchSA/SilentPatchSA.vcxproj.filters b/SilentPatchSA/SilentPatchSA.vcxproj.filters index d15dbdf..5aea32f 100644 --- a/SilentPatchSA/SilentPatchSA.vcxproj.filters +++ b/SilentPatchSA/SilentPatchSA.vcxproj.filters @@ -81,6 +81,9 @@ Source Files + + Source Files + @@ -164,6 +167,9 @@ Header Files + + Header Files + diff --git a/SilentPatchSA/VehicleSA.cpp b/SilentPatchSA/VehicleSA.cpp index eb5b40c..bdf799a 100644 --- a/SilentPatchSA/VehicleSA.cpp +++ b/SilentPatchSA/VehicleSA.cpp @@ -6,6 +6,7 @@ #include "PedSA.h" #include "Utils/DelimStringReader.h" #include "PlayerInfoSA.h" +#include "ParseUtils.hpp" #include "SVF.h" @@ -27,26 +28,29 @@ bool ReadDoubleRearWheels(const wchar_t* pPath) GetPrivateProfileSectionW( L"DoubleRearWheels", reader.GetBuffer(), reader.GetSize(), pPath ); while ( const wchar_t* str = reader.GetString() ) { - wchar_t textLine[64]; - wchar_t* context = nullptr; - wchar_t* token; + wchar_t textLine[128]; + wcscpy_s(textLine, str); - wcscpy_s( textLine, str ); - token = wcstok_s( textLine, L"=", &context ); + wchar_t* context = nullptr; + wchar_t* model = wcstok_s(textLine, L"=", &context); + if (model == nullptr) continue; - int32_t toList = wcstol( token, nullptr, 0 ); - if ( toList <= 0 ) continue; + wchar_t* val = wcstok_s(nullptr, L"=", &context); + if (val == nullptr) continue; - wchar_t* begin = wcstok_s( nullptr, L"=", &context ); - if ( begin == nullptr ) continue; + auto value = ParseUtils::TryParseInt(val); + if (!value) continue; - wchar_t* end = nullptr; - bool value = wcstoul( begin, &end, 0 ) != 0; - if ( begin != end ) + auto modelID = ParseUtils::TryParseInt(model); + if (modelID) { - SVF::RegisterFeature( toList, value ? SVF::Feature::_INTERNAL_FORCE_DOUBLE_RWHEELS_ON : SVF::Feature::_INTERNAL_FORCE_DOUBLE_RWHEELS_OFF ); - listedAny = true; + SVF::RegisterFeature(*modelID, *value ? SVF::Feature::_INTERNAL_FORCE_DOUBLE_RWHEELS_ON : SVF::Feature::_INTERNAL_FORCE_DOUBLE_RWHEELS_OFF); } + else + { + SVF::RegisterFeature(ParseUtils::ParseString(model), *value ? SVF::Feature::_INTERNAL_FORCE_DOUBLE_RWHEELS_ON : SVF::Feature::_INTERNAL_FORCE_DOUBLE_RWHEELS_OFF); + } + listedAny = true; } return listedAny; } @@ -202,9 +206,11 @@ void ReadRotorFixExceptions(const wchar_t* pPath) GetPrivateProfileSectionW( L"RotorFixExceptions", reader.GetBuffer(), reader.GetSize(), pPath ); while ( const wchar_t* str = reader.GetString() ) { - int32_t toList = wcstol( str, nullptr, 0 ); - if ( toList > 0 ) - SVF::RegisterFeature( toList, SVF::Feature::_INTERNAL_NO_ROTOR_FADE ); + auto ID = ParseUtils::TryParseInt(str); + if (ID) + SVF::RegisterFeature(*ID, SVF::Feature::_INTERNAL_NO_ROTOR_FADE); + else + SVF::RegisterFeature(ParseUtils::ParseString(str), SVF::Feature::_INTERNAL_NO_ROTOR_FADE); } } @@ -216,9 +222,11 @@ void ReadLightbeamFixExceptions(const wchar_t* pPath) GetPrivateProfileSectionW( L"LightbeamFixExceptions", reader.GetBuffer(), reader.GetSize(), pPath ); while ( const wchar_t* str = reader.GetString() ) { - int32_t toList = wcstol( str, nullptr, 0 ); - if ( toList > 0 ) - SVF::RegisterFeature( toList, SVF::Feature::_INTERNAL_NO_LIGHTBEAM_BFC_FIX ); + auto ID = ParseUtils::TryParseInt(str); + if (ID) + SVF::RegisterFeature(*ID, SVF::Feature::_INTERNAL_NO_LIGHTBEAM_BFC_FIX); + else + SVF::RegisterFeature(ParseUtils::ParseString(str), SVF::Feature::_INTERNAL_NO_LIGHTBEAM_BFC_FIX); } } diff --git a/SilentPatchVC/SilentPatchVC.cpp b/SilentPatchVC/SilentPatchVC.cpp index 6677dc7..b2b6211 100644 --- a/SilentPatchVC/SilentPatchVC.cpp +++ b/SilentPatchVC/SilentPatchVC.cpp @@ -771,6 +771,17 @@ namespace CarPartsBackfaceCulling } +namespace SVFReadyHook +{ + static void (*orgInitialiseObjectData)(const char*); + static void InitialiseObjectData_ReadySVF(const char* path) + { + orgInitialiseObjectData(path); + SVF::MarkModelNamesReady(); + } +} + + void InjectDelayedPatches_VC_Common( bool bHasDebugMenu, const wchar_t* wcModulePath ) { using namespace Memory; @@ -926,6 +937,20 @@ void InjectDelayedPatches_VC_Common( bool bHasDebugMenu, const wchar_t* wcModule TXN_CATCH(); } + // Register CBaseModelInfo::GetModelInfo for SVF so we can resolve model names + try + { + using namespace SVFReadyHook; + + auto initialiseObjectData = get_pattern("E8 ? ? ? ? 59 E8 ? ? ? ? E8 ? ? ? ? 31 DB"); + auto getModelInfo = (void*(*)(const char*, int*))get_pattern("57 31 FF 55 8B 6C 24 14", -6); + + InterceptCall(initialiseObjectData, orgInitialiseObjectData, InitialiseObjectData_ReadySVF); + SVF::RegisterGetModelInfoCB(getModelInfo); + } + TXN_CATCH(); + + FLAUtils::Init(moduleList); } diff --git a/SilentPatchVC/SilentPatchVC.vcxproj b/SilentPatchVC/SilentPatchVC.vcxproj index 5d26fd8..0677286 100644 --- a/SilentPatchVC/SilentPatchVC.vcxproj +++ b/SilentPatchVC/SilentPatchVC.vcxproj @@ -170,6 +170,7 @@ + @@ -198,6 +199,11 @@ NotUsing NotUsing + + NotUsing + NotUsing + NotUsing + NotUsing NotUsing diff --git a/SilentPatchVC/SilentPatchVC.vcxproj.filters b/SilentPatchVC/SilentPatchVC.vcxproj.filters index 6f9c906..58bcbb4 100644 --- a/SilentPatchVC/SilentPatchVC.vcxproj.filters +++ b/SilentPatchVC/SilentPatchVC.vcxproj.filters @@ -66,6 +66,9 @@ Header Files + + Header Files + @@ -107,6 +110,9 @@ Source Files + + Source Files +