diff --git a/doc/III.VC.SA.LimitAdjuster.ini b/doc/III.VC.SA.LimitAdjuster.ini index 05c4ff0..637af45 100644 --- a/doc/III.VC.SA.LimitAdjuster.ini +++ b/doc/III.VC.SA.LimitAdjuster.ini @@ -55,7 +55,7 @@ ColModel = 15000 AlphaEntityList = 2000 VisibleEntityPtrs = unlimited AtomicModels = 10000 -TimeModels = unlimited +TimeModels = 10000 ClumpModels = unlimited VehicleModels = unlimited PedModels = unlimited @@ -69,7 +69,7 @@ MemoryAvailable = 30% [GTA3LIMITS] StreamingInfo = 6350 TxdStore = 850 -ExtraObjectsDir = 128 +ExtraObjectsDir = 256 PtrNode = 90000 EntryInfoNode = 30000 Peds = 140 @@ -81,7 +81,7 @@ Dummys = 30000 AudioScriptObj = 256 AlphaEntityList = 2000 VisibleEntityPtrs = unlimited -TimeModels = unlimited +TimeModels = 10000 OutsideWorldWaterBlocks = 40 Coronas = 5000 FrameLimit = 30 diff --git a/premake5.bat b/premake5.bat new file mode 100644 index 0000000..18298f5 --- /dev/null +++ b/premake5.bat @@ -0,0 +1 @@ +premake5 vs2019 \ No newline at end of file diff --git a/premake5.exe b/premake5.exe index 28bccbc..9048d51 100644 Binary files a/premake5.exe and b/premake5.exe differ diff --git a/src/limits/Misc/ExtraObjectsDir.cpp b/src/limits/Misc/ExtraObjectsDir.cpp index 4116ea7..a9ae44c 100644 --- a/src/limits/Misc/ExtraObjectsDir.cpp +++ b/src/limits/Misc/ExtraObjectsDir.cpp @@ -2,24 +2,99 @@ using namespace injector; + +struct CDirectory +{ + struct DirectoryInfo { + uint32_t offset; + uint32_t size; + char name[24]; + }; + DirectoryInfo *entries; + int32_t maxFiles; + int32_t numFiles; + + void AddItem(DirectoryInfo *dirinfo, int imgid); + int FindItem(char *name); +}; +int +CDirectory::FindItem(char *name) +{ + int i; + for (i = 0; i < numFiles; i++) + if (strcmpi(entries[i].name, name) == 0) + return i; + return -1; +} +void +CDirectory::AddItem(DirectoryInfo *dirinfo, int imgid) +{ + int i; + DirectoryInfo dirinfo2 = *dirinfo; + dirinfo2.offset |= imgid << 24; + i = FindItem(dirinfo->name); + if (i < 0) + entries[numFiles++] = dirinfo2; +} + +void __declspec(naked) dirEntryHookVC() +{ + _asm + { + mov edx, [esp + 40h + 0x8] // fileID + lea eax, [esp + 40h - 0x34] // dir entry + mov ecx, ds:0xA10730 // CStreaming::ms_pExtraObjectsDir + push edx + push eax + call CDirectory::AddItem + + push dword ptr 0x40FCA2 + retn + } +} + +void __declspec(naked) dirEntryHookIII() +{ + _asm + { + mov edx, [esp + 48h + 0x8] // fileID + lea eax, [esp + 48h - 0x3C] // dir entry + mov ecx, ds:0x95CB90 // CStreaming::ms_pExtraObjectsDir + push edx + push eax + call CDirectory::AddItem + + push dword ptr 0x406F30 + retn + } +} + class ExtraObjectsDirIII : public SimpleAdjuster { - public: - const char* GetLimitName() { return GetGVM().IsIII()? "ExtraObjectsDir" : nullptr; } - void ChangeLimit(int, const std::string& value) - { - int n = std::stoi(value); - WriteMemory(0x4069D2 + 1, n, true); - } +public: + const char* GetLimitName() { return GetGVM().IsIII() ? "ExtraObjectsDir" : nullptr; } + void ChangeLimit(int, const std::string& value) + { + int n = std::stoi(value); + WriteMemory(0x4069D2 + 1, n, true); + + //for skin and bones mod + injector::MakeJMP(0x406F20, dirEntryHookIII, true); + + // ignore txd.img + injector::MakeJMP(0x48C12E, 0x48C14C, true); + } } ExtraObjectsDirIII; class ExtraObjectsDirVC : public SimpleAdjuster { - public: - const char* GetLimitName() { return GetGVM().IsVC()? "ExtraObjectsDir" : nullptr; } - void ChangeLimit(int, const std::string& value) - { - int n = std::stoi(value); - WriteMemory(0x4106E9 + 1, n, true); - } +public: + const char* GetLimitName() { return GetGVM().IsVC() ? "ExtraObjectsDir" : nullptr; } + void ChangeLimit(int, const std::string& value) + { + int n = std::stoi(value); + WriteMemory(0x4106E9 + 1, n, true); + + injector::MakeJMP(0x40FC92, dirEntryHookVC, true); + } } ExtraObjectsDirVC; diff --git a/src/shared/injector/LICENSE b/src/shared/injector/LICENSE new file mode 100644 index 0000000..65960fb --- /dev/null +++ b/src/shared/injector/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2012-2014 LINK/2012 + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + diff --git a/src/shared/injector/assembly.hpp b/src/shared/injector/assembly.hpp index 622f57e..4b63ac2 100644 --- a/src/shared/injector/assembly.hpp +++ b/src/shared/injector/assembly.hpp @@ -43,15 +43,15 @@ namespace injector // The ordering is very important, don't change // The first field is the last to be pushed and first to be poped - // PUSHFD / POPFD - uint32_t ef; - // PUSHAD/POPAD -- must be the lastest fields (because of esp) union { uint32_t arr[8]; struct { uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; }; }; + + // PUSHFD / POPFD + uint32_t ef; enum reg_name { reg_edi, reg_esi, reg_ebp, reg_esp, reg_ebx, reg_edx, reg_ecx, reg_eax @@ -100,9 +100,9 @@ namespace injector _asm { // Construct the reg_pack structure on the stack - pushad // Pushes general purposes registers to reg_pack - add [esp+12], 4 // Add 4 to reg_pack::esp 'cuz of our return pointer, let it be as before this func is called pushfd // Pushes EFLAGS to reg_pack + pushad // Pushes general purposes registers to reg_pack + add dword ptr[esp+12], 8 // Add 4 to reg_pack::esp 'cuz of our return pointer, let it be as before this func is called // Call wrapper sending reg_pack as parameter push esp @@ -110,9 +110,9 @@ namespace injector add esp, 4 // Destructs the reg_pack from the stack - sub [esp+12+4], 4 // Fix reg_pack::esp before popping it (doesn't make a difference though) (+4 because eflags) - popfd // Warning: Do not use any instruction that changes EFLAGS after this (-> sub affects EF!! <-) + sub dword ptr[esp+12], 8 // Fix reg_pack::esp before popping it (doesn't make a difference though) (+4 because eflags) popad + popfd // Warning: Do not use any instruction that changes EFLAGS after this (-> sub affects EF!! <-) // Back to normal flow ret @@ -152,14 +152,14 @@ namespace injector template void MakeInline(FuncT func) { - static FuncT static_func = func; // Stores the func object - static_func = func; // + static std::unique_ptr static_func; + static_func.reset(new FuncT(std::move(func))); // Encapsulates the call to static_func struct Caps { void operator()(reg_pack& regs) - { static_func(regs); } + { (*static_func)(regs); } }; // Does the actual MakeInline diff --git a/src/shared/injector/calling.hpp b/src/shared/injector/calling.hpp index ebf3bdf..0a1dd62 100644 --- a/src/shared/injector/calling.hpp +++ b/src/shared/injector/calling.hpp @@ -26,6 +26,7 @@ #pragma once #include "injector.hpp" #include +#include #if __cplusplus >= 201103L || _MSC_VER >= 1800 // MSVC 2013 #else diff --git a/src/shared/injector/gvm/gvm.hpp b/src/shared/injector/gvm/gvm.hpp index 48b3444..27a0562 100644 --- a/src/shared/injector/gvm/gvm.hpp +++ b/src/shared/injector/gvm/gvm.hpp @@ -42,43 +42,45 @@ namespace injector #ifndef INJECTOR_GVM_DUMMY class game_version_manager { - public: - // Set this if you would like that MessagesBox contain PluginName as caption - const char* PluginName; + public: + // Set this if you would like that MessagesBox contain PluginName as caption + const char* PluginName; - private: - char game, region, major, minor, cracker, steam; - - public: - game_version_manager() - { - #ifdef INJECTOR_GVM_PLUGIN_NAME - PluginName = INJECTOR_GVM_PLUGIN_NAME; - #else + private: + char game, region, major, minor, majorRevision, minorRevision, cracker, steam; + + public: + game_version_manager() + { + #ifdef INJECTOR_GVM_PLUGIN_NAME + PluginName = INJECTOR_GVM_PLUGIN_NAME; + #else PluginName = "Unknown Plugin Name"; #endif - - this->Clear(); - } + + this->Clear(); + } - // Clear any information about game version - void Clear() - { - game = region = major = minor = cracker = steam = 0; - } + // Clear any information about game version + void Clear() + { + game = region = major = minor = majorRevision = minorRevision = cracker = steam = 0; + } - // Checks if I don't know the game we are attached to - bool IsUnknown() { return game == 0; } - // Checks if this is the steam version - bool IsSteam() { return steam != 0; } - // Gets the game we are attached to (0, '3', 'V', 'S') - char GetGame() { return game; } - // Gets the region from the game we are attached to (0, 'U', 'E'); - char GetRegion() { return region; } - // Get major and minor version of the game (e.g. [major = 1, minor = 0] = 1.0) - int GetMajorVersion() { return major; } - int GetMinorVersion() { return minor; } + // Checks if I don't know the game we are attached to + bool IsUnknown() { return game == 0; } + // Checks if this is the steam version + bool IsSteam() { return steam != 0; } + // Gets the game we are attached to (0, '3', 'V', 'S', 'I', 'E') + char GetGame() { return game; } + // Gets the region from the game we are attached to (0, 'U', 'E'); + char GetRegion() { return region; } + // Get major and minor version of the game (e.g. [major = 1, minor = 0] = 1.0) + int GetMajorVersion() { return major; } + int GetMinorVersion() { return minor; } + int GetMajorRevisionVersion() { return majorRevision; } + int GetMinorRevisionVersion() { return minorRevision; } bool IsHoodlum() { return cracker == 'H'; } @@ -90,11 +92,13 @@ class game_version_manager bool IsIII() { return game == '3'; } bool IsVC () { return game == 'V'; } bool IsSA () { return game == 'S'; } + bool IsIV () { return game == 'I'; } + bool IsEFLC(){ return game == 'E'; } - // Detects game, region and version; returns false if could not detect it - bool Detect(); + // Detects game, region and version; returns false if could not detect it + bool Detect(); - // Gets the game version as text, the buffer must contain at least 32 bytes of space. + // Gets the game version as text, the buffer must contain at least 32 bytes of space. char* GetVersionText(char* buffer) { if(this->IsUnknown()) @@ -103,34 +107,34 @@ class game_version_manager return buffer; } - const char* g = this->IsIII()? "III" : this->IsVC()? "VC" : this->IsSA()? "SA" : "UNK"; + const char* g = this->IsIII() ? "III" : this->IsVC() ? "VC" : this->IsSA() ? "SA" : this->IsIV() ? "IV" : this->IsEFLC() ? "EFLC" : "UNK"; const char* r = this->IsUS()? "US" : this->IsEU()? "EURO" : "UNK_REGION"; const char* s = this->IsSteam()? "Steam" : ""; - sprintf(buffer, "GTA %s %d.%d %s%s", g, major, minor, r, s); + sprintf(buffer, "GTA %s %d.%d.%d.%d %s%s", g, major, minor, majorRevision, minorRevision, r, s); return buffer; } - public: - // Raises a error saying that you could not detect the game version - void RaiseCouldNotDetect() - { - MessageBoxA(0, - "Could not detect the game version\nContact the mod creator!", - PluginName, MB_ICONERROR - ); - } - - // Raises a error saying that the exe version is incompatible (and output the exe name) - void RaiseIncompatibleVersion() - { - char buf[128], v[32]; - sprintf(buf, - "An incompatible exe version has been detected! (%s)\nContact the mod creator!", - GetVersionText(v) - ); - MessageBoxA(0, buf, PluginName, MB_ICONERROR); - } + public: + // Raises a error saying that you could not detect the game version + void RaiseCouldNotDetect() + { + MessageBoxA(0, + "Could not detect the game version\nContact the mod creator!", + PluginName, MB_ICONERROR + ); + } + + // Raises a error saying that the exe version is incompatible (and output the exe name) + void RaiseIncompatibleVersion() + { + char buf[128], v[32]; + sprintf(buf, + "An incompatible exe version has been detected! (%s)\nContact the mod creator!", + GetVersionText(v) + ); + MessageBoxA(0, buf, PluginName, MB_ICONERROR); + } }; #else // INJECTOR_GVM_DUMMY class game_version_manager @@ -193,7 +197,17 @@ class address_manager : public game_version_manager public: // Functors for memory translation: - + + // Translates aslr translator + struct fn_mem_translator_aslr + { + void* operator()(void* p) const + { + static uintptr_t module = (uintptr_t)GetModuleHandle(NULL); + return (void*)((uintptr_t)(p)-(0x400000 - module)); + } + }; + // Translates nothing translator struct fn_mem_translator_nop { diff --git a/src/shared/injector/injector.hpp b/src/shared/injector/injector.hpp index e548f1e..2a95b7d 100644 --- a/src/shared/injector/injector.hpp +++ b/src/shared/injector/injector.hpp @@ -2,7 +2,6 @@ * Injectors - Base Header * * Copyright (C) 2012-2014 LINK/2012 - * Copyright (C) 2014 Deji * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -29,7 +28,7 @@ #include #include #include - +#include "gvm/gvm.hpp" /* The following macros (#define) are relevant on this header: @@ -73,14 +72,14 @@ union auto_pointer friend union memory_pointer_tr; template friend union basic_memory_pointer; - void* p; - uintptr_t a; + void* p; + uintptr_t a; public: - auto_pointer() : p(0) {} + auto_pointer() : p(0) {} auto_pointer(const auto_pointer& x) : p(x.p) {} - explicit auto_pointer(void* x) : p(x) {} - explicit auto_pointer(uint32_t x) : a(x) {} + explicit auto_pointer(void* x) : p(x) {} + explicit auto_pointer(uint32_t x) : a(x) {} bool is_null() const { return this->p != nullptr; } @@ -92,8 +91,8 @@ union auto_pointer template T* get() const { return (T*) this->p; } template T* get_raw() const { return (T*) this->p; } - template - operator T*() const { return reinterpret_cast(p); } + template + operator T*() const { return reinterpret_cast(p); } }; /* @@ -194,7 +193,7 @@ union basic_memory_pointer // Typedefs including memory translator for the above type typedef basic_memory_pointer memory_pointer; typedef basic_memory_pointer memory_pointer_raw; - +typedef basic_memory_pointer memory_pointer_aslr; @@ -270,7 +269,7 @@ union memory_pointer_tr */ inline bool ProtectMemory(memory_pointer_tr addr, size_t size, DWORD protection) { - return VirtualProtect(addr.get(), size, protection, &protection) != 0; + return VirtualProtect(addr.get(), size, protection, &protection) != 0; } /* @@ -280,7 +279,7 @@ inline bool ProtectMemory(memory_pointer_tr addr, size_t size, DWORD protection) */ inline bool UnprotectMemory(memory_pointer_tr addr, size_t size, DWORD& out_oldprotect) { - return VirtualProtect(addr.get(), size, PAGE_EXECUTE_READWRITE, &out_oldprotect) != 0; + return VirtualProtect(addr.get(), size, PAGE_EXECUTE_READWRITE, &out_oldprotect) != 0; } /* @@ -395,7 +394,29 @@ inline T ReadMemory(memory_pointer_tr addr, bool vp = false) return ReadObject(addr, value, vp); } - +/* + * AdjustPointer + * Searches in the range [@addr, @addr + @max_search] for a pointer in the range [@default_base, @default_end] and replaces + * it with the proper offset in the pointer @replacement_base. + * Does memory unprotection if @vp is true. + */ + inline memory_pointer_raw AdjustPointer(memory_pointer_tr addr, + memory_pointer_raw replacement_base, memory_pointer_tr default_base, memory_pointer_tr default_end, + size_t max_search = 8, bool vp = true) + { + scoped_unprotect xprotect(addr, vp? max_search + sizeof(void*) : 0); + for(size_t i = 0; i < max_search; ++i) + { + memory_pointer_raw ptr = ReadMemory(addr + i); + if(ptr >= default_base.get() && ptr <= default_end.get()) + { + auto result = replacement_base + (ptr - default_base.get()); + WriteMemory(addr + i, result.get()); + return result; + } + } + return nullptr; + } @@ -417,7 +438,7 @@ inline memory_pointer_raw GetAbsoluteOffset(int rel_value, memory_pointer_tr end */ inline int GetRelativeOffset(memory_pointer_tr abs_value, memory_pointer_tr end_of_instruction) { - return uintptr_t(abs_value.get() - end_of_instruction.get()); + return uintptr_t(abs_value.get() - end_of_instruction.get()); } /* @@ -426,13 +447,13 @@ inline int GetRelativeOffset(memory_pointer_tr abs_value, memory_pointer_tr end_ */ inline memory_pointer_raw ReadRelativeOffset(memory_pointer_tr at, size_t sizeof_addr = 4, bool vp = true) { - switch(sizeof_addr) - { - case 1: return (GetAbsoluteOffset(ReadMemory (at, vp), at+sizeof_addr)); - case 2: return (GetAbsoluteOffset(ReadMemory(at, vp), at+sizeof_addr)); - case 4: return (GetAbsoluteOffset(ReadMemory(at, vp), at+sizeof_addr)); - } - return nullptr; + switch(sizeof_addr) + { + case 1: return (GetAbsoluteOffset(ReadMemory (at, vp), at+sizeof_addr)); + case 2: return (GetAbsoluteOffset(ReadMemory(at, vp), at+sizeof_addr)); + case 4: return (GetAbsoluteOffset(ReadMemory(at, vp), at+sizeof_addr)); + } + return nullptr; } /* @@ -441,12 +462,12 @@ inline memory_pointer_raw ReadRelativeOffset(memory_pointer_tr at, size_t sizeof */ inline void MakeRelativeOffset(memory_pointer_tr at, memory_pointer_tr dest, size_t sizeof_addr = 4, bool vp = true) { - switch(sizeof_addr) - { - case 1: WriteMemory (at, static_cast (GetRelativeOffset(dest, at+sizeof_addr)), vp); - case 2: WriteMemory(at, static_cast(GetRelativeOffset(dest, at+sizeof_addr)), vp); - case 4: WriteMemory(at, static_cast(GetRelativeOffset(dest, at+sizeof_addr)), vp); - } + switch(sizeof_addr) + { + case 1: WriteMemory (at, static_cast (GetRelativeOffset(dest, at+sizeof_addr)), vp); + case 2: WriteMemory(at, static_cast(GetRelativeOffset(dest, at+sizeof_addr)), vp); + case 4: WriteMemory(at, static_cast(GetRelativeOffset(dest, at+sizeof_addr)), vp); + } } /* @@ -456,23 +477,23 @@ inline void MakeRelativeOffset(memory_pointer_tr at, memory_pointer_tr dest, siz */ inline memory_pointer_raw GetBranchDestination(memory_pointer_tr at, bool vp = true) { - switch(ReadMemory(at, vp)) - { + switch(ReadMemory(at, vp)) + { // We need to handle other instructions (and prefixes) later... - case 0xE8: // call rel - case 0xE9: // jmp rel - return ReadRelativeOffset(at + 1, 4, vp); + case 0xE8: // call rel + case 0xE9: // jmp rel + return ReadRelativeOffset(at + 1, 4, vp); case 0xFF: switch(ReadMemory(at + 1, vp)) { - case 0x15: // call dword ptr [addr] + case 0x15: // call dword ptr [addr] case 0x25: // jmp dword ptr [addr] return *(ReadMemory(at + 2, vp)); } break; - } - return nullptr; + } + return nullptr; } /* @@ -482,10 +503,10 @@ inline memory_pointer_raw GetBranchDestination(memory_pointer_tr at, bool vp = t */ inline memory_pointer_raw MakeJMP(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) { - auto p = GetBranchDestination(at, vp); - WriteMemory(at, 0xE9, vp); - MakeRelativeOffset(at+1, dest, 4, vp); - return p; + auto p = GetBranchDestination(at, vp); + WriteMemory(at, 0xE9, vp); + MakeRelativeOffset(at+1, dest, 4, vp); + return p; } /* @@ -495,10 +516,10 @@ inline memory_pointer_raw MakeJMP(memory_pointer_tr at, memory_pointer_raw dest, */ inline memory_pointer_raw MakeCALL(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) { - auto p = GetBranchDestination(at, vp); - WriteMemory(at, 0xE8, vp); - MakeRelativeOffset(at+1, dest, 4, vp); - return p; + auto p = GetBranchDestination(at, vp); + WriteMemory(at, 0xE8, vp); + MakeRelativeOffset(at+1, dest, 4, vp); + return p; } /* @@ -508,8 +529,8 @@ inline memory_pointer_raw MakeCALL(memory_pointer_tr at, memory_pointer_raw dest */ inline void MakeJA(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true) { - WriteMemory(at, 0x87F0, vp); - MakeRelativeOffset(at+2, dest, 4, vp); + WriteMemory(at, 0x87F0, vp); + MakeRelativeOffset(at+2, dest, 4, vp); } /* @@ -616,7 +637,7 @@ inline memory_pointer_raw raw_ptr(T p) template inline memory_pointer_raw raw_ptr(basic_memory_pointer p) { - return raw_ptr(p.get()); + return raw_ptr(p.get()); } template @@ -625,7 +646,11 @@ inline memory_pointer_raw lazy_ptr() return lazy_pointer::get(); } - +template +inline memory_pointer_aslr aslr_ptr(T p) +{ + return memory_pointer_aslr(p); +} @@ -641,13 +666,13 @@ inline bool game_version_manager::Detect() this->Clear(); // Find NT header - uintptr_t base = (uintptr_t) GetModuleHandleA(NULL);; + uintptr_t base = (uintptr_t) GetModuleHandleA(NULL); IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)(base); IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew); // Look for game and version thought the entry-point // Thanks to Silent for many of the entry point offsets - switch(base + nt->OptionalHeader.AddressOfEntryPoint) + switch (base + nt->OptionalHeader.AddressOfEntryPoint + (0x400000 - base)) { case 0x5C1E70: // GTA III 1.0 game = '3', major = 1, minor = 0, region = 0, steam = false; @@ -700,6 +725,26 @@ inline bool game_version_manager::Detect() game = 'S', major = 3, minor = 0, region = 0, steam = true; return true; + case 0xC965AD: // GTA IV 1.0.0.4 US + game = 'I', major = 1, minor = 0, majorRevision = 0, minorRevision = 4, region = 'U', steam = false; + return true; + + case 0xD0D011: // GTA IV 1.0.0.7 US + game = 'I', major = 1, minor = 0, majorRevision = 0, minorRevision = 7, region = 'U', steam = false; + return true; + + case 0xCF529E: // GTA IV 1.0.0.8 US + game = 'I', major = 1, minor = 0, majorRevision = 0, minorRevision = 8, region = 'U', steam = false; + return true; + + case 0xD0AF06: // GTA EFLC 1.1.2.0 US + game = 'E', major = 1, minor = 1, majorRevision = 2, minorRevision = 0, region = 'U', steam = false; + return true; + + case 0xCF4BAD: // GTA EFLC 1.1.3.0 US + game = 'E', major = 1, minor = 1, majorRevision = 3, minorRevision = 0, region = 'U', steam = false; + return true; + default: return false; }