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);
+ }
+}