diff --git a/README.md b/README.md
index c75df1e0..821ee1a3 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,11 @@
# BOIII ☄️
-Reverse engineering and analysis of Call of Duty: Black Ops 3.
+An attempt at reverse engineering and analyzing of Call of Duty: Black Ops 3.
-## Roadmap
+## Technical Features
- [x] Steam API Emulation
- [x] Steam Integrity Bypass
@@ -24,6 +24,8 @@ Reverse engineering and analysis of Call of Duty: Black Ops 3.
- [x] P2P multiplayer
- [x] Dedicated Servers
+Check out the closed issues for more gameplay related features and fixes that have been added!
+
## Writeups & Articles
- Reverse engineering integrity checks in Black Ops 3
diff --git a/data/gamesettings/mp/gamesettings_escort.cfg b/data/gamesettings/mp/gamesettings_escort.cfg
new file mode 100644
index 00000000..5b713e17
--- /dev/null
+++ b/data/gamesettings/mp/gamesettings_escort.cfg
@@ -0,0 +1,26 @@
+gametype_setting timelimit 5
+gametype_setting scorelimit 0
+gametype_setting roundscorelimit 1
+gametype_setting roundwinlimit 2
+gametype_setting roundlimit 2
+gametype_setting preroundperiod 10
+gametype_setting teamCount 2
+
+gametype_setting shutdownDamage 3
+gametype_setting bootTime 5
+gametype_setting rebootTime 15
+gametype_setting rebootPlayers 0
+gametype_setting movePlayers 1
+gametype_setting robotSpeed 1
+gametype_setting robotShield 0
+
+
+gametype_setting scoreHeroPowerGainFactor 0.788 //Score earned towards Hero Weapons and Abilities are multiplied by this factor
+gametype_setting scoreHeroPowerTimeFactor 0.788
+
+gametype_setting spawntraptriggertime 5
+
+gametype_setting disableVehicleSpawners 1
+
+gametype_setting gameAdvertisementRuleTimeLeft 3.5
+gametype_setting gameAdvertisementRuleRound 3
diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp
index e870dfc5..959d6e57 100644
--- a/src/client/component/auth.cpp
+++ b/src/client/component/auth.cpp
@@ -109,20 +109,29 @@ namespace auth
int send_connect_data_stub(const game::netsrc_t sock, game::netadr_t* adr, const char* data, int len)
{
- std::string buffer{};
-
- const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0;
- if (is_connect_sequence)
+ try
{
- buffer.append("connect");
- buffer.push_back(' ');
- buffer.append(serialize_connect_data(data, len));
+ std::string buffer{};
- data = buffer.data();
- len = static_cast(buffer.size());
+ const auto is_connect_sequence = len >= 7 && strncmp("connect", data, 7) == 0;
+ if (is_connect_sequence)
+ {
+ buffer.append("connect");
+ buffer.push_back(' ');
+ buffer.append(serialize_connect_data(data, len));
+
+ data = buffer.data();
+ len = static_cast(buffer.size());
+ }
+
+ return reinterpret_cast(0x142173600_g)(sock, adr, data, len);
+ }
+ catch (std::exception& e)
+ {
+ printf("Error: %s\n", e.what());
}
- return reinterpret_cast(0x142173600_g)(sock, adr, data, len);
+ return 0;
}
void handle_connect_packet(const game::netadr_t& target, const network::data_view& data)
diff --git a/src/client/component/dedicated_patches.cpp b/src/client/component/dedicated_patches.cpp
index 6e7f249e..80a0245c 100644
--- a/src/client/component/dedicated_patches.cpp
+++ b/src/client/component/dedicated_patches.cpp
@@ -104,6 +104,9 @@ namespace dedicated_patches
utils::hook::jump(0x14052F0F5_g, 0x14052F139_g);
utils::hook::call(0x1402853D7_g, sv_get_player_xuid_stub); // PlayerCmd_GetXuid
+
+ // Stop executing default_dedicated.cfg & language_settings.cfg
+ utils::hook::set(0x1405063C0_g, 0xC3);
}
};
}
diff --git a/src/client/component/game_settings.cpp b/src/client/component/game_settings.cpp
new file mode 100644
index 00000000..0572f0b8
--- /dev/null
+++ b/src/client/component/game_settings.cpp
@@ -0,0 +1,133 @@
+#include
+#include "loader/component_loader.hpp"
+#include "game/game.hpp"
+
+#include
+#include
+#include
+
+namespace gamesettings
+{
+ namespace
+ {
+ //
+ std::unordered_map game_settings_files;
+
+ std::string get_game_settings_name(const std::vector& sub_strings)
+ {
+ if (sub_strings.size() > 2)
+ {
+ return sub_strings[sub_strings.size() - 2] + '/' + sub_strings[sub_strings.size() - 1];
+ }
+
+ return {};
+ }
+
+ std::string get_game_settings_path(const std::string& name)
+ {
+ const auto itr = game_settings_files.find(name);
+ return (itr == game_settings_files.end()) ? std::string() : itr->second;
+ }
+
+ void search_game_settings_folder(const std::string& game_settings_dir)
+ {
+ if (!utils::io::directory_exists(game_settings_dir))
+ {
+ return;
+ }
+
+ const auto files = utils::io::list_files(game_settings_dir, true);
+
+ for (const auto& path : files)
+ {
+ if (!std::filesystem::is_directory(path))
+ {
+ auto sub_strings = utils::string::split(path.generic_string(), '/');
+ game_settings_files.insert_or_assign(get_game_settings_name(sub_strings), path.generic_string());
+ }
+ }
+ }
+
+ bool has_game_settings_file_on_disk(const char* path)
+ {
+ if (!path)
+ {
+ return false;
+ }
+
+ const auto sub_strings = utils::string::split(path, '/');
+ const auto game_settings_name = get_game_settings_name(sub_strings);
+
+ return !get_game_settings_path(game_settings_name).empty();
+ }
+
+ void cmd_exec_stub(utils::hook::assembler& a)
+ {
+ const auto exec_from_fastfile = a.newLabel();
+ const auto exec_from_disk = a.newLabel();
+
+ a.pushad64();
+
+ a.mov(rcx, r10);
+ a.call_aligned(has_game_settings_file_on_disk);
+ a.cmp(rax, 1);
+;
+ a.popad64();
+
+ a.jnz(exec_from_fastfile);
+
+ a.bind(exec_from_disk);
+ a.jmp(game::select(0x1420ED087, 0x1404F855E));
+
+ a.bind(exec_from_fastfile);
+ a.lea(rdx, ptr(rsp, (game::is_server() ? 0x30 : 0x40)));
+ a.jmp(game::select(0x1420ED007, 0x1404F853F));
+ }
+
+ int read_file_stub(const char* qpath, void** buffer)
+ {
+ const auto sub_strings = utils::string::split(qpath, '/');
+ const auto game_settings_name = get_game_settings_name(sub_strings);
+
+ std::string gamesettings_data;
+ utils::io::read_file(get_game_settings_path(game_settings_name), &gamesettings_data);
+
+ if (!gamesettings_data.empty())
+ {
+ ++(*game::fs_loadStack);
+
+ auto len = static_cast(gamesettings_data.length());
+ auto buf = game::FS_AllocMem(len + 1);
+
+ *buffer = buf;
+ gamesettings_data.copy(reinterpret_cast(*buffer), len);
+ buf[len] = '\0';
+
+ return len;
+ }
+
+ return utils::hook::invoke(game::select(0x1422A48D0, 0x140564F70), qpath, buffer);
+ }
+
+ void search_gamesettings_files_on_disk()
+ {
+ const utils::nt::library host{};
+
+ search_game_settings_folder((game::get_appdata_path() / "data/gamesettings").string());
+ search_game_settings_folder((host.get_folder() / "boiii/gamesettings").string());
+ }
+ }
+
+ struct component final : generic_component
+ {
+ void post_unpack() override
+ {
+ search_gamesettings_files_on_disk();
+
+ utils::hook::call(game::select(0x1420ED0A1, 0x1404F857D), read_file_stub);
+ utils::hook::jump(game::select(0x1420ED002, 0x1404F853A), utils::hook::assemble(cmd_exec_stub));
+ }
+ };
+};
+
+REGISTER_COMPONENT(gamesettings::component)
diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp
index 9a722405..c517e862 100644
--- a/src/client/component/network.cpp
+++ b/src/client/component/network.cpp
@@ -256,6 +256,38 @@ namespace network
return a.port == b.port && a.addr == b.addr;
}
+ int net_sendpacket_stub(const game::netsrc_t sock, const int length, const char* data, const game::netadr_t* to)
+ {
+ printf("Sending packet of size: %X\n", length);
+
+ if (to->type != game::NA_RAWIP)
+ {
+ printf("NET_SendPacket: bad address type\n");
+ return 0;
+ }
+
+ const auto s = *game::ip_socket;
+ if (!s || sock > game::NS_MAXCLIENTS)
+ {
+ return 0;
+ }
+
+ sockaddr_in address{};
+ address.sin_family = AF_INET;
+ address.sin_port = htons(to->port);
+ address.sin_addr.s_addr = htonl(((to->ipv4.c | ((to->ipv4.b | (to->ipv4.a << 8)) << 8)) << 8) | to->ipv4.d);
+
+ const auto size = static_cast(length);
+
+ std::vector buffer{};
+ buffer.resize(size + 1);
+ buffer[0] = static_cast((static_cast(sock) & 0xF) | ((to->localNetID & 0xF) << 4));
+ memcpy(buffer.data() + 1, data, size);
+
+ return sendto(s, buffer.data(), static_cast(buffer.size()), 0, reinterpret_cast(&address),
+ sizeof(address));
+ }
+
struct component final : generic_component
{
void post_unpack() override
@@ -268,6 +300,9 @@ namespace network
// skip checksum verification
utils::hook::set(game::select(0x14233249E, 0x140596F2E), 0); // don't add checksum to packet
+ // Recreate NET_SendPacket to increase max packet size
+ utils::hook::jump(game::select(0x1423323B0, 0x140596E40), net_sendpacket_stub);
+
utils::hook::set(game::select(0x14134C6E0, 0x14018E574), 5);
// set initial connection state to challenging
diff --git a/src/client/component/profile_infos.cpp b/src/client/component/profile_infos.cpp
index 597be96a..16bd8dbc 100644
--- a/src/client/component/profile_infos.cpp
+++ b/src/client/component/profile_infos.cpp
@@ -100,6 +100,10 @@ namespace profile_infos
}
else
{
+#ifdef DEV_BUILD
+ printf("Erasing profile info: %llX\n", i->first);
+#endif
+
i = profiles.erase(i);
}
}
@@ -126,6 +130,10 @@ namespace profile_infos
return;
}
+#ifdef DEV_BUILD
+ printf("Adding profile info: %llX\n", user_id);
+#endif
+
profile_mapping.access([&](profile_map& profiles)
{
profiles[user_id] = info;
@@ -202,7 +210,17 @@ namespace profile_infos
if (profile_entry != profiles.end())
{
result = profile_entry->second;
+
+#ifdef DEV_BUILD
+ printf("Requesting profile info: %llX - good\n", user_id);
+#endif
}
+#ifdef DEV_BUILD
+ else
+ {
+ printf("Requesting profile info: %llX - bad\n", user_id);
+ }
+#endif
return result;
});
diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp
index 54dde0c0..ca9b7904 100644
--- a/src/client/game/structs.hpp
+++ b/src/client/game/structs.hpp
@@ -689,6 +689,7 @@ namespace game
byte color[4];
const dvar_t* indirect[3];
} value;
+
uint64_t encryptedValue;
};
@@ -699,26 +700,31 @@ namespace game
int stringCount;
const char** strings;
} enumeration;
+
struct
{
int min;
int max;
} integer;
+
struct
{
int64_t min;
int64_t max;
} integer64;
+
struct
{
uint64_t min;
uint64_t max;
} unsignedInt64;
+
struct
{
float min;
float max;
} value;
+
struct
{
vec_t min;
@@ -1028,7 +1034,7 @@ namespace game
JoinResult joinResult;
};
-
+#ifdef __cplusplus
namespace hks
{
struct lua_State;
@@ -1051,7 +1057,7 @@ namespace game
typedef size_t hksSize;
typedef void* (*lua_Alloc)(void*, void*, size_t, size_t);
- typedef hksInt32(*lua_CFunction)(lua_State*);
+ typedef hksInt32 (*lua_CFunction)(lua_State*);
struct GenericChunkHeader
{
@@ -1108,11 +1114,14 @@ namespace game
TNUMBER = 0x3,
TSTRING = 0x4,
TTABLE = 0x5,
- TFUNCTION = 0x6, // idk
+ TFUNCTION = 0x6,
+ // idk
TUSERDATA = 0x7,
TTHREAD = 0x8,
- TIFUNCTION = 0x9, // Lua function
- TCFUNCTION = 0xA, // C function
+ TIFUNCTION = 0x9,
+ // Lua function
+ TCFUNCTION = 0xA,
+ // C function
TUI64 = 0xB,
TSTRUCT = 0xC,
NUM_TYPE_OBJECTS = 0xE,
@@ -1294,7 +1303,7 @@ namespace game
int _m_isHksGlobalMemoTestingMode;
HksCompilerSettings_BytecodeSharingFormat m_bytecodeSharingFormat;
HksCompilerSettings_IntLiteralOptions m_enableIntLiterals;
- int(*m_debugMap)(const char*, int);
+ int (*m_debugMap)(const char*, int);
};
enum HksBytecodeSharingMode : __int64
@@ -1504,7 +1513,7 @@ namespace game
void* m_profiler;
RuntimeProfileData m_runProfilerData;
HksCompilerSettings m_compilerSettings;
- int(*m_panicFunction)(lua_State*);
+ int (*m_panicFunction)(lua_State*);
void* m_luaplusObjectList;
int m_heapAssertionFrequency;
int m_heapAssertionCount;
@@ -1533,6 +1542,7 @@ namespace game
HksError m_error;
};
}
+#endif
typedef uint32_t ScrVarCanonicalName_t;
@@ -1556,19 +1566,23 @@ namespace game
char __pad4[0x29DAC];
};
+#ifdef __cplusplus
static_assert(sizeof(client_s) == 0xE5110);
static_assert(offsetof(game::client_s, address) == 0x2C);
static_assert(offsetof(game::client_s, xuid) == 0x55C8);
static_assert(offsetof(game::client_s, guid) == 0xBB354);
static_assert(offsetof(game::client_s, bIsTestClient) == 0xBB360);
+#endif
struct client_s_cl : client_s
{
char __pad1_0[0x60];
};
+#ifdef __cplusplus
static_assert(sizeof(client_s_cl) == 0xE5170);
+#endif
enum scriptInstance_t
{
@@ -1598,7 +1612,9 @@ namespace game
unsigned char __pad1[0x2A0];
};
+#ifdef __cplusplus
static_assert(sizeof(gentity_s) == 0x4F8);
+#endif
enum workshop_type
{
@@ -1623,7 +1639,9 @@ namespace game
workshop_type type;
};
+#ifdef __cplusplus
static_assert(sizeof(workshop_data) == 0x4C8);
+#endif
struct DDLMember
{
@@ -1695,7 +1713,7 @@ namespace game
};
struct DDLContext;
- typedef void(* DDLWriteCB)(DDLContext*, void*);
+ typedef void (* DDLWriteCB)(DDLContext*, void*);
struct DDLContext
{
diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp
index 0fe15b80..820d68c6 100644
--- a/src/client/game/symbols.hpp
+++ b/src/client/game/symbols.hpp
@@ -35,6 +35,7 @@ namespace game
WEAK symbol Com_SwitchMode{
0x14214A4D0
};
+ WEAK symbol Com_LoadRawTextFile{0x1420F61B0};
WEAK symbol Cbuf_AddText{0x1420EC010, 0x1404F75B0};
WEAK symbol Cbuf_ExecuteBuffer{
@@ -184,6 +185,9 @@ namespace game
WEAK symbol SV_Cmd_TokenizeString{0x1420EF130, 0x1404FA6C0};
WEAK symbol SV_Cmd_EndTokenizedString{0x1420EF0E0, 0x1404FA670};
+ // FS
+ WEAK symbol FS_AllocMem{0x1422AC9F0, 0x14056C340};
+
// Utils
WEAK symbol I_CleanStr{0x1422E9050, 0x140580E80};
@@ -202,6 +206,8 @@ namespace game
WEAK symbol s_dvarPool{0x157AC6220, 0x14A3CB620};
WEAK symbol g_dvarCount{0x157AC61CC, 0x14A3CB5FC};
+ WEAK symbol fs_loadStack{0x157A65310, 0x14A39C650};
+
// Client and dedi struct size differs :(
WEAK symbol svs_clients_cl{0x1576F9318, 0};
WEAK symbol svs_clients{0x0, 0x14A178E98};