From 787ba74aac83573cf851d4819ac18828511c0e8a Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Mon, 22 Jun 2026 19:56:07 +0100 Subject: [PATCH] feat(IW4-TU6): implement title version spoofing --- src/dllmain.cpp | 46 ++++++++++++++++++++++++++++++++++++++ src/plugin_manager.cpp | 37 ++++++++++++++----------------- src/plugin_manager.h | 12 ++++++++++ src/xbox360.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++ src/xbox360.h | 2 ++ 5 files changed, 126 insertions(+), 21 deletions(-) diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 031cf77..0bd8a69 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -6,6 +6,11 @@ namespace const DWORD XEXP_FINISH_EXECUTABLE_LOAD_RETAIL = 0x8007AF68; const DWORD XEXP_FINISH_EXECUTABLE_LOAD_DEVKIT = 0x800A17C8; +// TODO: make this nicer +const DWORD IW4_MP_TU6_TIMESTAMP = 0x4BE22338; +const DWORD IW4_MP_TU6_EXECUTION_VERSION = 0x0000060A; +const DWORD IW4_MP_TU9_EXECUTION_VERSION = 0x0000090A; + PluginManager *g_plugin_manager = nullptr; Detour g_xexp_finish_executable_load_detour; bool g_title_terminate_registered = false; @@ -44,6 +49,40 @@ DWORD ResolveCurrentTimeDateStamp() return module != nullptr ? module->TimeDateStamp : 0; } +bool SpoofCurrentTitleVersionIfNeeded(DWORD timestamp) +{ + // Report MW2 TU6 as TU9 so spoof-enabled consoles can join each other on Xbox Live. + if (timestamp != IW4_MP_TU6_TIMESTAMP) + { + return false; + } + + PXEX_EXECUTION_ID execution_id = nullptr; + const NTSTATUS status = XamGetExecutionId(&execution_id); + if (status != 0 || execution_id == nullptr) + { + return false; + } + + if (!MmIsAddressValid(execution_id) || !MmIsAddressValid(&execution_id->Version)) + { + return false; + } + + if (execution_id->TitleID != TITLE_ID_IW4) + { + return false; + } + + if (execution_id->Version != IW4_MP_TU6_EXECUTION_VERSION) + { + return false; + } + + execution_id->Version = IW4_MP_TU9_EXECUTION_VERSION; + return true; +} + void XexpFinishExecutableLoad_Hook(PLDR_DATA_TABLE_ENTRY module, const char *commandLine) { if (g_plugin_manager != nullptr) @@ -56,7 +95,12 @@ void XexpFinishExecutableLoad_Hook(PLDR_DATA_TABLE_ENTRY module, const char *com if (g_plugin_manager != nullptr) { const DWORD timestamp = module != nullptr ? module->TimeDateStamp : 0; + const bool spoofed_title_version = SpoofCurrentTitleVersionIfNeeded(timestamp); g_plugin_manager->OnExecutableLoaded(ResolveCurrentTitleId(), timestamp); + if (spoofed_title_version) + { + xbox::Notify("Title update spoofed: TU6 -> TU9"); + } } } } // namespace @@ -86,6 +130,8 @@ bool DllMain(HANDLE hModule, DWORD reason, LPVOID lpvReserved) return TRUE; } + xbox::ApplySystemPatches(); + ExRegisterTitleTerminateNotification(&g_title_terminate_registration, TRUE); g_title_terminate_registered = true; diff --git a/src/plugin_manager.cpp b/src/plugin_manager.cpp index 0781b63..9b442d0 100644 --- a/src/plugin_manager.cpp +++ b/src/plugin_manager.cpp @@ -1,11 +1,6 @@ #include "pch.h" #include "plugin_manager.h" -enum TitleID : DWORD -{ - DASHBOARD = 0xFFFE07D1, -}; - template std::unique_ptr CreatePlugin() { return make_unique(); @@ -41,7 +36,7 @@ struct GameInfo const GameInfo GAME_INFO[] = { { - 0x415607FF, + TITLE_ID_QOS, 0x49E8DEDE, // Fri Apr 17 20:56:14 2009 "default_mp.xex", "", @@ -49,16 +44,15 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x415607FF, + TITLE_ID_QOS, 0x48D0C2CA, // Wed Sep 17 09:41:46 2008 "default.xex", "", "007: Quantum of Solace SP Title Update #2", &CreatePlugin, }, - { - 0x415607D1, + TITLE_ID_IW2, 0x4456B8A3, // Tue May 2 02:40:51 2006 "default_mp.xex", "", @@ -66,7 +60,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x415607D1, + TITLE_ID_IW2, 0x43E7A218, // Mon Feb 6 19:23:04 2006 "default.xex", "", @@ -74,7 +68,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x415607E6, + TITLE_ID_IW3, 0x4A78A577, // Tue Aug 4 22:17:43 2009 "default_mp.xex", "CoD4 MP 1.4 build 79 nightly Tue Aug 04 2009 01:51:14PM xenon", @@ -82,7 +76,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x415607E6, + TITLE_ID_IW3, 0x46E1E82F, // Sat Sep 8 01:09:19 2007 "default.xex", "CoD4 1.0 build 472 nightly Fri Sep 07 2007 04:59:49PM xenon", @@ -90,7 +84,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x4156081C, + TITLE_ID_T4, 0x4AD7C0EE, // Fri Oct 16 01:40:14 2009 "default_mp.xex", "Call of Duty Multiplayer COD_WaW MP build 5.5.51 CL(0) ZQABUILD1 Thu Oct 15 17:39:03 2009 xenon", @@ -98,7 +92,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x4156081C, + TITLE_ID_T4, 0x4AD7C0DF, // Fri Oct 16 01:39:59 2009 "default.xex", "", @@ -106,7 +100,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x41560817, + TITLE_ID_IW4, 0x4BE22338, // Thu May 6 03:02:32 2010 "default_mp.xex", "IW4 MP 1.4 build 669 latest Wed May 05 2010 06:55:32PM xenon", @@ -114,7 +108,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x41560817, + TITLE_ID_IW4, 0x5B11C269, // Fri Jun 1 23:02:17 2018 "default_mp.xex", "IW4 MP 1.4 build 163842 Tue Feb 08 16:52:00 2011 xenon", @@ -122,7 +116,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x41560817, + TITLE_ID_IW4, 0x5074B056, // Wed Oct 10 00:16:38 2012 "default.xex", "IW4 1.0 build 125545 Fri Sep 25 22:12:21 2009 xenon", @@ -130,7 +124,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x41560855, + TITLE_ID_T5, 0x4E542876, // Tue Aug 23 23:23:50 2011 "default_mp.xex", "", @@ -138,7 +132,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x41560855, + TITLE_ID_T5, 0x4E542875, // Tue Aug 23 23:23:49 2011 "default.xex", "", @@ -146,7 +140,7 @@ const GameInfo GAME_INFO[] = { &CreatePlugin, }, { - 0x415608CB, + TITLE_ID_IW5, 0x5B10A113, // Fri Jun 1 02:27:47 2018 "default_mp.xex", "IW5 MP 1.8 build 388110 Fri Sep 14 00:04:28 2012 xenon", @@ -224,7 +218,7 @@ void PluginManager::OnExecutableLoadStarted() void PluginManager::OnExecutableLoaded(DWORD title_id, DWORD timestamp) { - if (title_id == DASHBOARD) + if (title_id == TITLE_ID_DASHBOARD) { return; } @@ -238,6 +232,7 @@ void PluginManager::OnExecutableLoaded(DWORD title_id, DWORD timestamp) } DbgPrint("[codxe][PluginManager] Game detected: '%s'.\n", info->friendlyVersion); + xbox::Notify(info->friendlyVersion); LoadPlugin(info); } diff --git a/src/plugin_manager.h b/src/plugin_manager.h index 6ac722a..fa584b5 100644 --- a/src/plugin_manager.h +++ b/src/plugin_manager.h @@ -4,6 +4,18 @@ struct GameInfo; +enum TitleID : DWORD +{ + TITLE_ID_DASHBOARD = 0xFFFE07D1, + TITLE_ID_IW2 = 0x415607D1, + TITLE_ID_IW3 = 0x415607E6, + TITLE_ID_IW4 = 0x41560817, + TITLE_ID_IW5 = 0x415608CB, + TITLE_ID_QOS = 0x415607FF, + TITLE_ID_T4 = 0x4156081C, + TITLE_ID_T5 = 0x41560855, +}; + class PluginManager { public: diff --git a/src/xbox360.cpp b/src/xbox360.cpp index ac617f0..110a4dc 100644 --- a/src/xbox360.cpp +++ b/src/xbox360.cpp @@ -14,6 +14,19 @@ void *ResolveFunction(const char *moduleName, unsigned int ordinal) return GetProcAddress(moduleHandle, reinterpret_cast(ordinal)); } +enum XNotifyQueueUIType +{ + XNOTIFYUI_TYPE_GENERIC = 3, + XNOTIFYUI_TYPE_EXCLAIM = 34, +}; + +typedef int (*XNotifyQueueUI_t)(XNotifyQueueUIType type, DWORD user_index, DWORD areas, const WCHAR *display_text, + void *context_data); +static XNotifyQueueUI_t XNotifyQueueUI = reinterpret_cast(ResolveFunction("xam.xex", 656)); + +const DWORD XNOTIFY_USER_INDEX_ANY = 0x000000FF; +const DWORD XNOTIFY_AREA_SYSTEM = 0x00000001; + /** * Check if we are running in Xenia Canary. * @@ -41,6 +54,25 @@ Environment DetectEnvironment() return DetectDevkit() ? ENVIRONMENT_XBOX_DEVKIT : ENVIRONMENT_XBOX_RETAIL; } + +void CopyAsciiToWide(const char *source, WCHAR *destination, size_t destination_count) +{ + if (destination == nullptr || destination_count == 0) + { + return; + } + + size_t i = 0; + if (source != nullptr) + { + for (; source[i] != '\0' && i + 1 < destination_count; i++) + { + destination[i] = static_cast(source[i]); + } + } + + destination[i] = L'\0'; +} } // namespace Environment GetEnvironment() @@ -65,4 +97,22 @@ const char *GetEnvironmentName(Environment environment) } } +void ApplySystemPatches() +{ + // Allow XNotifyQueueUI to be called from system threads. + *(volatile uint16_t *)0x816A3158 = 0x4800; +} + +void Notify(const char *message) +{ + assert(message != nullptr); + + char branded_message[160]; + _snprintf_s(branded_message, sizeof(branded_message), _TRUNCATE, "CoD Xe: %s", message); + + WCHAR display_text[160]; + CopyAsciiToWide(branded_message, display_text, ARRAYSIZE(display_text)); + XNotifyQueueUI(XNOTIFYUI_TYPE_EXCLAIM, XNOTIFY_USER_INDEX_ANY, XNOTIFY_AREA_SYSTEM, display_text, nullptr); +} + } // namespace xbox diff --git a/src/xbox360.h b/src/xbox360.h index 589873a..9a5dc0e 100644 --- a/src/xbox360.h +++ b/src/xbox360.h @@ -12,5 +12,7 @@ enum Environment Environment GetEnvironment(); const char *GetEnvironmentName(Environment environment); +void ApplySystemPatches(); +void Notify(const char *message); } // namespace xbox