diff --git a/iw3xe.vcxproj b/iw3xe.vcxproj index ead56fc..54dcb75 100644 --- a/iw3xe.vcxproj +++ b/iw3xe.vcxproj @@ -128,6 +128,12 @@ + + + + + + @@ -217,6 +223,16 @@ + + + + + + + + + + diff --git a/resources/t6/_codxe/codxe.json b/resources/t6/_codxe/codxe.json new file mode 100644 index 0000000..08be6c6 --- /dev/null +++ b/resources/t6/_codxe/codxe.json @@ -0,0 +1,3 @@ +{ + "active_mod": "example" +} diff --git a/resources/t6/_codxe/mods/example/maps/mp/gametypes/_clientids.gsc b/resources/t6/_codxe/mods/example/maps/mp/gametypes/_clientids.gsc new file mode 100644 index 0000000..dad5db2 --- /dev/null +++ b/resources/t6/_codxe/mods/example/maps/mp/gametypes/_clientids.gsc @@ -0,0 +1,66 @@ +init() +{ + level.clientid = 0; + level thread onPlayerConnect(); +} + +onPlayerConnect() +{ + for (;;) + { + level waittill("connecting", player); + + player.clientid = level.clientid; + level.clientid++; + + player thread onPlayerSpawned(); + } +} + +onPlayerSpawned() +{ + self endon("disconnect"); + level endon("game_ended"); + + for (;;) + { + self waittill("spawned_player"); + + self iprintlnbold(rainbow_text("Welcome to the Example Mod !")); + } +} + +rainbow_text(str) +{ + if (!isDefined(str)) + return ""; + + colors = []; + colors[colors.size] = "^1"; // Red + colors[colors.size] = "^2"; // Green + colors[colors.size] = "^3"; // Yellow + colors[colors.size] = "^4"; // Blue + colors[colors.size] = "^5"; // Cyan + colors[colors.size] = "^6"; // Pink + colors[colors.size] = "^7"; // White + + rainbow = ""; + colorCount = colors.size; + colorIndex = 0; + + for (i = 0; i < str.size; i++) + { + ch = str[i]; + if (ch == " ") + { + rainbow += ch; + continue; + } + + color = colors[colorIndex % colorCount]; + rainbow += color + ch; + colorIndex++; + } + + return rainbow; +} diff --git a/resources/t6/_codxe/mods/example/maps/mp/gametypes/_clientids.gscbin b/resources/t6/_codxe/mods/example/maps/mp/gametypes/_clientids.gscbin new file mode 100644 index 0000000..43f7a3b Binary files /dev/null and b/resources/t6/_codxe/mods/example/maps/mp/gametypes/_clientids.gscbin differ diff --git a/resources/t6/tu00000002_00000000 b/resources/t6/tu00000002_00000000 new file mode 100644 index 0000000..409098d Binary files /dev/null and b/resources/t6/tu00000002_00000000 differ diff --git a/resources/xenia/plugins/415608C3/plugins.toml b/resources/xenia/plugins/415608C3/plugins.toml new file mode 100644 index 0000000..2d32a41 --- /dev/null +++ b/resources/xenia/plugins/415608C3/plugins.toml @@ -0,0 +1,19 @@ +title_name = "Call of Duty: Black Ops II" +title_id = "415608C3" # AV-2243 +#media_id = "05C7492A" # Disc (World): http://redump.org/disc/37078/ + +# TU18 + +[[plugin]] +author = "mo" +name = "iw3xe" +file = "iw3xe.xex" +hash = "B56F16A83B7E7CCC" # default.xex +is_enabled = true + +[[plugin]] +author = "mo" +name = "iw3xe" +file = "iw3xe.xex" +hash = "5699D5C3866AD7DF" # default_mp.xex +is_enabled = true diff --git a/src/common.h b/src/common.h index 94435af..a4c0738 100644 --- a/src/common.h +++ b/src/common.h @@ -70,6 +70,15 @@ #include "game/t5/sp/structs.h" #include "game/t5/sp/symbols.h" +// T6-specific includes +#include "game/t6/mp/main.h" +#include "game/t6/mp/structs.h" +#include "game/t6/mp/symbols.h" + +#include "game/t6/sp/main.h" +#include "game/t6/sp/structs.h" +#include "game/t6/sp/symbols.h" + // QOS-specific includes #include "game/qos/sp/main.h" #include "game/qos/sp/structs.h" diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 2d11eb8..f40dd62 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -8,6 +8,7 @@ enum GameTitleId GAME_TITLE_ID_IW4 = 0x41560817, // Call of Duty: Modern Warfare 2 GAME_TITLE_ID_QOS = 0x415607FF, // 007: Quantum of Solace GAME_TITLE_ID_T5 = 0x41560855, // Call of Duty: Black Ops + GAME_TITLE_ID_T6 = 0x415608C3, // Call of Duty: Black Ops II }; void monitor_title_id() @@ -145,6 +146,28 @@ void monitor_title_id() } return; } + else if (current_title_id == GAME_TITLE_ID_T6) + { + if (strncmp((char *)0x8201D15C, "startSingleplayer", 17) == 0) + { + if (!in_xenia) + Sleep(1000); + xbox::show_notification(L"CODxe - T6 Singleplayer Detected"); + t6::sp::init(); + } + else if (strncmp((char *)0x82003580, "multiplayer", 11) == 0) + { + if (!in_xenia) + Sleep(1000); + xbox::show_notification(L"CODxe - T6 Multiplayer Detected"); + t6::mp::init(); + } + else + { + xbox::show_notification(L"CODxe - T6 Unsupported Executable"); + } + return; + } Sleep(50); } diff --git a/src/game/t6/mp/components/scr_parser.cpp b/src/game/t6/mp/components/scr_parser.cpp new file mode 100644 index 0000000..af1d750 --- /dev/null +++ b/src/game/t6/mp/components/scr_parser.cpp @@ -0,0 +1,95 @@ +#include "scr_parser.h" +#include "common.h" + +namespace t6 +{ + namespace mp + { + + // Detour Load_ScriptParseTreeAsset_Detour; + // void Load_ScriptParseTreeAsset_Hook(ScriptParseTree **a1) + // { + // ScriptParseTree *scriptParseTree = *a1; + // DbgPrint("GSCLoader: Loading script %s\n", scriptParseTree->name); + + // // DbgPrint("GSCLoader: Dumping script %s\n", scriptParseTree->name); + // // std::string dumpPath = std::string(DUMP_DIR) + "\\" + scriptParseTree->name; + // // std::replace(dumpPath.begin(), dumpPath.end(), '/', '\\'); + // // filesystem::write_file_to_disk(dumpPath.c_str(), scriptParseTree->buffer, scriptParseTree->len); + // // DbgPrint("GSCLoader: Dumped script to %s\n", dumpPath.c_str()); + + // Config config; + // LoadConfigFromFile(CONFIG_PATH, config); + // std::string modBasePath = config.GetModBasePath(); + // if (!modBasePath.empty()) + // { + // std::string overridePath = modBasePath + "\\" + scriptParseTree->name + "bin"; + // DbgPrint("GSCLoader: Checking for override script at %s\n", overridePath.c_str()); + // } + + // Load_ScriptParseTreeAsset_Detour.GetOriginal()(a1); + // } + + Detour Load_ScriptParseTreeAsset_Detour; + + void Load_ScriptParseTreeAsset_Hook(ScriptParseTree **a1) + { + ScriptParseTree *scriptParseTree = *a1; + DbgPrint("GSCLoader: Loading script %s\n", scriptParseTree->name); + + Config config; + LoadConfigFromFile(CONFIG_PATH, config); + std::string modBasePath = config.GetModBasePath(); + if (!modBasePath.empty()) + { + std::string overridePath = modBasePath + "\\" + scriptParseTree->name + "bin"; + std::replace(overridePath.begin(), overridePath.end(), '/', '\\'); + DbgPrint("GSCLoader: Checking for override script at %s\n", overridePath.c_str()); + + HANDLE file = CreateFileA( + overridePath.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file != INVALID_HANDLE_VALUE) + { + DWORD fileSize = GetFileSize(file, NULL); + if (fileSize != INVALID_FILE_SIZE && fileSize > 0) + { + char *buffer = new char[fileSize]; + DWORD bytesRead = 0; + if (ReadFile(file, buffer, fileSize, &bytesRead, NULL) && bytesRead == fileSize) + { + scriptParseTree->buffer = buffer; + scriptParseTree->len = fileSize; + DbgPrint("GSCLoader: Overridden script loaded (%d bytes)\n", fileSize); + } + else + { + delete[] buffer; + DbgPrint("GSCLoader: Failed to read override script\n"); + } + } + CloseHandle(file); + } + } + + Load_ScriptParseTreeAsset_Detour.GetOriginal()(a1); + } + + scr_parser::scr_parser() + { + Load_ScriptParseTreeAsset_Detour = Detour(Load_ScriptParseTreeAsset, Load_ScriptParseTreeAsset_Hook); + Load_ScriptParseTreeAsset_Detour.Install(); + } + + scr_parser::~scr_parser() + { + Load_ScriptParseTreeAsset_Detour.Remove(); + } + } +} diff --git a/src/game/t6/mp/components/scr_parser.h b/src/game/t6/mp/components/scr_parser.h new file mode 100644 index 0000000..34c6e5c --- /dev/null +++ b/src/game/t6/mp/components/scr_parser.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common.h" + +namespace t6 +{ + namespace mp + { + class scr_parser : public Module + { + public: + scr_parser(); + ~scr_parser(); + + const char *get_name() override { return "scr_parser"; }; + }; + } +} diff --git a/src/game/t6/mp/main.cpp b/src/game/t6/mp/main.cpp new file mode 100644 index 0000000..78e8d97 --- /dev/null +++ b/src/game/t6/mp/main.cpp @@ -0,0 +1,32 @@ +#include "components/scr_parser.h" +#include "main.h" +#include "common.h" + +namespace t6 +{ + namespace mp + { + std::vector components; + + void RegisterComponent(Module *module) + { + DbgPrint("T4 SP: Component registered: %s\n", module->get_name()); + components.push_back(module); + } + + void init() + { + DbgPrint("t6 SP: Registering modules\n"); + RegisterComponent(new scr_parser()); + } + + void shutdown() + { + // Clean up in reverse order + for (auto it = components.rbegin(); it != components.rend(); ++it) + delete *it; + + components.clear(); + } + } +} diff --git a/src/game/t6/mp/main.h b/src/game/t6/mp/main.h new file mode 100644 index 0000000..4d666ac --- /dev/null +++ b/src/game/t6/mp/main.h @@ -0,0 +1,10 @@ +#pragma once + +namespace t6 +{ + namespace mp + { + void init(); + void shutdown(); + } +} diff --git a/src/game/t6/mp/structs.h b/src/game/t6/mp/structs.h new file mode 100644 index 0000000..6e43018 --- /dev/null +++ b/src/game/t6/mp/structs.h @@ -0,0 +1,13 @@ +#pragma once + +namespace t6 +{ + namespace mp + { + enum scriptInstance_t : __int32 + { + SCRIPTINSTANCE_SERVER = 0x0, + SCRIPTINSTANCE_CLIENT = 0x1, + }; + } +} diff --git a/src/game/t6/mp/symbols.h b/src/game/t6/mp/symbols.h new file mode 100644 index 0000000..a30527c --- /dev/null +++ b/src/game/t6/mp/symbols.h @@ -0,0 +1,23 @@ +#pragma once + +#include "structs.h" + +namespace t6 +{ + namespace mp + { + static auto Hunk_AllocateTempMemoryHighInternal = reinterpret_cast(0x8248F1B0); + + static auto Scr_AddSourceBuffer = reinterpret_cast(0x82530128); + + struct ScriptParseTree + { + const char *name; + int len; + char *buffer; + }; + + static auto Load_ScriptParseTreeAsset = reinterpret_cast(0x822CA4C8); + + } +} diff --git a/src/game/t6/sp/components/scr_parser.cpp b/src/game/t6/sp/components/scr_parser.cpp new file mode 100644 index 0000000..81b6f30 --- /dev/null +++ b/src/game/t6/sp/components/scr_parser.cpp @@ -0,0 +1,74 @@ +#include "scr_parser.h" +#include "common.h" + +namespace t6 +{ + namespace sp + { + Detour Scr_AddSourceBuffer_Detour; + + char *Scr_AddSourceBuffer_Hook(scriptInstance_t inst, const char *filename, const char *extFilename, const char *codePos, bool archive) + { + auto callOriginal = [&]() + { + return Scr_AddSourceBuffer_Detour.GetOriginal()(inst, filename, extFilename, codePos, archive); + }; + + Config config; + LoadConfigFromFile(CONFIG_PATH, config); + + if (config.dump_raw) + { + DbgPrint("GSCLoader: Dumping script %s\n", extFilename); + auto contents = callOriginal(); + if (contents) + { + // Dump the script to a file + std::string dumpPath = std::string(DUMP_DIR) + "\\" + extFilename; + std::replace(dumpPath.begin(), dumpPath.end(), '/', '\\'); + filesystem::write_file_to_disk(dumpPath.c_str(), contents, std::strlen(contents)); + DbgPrint("GSCLoader: Dumped script to %s\n", dumpPath.c_str()); + } + + return contents; + } + + // Check if mod is active + std::string modBasePath = config.GetModBasePath(); + if (modBasePath.empty()) + return callOriginal(); + + // Build full path to override file + std::string overridePath = modBasePath + "\\" + extFilename; + std::replace(overridePath.begin(), overridePath.end(), '/', '\\'); + + // Try to load override file + std::string fileContent = filesystem::read_file_to_string(overridePath); + if (fileContent.empty()) + return callOriginal(); + + // Allocate buffer using game's memory allocator + char *buffer = (char *)Hunk_AllocateTempMemoryHighInternal(fileContent.size() + 1); + if (!buffer) + return callOriginal(); + + // Copy content and null terminate + memcpy(buffer, fileContent.c_str(), fileContent.size()); + buffer[fileContent.size()] = '\0'; + + DbgPrint("GSCLoader: Loaded override script: %s\n", overridePath.c_str()); + return buffer; + } + + scr_parser::scr_parser() + { + Scr_AddSourceBuffer_Detour = Detour(Scr_AddSourceBuffer, Scr_AddSourceBuffer_Hook); + Scr_AddSourceBuffer_Detour.Install(); + } + + scr_parser::~scr_parser() + { + Scr_AddSourceBuffer_Detour.Remove(); + } + } +} diff --git a/src/game/t6/sp/components/scr_parser.h b/src/game/t6/sp/components/scr_parser.h new file mode 100644 index 0000000..9b459e4 --- /dev/null +++ b/src/game/t6/sp/components/scr_parser.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common.h" + +namespace t6 +{ + namespace sp + { + class scr_parser : public Module + { + public: + scr_parser(); + ~scr_parser(); + + const char *get_name() override { return "scr_parser"; }; + }; + } +} diff --git a/src/game/t6/sp/main.cpp b/src/game/t6/sp/main.cpp new file mode 100644 index 0000000..4631c48 --- /dev/null +++ b/src/game/t6/sp/main.cpp @@ -0,0 +1,32 @@ +#include "components/scr_parser.h" +#include "main.h" +#include "common.h" + +namespace t6 +{ + namespace sp + { + std::vector components; + + void RegisterComponent(Module *module) + { + DbgPrint("T4 SP: Component registered: %s\n", module->get_name()); + components.push_back(module); + } + + void init() + { + DbgPrint("t6 SP: Registering modules\n"); + RegisterComponent(new scr_parser()); + } + + void shutdown() + { + // Clean up in reverse order + for (auto it = components.rbegin(); it != components.rend(); ++it) + delete *it; + + components.clear(); + } + } +} diff --git a/src/game/t6/sp/main.h b/src/game/t6/sp/main.h new file mode 100644 index 0000000..fc24cf4 --- /dev/null +++ b/src/game/t6/sp/main.h @@ -0,0 +1,18 @@ +#pragma once + +namespace t6 +{ + class Module + { + public: + Module() {}; + virtual ~Module() {}; + virtual const char *get_name() { return "unknown"; } + }; + + namespace sp + { + void init(); + void shutdown(); + } +} diff --git a/src/game/t6/sp/structs.h b/src/game/t6/sp/structs.h new file mode 100644 index 0000000..7a2a84f --- /dev/null +++ b/src/game/t6/sp/structs.h @@ -0,0 +1,13 @@ +#pragma once + +namespace t6 +{ + namespace sp + { + enum scriptInstance_t : __int32 + { + SCRIPTINSTANCE_SERVER = 0x0, + SCRIPTINSTANCE_CLIENT = 0x1, + }; + } +} diff --git a/src/game/t6/sp/symbols.h b/src/game/t6/sp/symbols.h new file mode 100644 index 0000000..9b030d2 --- /dev/null +++ b/src/game/t6/sp/symbols.h @@ -0,0 +1,13 @@ +#pragma once + +#include "structs.h" + +namespace t6 +{ + namespace sp + { + static auto Hunk_AllocateTempMemoryHighInternal = reinterpret_cast(0x823733E8); + + static auto Scr_AddSourceBuffer = reinterpret_cast(0x824267F0); + } +}