diff --git a/data/cdata/scripts/mp_patches/custom_weapons.gsc b/data/cdata/scripts/mp_patches/custom_weapons.gsc new file mode 100644 index 00000000..4122e199 --- /dev/null +++ b/data/cdata/scripts/mp_patches/custom_weapons.gsc @@ -0,0 +1,336 @@ +main() +{ + replacefunc(maps\mp\gametypes\_class::isvalidprimary, ::isvalidprimary); + replacefunc(maps\mp\gametypes\_class::isvalidsecondary, ::isvalidsecondary); + replacefunc(maps\mp\gametypes\_class::isvalidweapon, ::isvalidweapon); + replacefunc(maps\mp\gametypes\_class::buildweaponname, ::buildweaponname); + replacefunc(maps\mp\gametypes\_weapons::watchweaponchange, ::watchweaponchange); +} + +find_in_table(csv, weap) +{ + rows = tablelookuprownum(csv); + for (i = 0; i < rows; i++) + { + if (tablelookup(csv, i, 0) == weap) + { + return true; + } + } + + return false; +} + +is_custom_weapon(weap) +{ + return find_in_table("mp/customweapons.csv", weap); +} + +watchweaponchange() +{ + self endon("death"); + self endon("disconnect"); + self endon("faux_spawn"); + thread maps\mp\gametypes\_weapons::watchstartweaponchange(); + self.lastdroppableweapon = self.currentweaponatspawn; + self.hitsthismag = []; + var_0 = self getcurrentweapon(); + + if (maps\mp\_utility::iscacprimaryweapon(var_0) && !isdefined(self.hitsthismag[var_0])) + { + self.hitsthismag[var_0] = weaponclipsize(var_0); + } + + self.bothbarrels = undefined; + + if (issubstr(var_0, "ranger")) + { + thread maps\mp\gametypes\_weapons::watchrangerusage(var_0); + } + + var_1 = 1; + + for (;;) + { + if (!var_1) + { + self waittill("weapon_change"); + } + + var_1 = 0; + var_0 = self getcurrentweapon(); + + if (var_0 == "none") + { + continue; + } + + var_2 = getweaponattachments(var_0); + self.has_opticsthermal = 0; + self.has_target_enhancer = 0; + self.has_stock = 0; + self.has_laser = 0; + + if (isdefined(var_2)) + { + foreach (var_4 in var_2) + { + if (var_4 == "opticstargetenhancer") + { + self.has_target_enhancer = 1; + continue; + } + + if (var_4 == "stock") + { + self.has_stock = 1; + continue; + } + + if (var_4 == "lasersight") + { + self.has_laser = 1; + continue; + } + + if (issubstr(var_4, "opticsthermal")) + { + self.has_opticsthermal = 1; + } + } + } + + if (maps\mp\_utility::isbombsiteweapon(var_0)) + { + continue; + } + + var_6 = maps\mp\_utility::getweaponnametokens(var_0); + self.bothbarrels = undefined; + + if (issubstr(var_0, "ranger")) + { + thread maps\mp\gametypes\_weapons::watchrangerusage(var_0); + } + + if (var_6[0] == "alt") + { + var_7 = getsubstr(var_0, 4); + var_0 = var_7; + var_6 = maps\mp\_utility::getweaponnametokens(var_0); + } + + if (var_0 != "none" && var_6[0] != "iw5" && var_6[0] != "iw6" && var_6[0] != "h1" && var_6[0] != "h2") + { + if (maps\mp\_utility::iscacprimaryweapon(var_0) && !isdefined(self.hitsthismag[var_0 + "_mp"])) + { + self.hitsthismag[var_0 + "_mp"] = weaponclipsize(var_0 + "_mp"); + } + } + else if (var_0 != "none" && (var_6[0] == "iw5" || var_6[0] == "iw6" || var_6[0] == "h1" || var_6[0] == "h2")) + { + if (maps\mp\_utility::iscacprimaryweapon(var_0) && !isdefined(self.hitsthismag[var_0])) + { + self.hitsthismag[var_0] = weaponclipsize(var_0); + } + } + + if (maps\mp\gametypes\_weapons::maydropweapon(var_0)) + { + self.lastdroppableweapon = var_0; + } + + self.changingweapon = undefined; + } +} + +buildweaponname(var_0, var_1, var_2, var_3, var_4, var_5) +{ + if (!isdefined(var_0) || var_0 == "none" || var_0 == "") + { + return var_0; + } + + if (!isdefined(level.lettertonumber)) + { + level.lettertonumber = maps\mp\gametypes\_class::makeletterstonumbers(); + } + + var_6 = ""; + + if (issubstr(var_0, "iw5_") || issubstr(var_0, "h1_") || issubstr(var_0, "h2_")) + { + var_7 = var_0 + "_mp"; + var_8 = var_0.size; + + if (issubstr(var_0, "h1_") || issubstr(var_0, "h2_")) + { + var_6 = getsubstr(var_0, 3, var_8); + } + else + { + var_6 = getsubstr(var_0, 4, var_8); + } + } + else + { + var_7 = var_0; + var_6 = var_0; + } + + if (var_7 == "h1_junsho_mp") + { + var_1 = "akimbohidden"; + } + + var_9 = isdefined(var_1) && var_1 != "none"; + var_10 = isdefined(var_2) && var_2 != "none"; + + if (!var_10) + { + var_11 = tablelookuprownum("mp/furniturekits/base.csv", 0, var_7); + + if (var_11 >= 0) + { + var_2 = "base"; + var_10 = 1; + } + } + + if (issubstr(var_0, "h2_")) + { + if (var_9) + { + var_7 += "_" + var_1; + } + } + else if (var_9 || var_10) + { + if (!var_9) + var_1 = "none"; + + if (!var_10) + var_2 = "base"; + + var_7 += ("_a#" + var_1); + var_7 += ("_f#" + var_2); + } + + if (issubstr(var_7, "iw5_") || issubstr(var_7, "h1_") || issubstr(var_7, "h2_")) + { + var_7 = maps\mp\gametypes\_class::buildweaponnamereticle(var_7, var_4); + var_7 = maps\mp\gametypes\_class::buildweaponnameemblem(var_7, var_5); + var_7 = maps\mp\gametypes\_class::buildweaponnamecamo(var_7, var_3); + return var_7; + } + else if (!isvalidweapon(var_7 + "_mp")) + { + return var_0 + "_mp"; + } + else + { + var_7 = maps\mp\gametypes\_class::buildweaponnamereticle(var_7, var_4); + var_7 = maps\mp\gametypes\_class::buildweaponnameemblem(var_7, var_5); + var_7 = maps\mp\gametypes\_class::buildweaponnamecamo(var_7, var_3); + return var_7 + "_mp"; + } +} + +isvalidweapon(var_0, var_1) +{ + if (!isdefined(level.weaponrefs)) + { + level.weaponrefs = []; + + foreach (var_3 in level.weaponlist) + { + level.weaponrefs[var_3] = 1; + } + } + + if (isdefined(level.weaponrefs[var_0])) + { + return 1; + } + + return 0; +} + +isvalidsecondary(var_0, var_1) +{ + if (is_custom_weapon(var_0)) + { + return true; + } + + switch (var_0) + { + case "none": + case "h1_beretta": + case "h1_colt45": + case "h1_deserteagle": + case "h1_deserteagle55": + case "h1_usp": + case "h1_janpst": + case "h1_aprpst": + case "h1_augpst": + case "h1_rpg": + return 1; + default: + return 0; + } + + return 0; +} + + +isvalidprimary(var_0, var_1) +{ + if (is_custom_weapon(var_0)) + { + return true; + } + + switch (var_0) + { + case "h1_ak47": + case "h1_g3": + case "h1_g36c": + case "h1_m14": + case "h1_m16": + case "h1_m4": + case "h1_mp44": + case "h1_xmlar": + case "h1_aprast": + case "h1_augast": + case "h1_ak74u": + case "h1_mp5": + case "h1_p90": + case "h1_skorpion": + case "h1_uzi": + case "h1_febsmg": + case "h1_aprsmg": + case "h1_augsmg": + case "h1_m1014": + case "h1_winchester1200": + case "h1_kam12": + case "h1_junsho": + case "h1_m60e4": + case "h1_rpd": + case "h1_saw": + case "h1_feblmg": + case "h1_junlmg": + case "h1_barrett": + case "h1_dragunov": + case "h1_m21": + case "h1_m40a3": + case "h1_remington700": + case "h1_febsnp": + case "h1_junsnp": + return 1; + default: + return 0; + } + + return 0; +} diff --git a/data/cdata/ui_scripts/custom_weapons/__init__.lua b/data/cdata/ui_scripts/custom_weapons/__init__.lua new file mode 100644 index 00000000..101e1b69 --- /dev/null +++ b/data/cdata/ui_scripts/custom_weapons/__init__.lua @@ -0,0 +1,26 @@ +if (not Engine.InFrontend()) then + return +end + +local cols = { + name = 0, + class = 1, +} + +local csv = "mp/customWeapons.csv" +local rows = Engine.TableGetRowCount(csv) +for i = 0, rows do + local weap = Engine.TableLookupByRow(csv, i, cols.name) + local class = Engine.TableLookupByRow(csv, i, cols.class) + if (type(Cac.Weapons.Primary[class]) == "table") then + table.insert(Cac.Weapons.Primary[class], { + weap, + 0 + }) + elseif (type(Cac.Weapons.Secondary[class]) == "table") then + table.insert(Cac.Weapons.Secondary[class], { + weap, + 0 + }) + end +end diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp index a07793b2..e7f41bd8 100644 --- a/src/client/component/fastfiles.cpp +++ b/src/client/component/fastfiles.cpp @@ -346,6 +346,8 @@ namespace fastfiles // code_pre_gfx + try_load_zone("h1_mod_code_pre_gfx", true); + game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode); } diff --git a/src/client/component/gsc/script_error.cpp b/src/client/component/gsc/script_error.cpp index 20af68f2..0d6ae30e 100644 --- a/src/client/component/gsc/script_error.cpp +++ b/src/client/component/gsc/script_error.cpp @@ -82,6 +82,27 @@ namespace gsc } return res; } + + template + void safe_func() + { + static utils::hook::detour hook; + + const auto stub = []() + { + __try + { + hook.invoke(); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + game::Scr_ErrorInternal(); + } + }; + + const auto ptr = rva + 0_b; + hook.create(ptr, stub); + } } std::optional> find_function(const char* pos) @@ -111,6 +132,8 @@ namespace gsc utils::hook::call(SELECT_VALUE(0x3BD626_b, 0x504606_b), unknown_function_stub); // CompileError (LinkFile) utils::hook::call(SELECT_VALUE(0x3BD672_b, 0x504652_b), unknown_function_stub); // ^ utils::hook::call(SELECT_VALUE(0x3BD75A_b, 0x50473A_b), find_variable_stub); // Scr_EmitFunction + + safe_func<0xBA7A0>(); // fix vlobby cac crash } void pre_destroy() override diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp index a8aee87d..5dd76f54 100644 --- a/src/client/component/gsc/script_loading.cpp +++ b/src/client/component/gsc/script_loading.cpp @@ -109,18 +109,20 @@ namespace gsc return false; } + bool force_load = false; + game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name) { - if (game::VirtualLobby_Loaded()) - { - return nullptr; - } - if (loaded_scripts.contains(real_name)) { return loaded_scripts[real_name]; } + if (game::VirtualLobby_Loaded() && !force_load) + { + return nullptr; + } + std::string source_buffer; if (!read_scriptfile(real_name + ".gsc", &source_buffer) || source_buffer.empty()) { @@ -283,21 +285,27 @@ namespace gsc { utils::hook::invoke(SELECT_VALUE(0x2B9DA0_b, 0x18BC00_b), a1, a2); - if (game::VirtualLobby_Loaded()) - { - return; - } - for (const auto& path : filesystem::get_search_paths()) { - load_scripts(path, "scripts/"); if (game::environment::is_sp()) { load_scripts(path, "scripts/sp/"); + load_scripts(path, "scripts/"); } else { - load_scripts(path, "scripts/mp/"); + if (!game::VirtualLobby_Loaded()) + { + load_scripts(path, "scripts/mp/"); + load_scripts(path, "scripts/"); + } + + force_load = true; + const auto _0 = gsl::finally([&] + { + force_load = false; + }); + load_scripts(path, "scripts/mp_patches/"); } } } diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index 3f9e9ad9..90f3f330 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -97,20 +97,16 @@ namespace scripting game::G_LogPrintf("InitGame\n"); lua::engine::start(); - - gsc::load_main_handles(); } + gsc::load_main_handles(); + g_load_structs_hook.invoke(); } void scr_load_level_stub() { - if (!game::VirtualLobby_Loaded()) - { - gsc::load_init_handles(); - } - + gsc::load_init_handles(); scr_load_level_hook.invoke(); } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 1f46f918..3ab187d7 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -379,7 +379,26 @@ namespace ui_scripting setup_functions(); - lua["print"] = function(reinterpret_cast(SELECT_VALUE(0x93490_b, 0x209EB0_b))); + lua["print"] = [](const variadic_args& va) + { + std::string buffer{}; + const auto to_string = get_globals()["tostring"]; + + for (auto i = 0; i < va.size(); i++) + { + const auto& arg = va[i]; + const auto str = to_string(arg)[0].as(); + buffer.append(str); + + if (i < va.size() - 1) + { + buffer.append("\t"); + } + } + + console::info("%s\n", buffer.data()); + }; + lua["table"]["unpack"] = lua["unpack"]; lua["luiglobals"] = lua; diff --git a/src/client/component/weapon.cpp b/src/client/component/weapon.cpp index e7e2342f..3bac7d9e 100644 --- a/src/client/component/weapon.cpp +++ b/src/client/component/weapon.cpp @@ -116,6 +116,122 @@ namespace weapon { set_weapon_field(weapon_name, field, value); } + + int compare_hash(const void* a, const void* b) + { + const auto hash_a = reinterpret_cast( + reinterpret_cast(a))->hash; + const auto hash_b = reinterpret_cast( + reinterpret_cast(b))->hash; + + if (hash_a < hash_b) + { + return -1; + } + else if (hash_a > hash_b) + { + return 1; + } + + return 0; + } + + utils::memory::allocator ddl_allocator; + + std::vector get_stringtable_entries(const std::string& name) + { + std::vector entries; + + const auto string_table = game::DB_FindXAssetHeader( + game::ASSET_TYPE_STRINGTABLE, name.data(), false).stringTable; + + if (string_table == nullptr) + { + return entries; + } + + for (auto row = 0; row < string_table->rowCount; row++) + { + if (string_table->columnCount <= 0) + { + continue; + } + + const auto index = (row * string_table->columnCount); + const auto weapon = string_table->values[index].string; + entries.push_back(ddl_allocator.duplicate_string(weapon)); + } + + return entries; + } + + void add_entries_to_enum(game::DDLEnum* enum_, const std::vector entries) + { + const auto new_size = enum_->memberCount + entries.size(); + const auto members = ddl_allocator.allocate_array(new_size); + const auto hash_list = ddl_allocator.allocate_array(new_size); + + std::memcpy(members, enum_->members, 8 * enum_->memberCount); + std::memcpy(hash_list, enum_->hashTable.list, 8 * enum_->hashTable.count); + + for (auto i = 0; i < entries.size(); i++) + { + const auto hash = utils::hook::invoke(0x794FB0_b, entries[i], 0); + const auto index = enum_->memberCount + i; + hash_list[index].index = index; + hash_list[index].hash = hash; + members[index] = entries[i]; + } + + std::qsort(hash_list, new_size, sizeof(game::DDLHash), compare_hash); + + enum_->members = members; + enum_->hashTable.list = hash_list; + enum_->memberCount = static_cast(new_size); + enum_->hashTable.count = static_cast(new_size); + } + + void load_ddl_asset_stub(game::DDLRoot** asset) + { + static std::unordered_set modified_enums; + + const auto root = *asset; + if (!root->ddlDef) + { + return utils::hook::invoke(0x39BE20_b, root); + } + + auto ddl_def = root->ddlDef; + while (ddl_def) + { + for (auto i = 0; i < ddl_def->enumCount; i++) + { + const auto enum_ = &ddl_def->enumList[i]; + if (modified_enums.contains(enum_)) + { + continue; + } + + if ((enum_->name == "WeaponStats"s || enum_->name == "Weapon"s)) + { + static const auto weapons = get_stringtable_entries("mp/customWeapons.csv"); + add_entries_to_enum(enum_, weapons); + modified_enums.insert(enum_); + } + + if (enum_->name == "AttachmentBase"s) + { + static const auto attachments = get_stringtable_entries("mp/customAttachments.csv"); + add_entries_to_enum(enum_, attachments); + modified_enums.insert(enum_); + } + } + + ddl_def = ddl_def->next; + } + + utils::hook::invoke(0x39BE20_b, asset); + } } class component final : public component_interface @@ -137,6 +253,8 @@ namespace weapon // patch attachment configstring so it will create if not found utils::hook::call(0x41C595_b, g_find_config_string_index_stub); + + utils::hook::call(0x36B4D4_b, load_ddl_asset_stub); } #ifdef DEBUG diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 3f9596e8..5459fa34 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1600,6 +1600,81 @@ namespace game const char* name; }; + struct DDLMember + { + const char* name; + int index; + void* parent; + int bitSize; + int limitSize; + int offset; + int type; + int externalIndex; + unsigned int rangeLimit; + unsigned int serverDelta; + unsigned int clientDelta; + int arraySize; + int enumIndex; + int permission; + }; + + struct DDLHash + { + unsigned int hash; + int index; + }; + + struct DDLHashTable + { + DDLHash* list; + int count; + int max; + }; + + struct DDLStruct + { + const char* name; + int bitSize; + int memberCount; + DDLMember* members; + DDLHashTable hashTableUpper; + DDLHashTable hashTableLower; + }; + + struct DDLEnum + { + const char* name; + int memberCount; + const char** members; + DDLHashTable hashTable; + }; + + struct DDLDef + { + char* name; + unsigned short version; + unsigned int checksum; + unsigned char flags; + int bitSize; + int byteSize; + DDLStruct* structList; + int structCount; + DDLEnum* enumList; + int enumCount; + DDLDef* next; + int headerBitSize; + int headerByteSize; + int reserveSize; + int userFlagsSize; + bool paddingUsed; + }; + + struct DDLRoot + { + const char* name; + DDLDef* ddlDef; + }; + union XAssetHeader { void* data;