diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 629fd787..18877785 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,6 +8,9 @@ on:
branches:
- "*"
types: [opened, synchronize, reopened]
+concurrency:
+ group: ${{ github.ref }}
+ cancel-in-progress: false
jobs:
build:
name: Build binaries
@@ -18,17 +21,6 @@ jobs:
- Debug
- Release
steps:
- - name: Wait for previous workflows
- if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
- uses: softprops/turnstyle@v1
- with:
- poll-interval-seconds: 10
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up git config
- run: git config --global url."https://".insteadOf git://
-
- name: Check out files
uses: actions/checkout@v3
with:
@@ -38,10 +30,9 @@ jobs:
lfs: false
- name: Add msbuild to PATH
- uses: microsoft/setup-msbuild@v1.0.2
+ uses: microsoft/setup-msbuild@v1.1.3
- name: Generate project files
- #run: tools/premake5 vs2022 --ci-build
run: tools/premake5 vs2022
- name: Set up problem matching
@@ -51,7 +42,7 @@ jobs:
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=x64 build/h1-mod.sln
- name: Upload ${{matrix.configuration}} binaries
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3.1.0
with:
name: ${{matrix.configuration}} binaries
path: |
@@ -59,7 +50,7 @@ jobs:
build/bin/x64/${{matrix.configuration}}/h1-mod.pdb
- name: Upload ${{matrix.configuration}} data artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3.1.0
with:
name: ${{matrix.configuration}} data artifacts
path: |
@@ -79,12 +70,12 @@ jobs:
run: echo "H1_MOD_MASTER_PATH=${{ secrets.H1_MOD_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV
- name: Download Release binaries
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: Release binaries
- name: Download Release data artifacts
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
name: Release data artifacts
path: data
@@ -98,13 +89,6 @@ jobs:
- name: Add known hosts
run: ssh-keyscan -H ${{ secrets.H1_MOD_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts
- - name: Wait for previous workflows
- uses: softprops/turnstyle@v1
- with:
- poll-interval-seconds: 10
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- name: Remove old data files
run: ssh ${{ secrets.H1_MOD_MASTER_SSH_USER }}@${{ secrets.H1_MOD_MASTER_SSH_ADDRESS }} rm -rf ${{ env.H1_MOD_MASTER_PATH }}/h1-mod/*
diff --git a/.gitmodules b/.gitmodules
index a6a9bd10..62d68e1f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -12,7 +12,7 @@
url = https://github.com/TsudaKageyu/minhook.git
[submodule "deps/discord-rpc"]
path = deps/discord-rpc
- url = https://github.com/discord/discord-rpc.git
+ url = https://github.com/fedddddd/discord-rpc.git
[submodule "deps/asmjit"]
path = deps/asmjit
url = https://github.com/asmjit/asmjit.git
@@ -50,7 +50,7 @@
[submodule "deps/gsc-tool"]
path = deps/gsc-tool
url = https://github.com/xensik/gsc-tool.git
- branch = xlabs
+ branch = dev
[submodule "deps/stb"]
path = deps/stb
url = https://github.com/nothings/stb.git
diff --git a/README.md b/README.md
index 46739214..2e731411 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,7 @@
-A client for Call of Duty: Modern Warfare Remastered.
-
-[This project is based on S1x.](https://github.com/XLabsProject/s1x-client)
+NOTE: You must legally own Call of Duty®: Modern Warfare Remastered to run this mod. Cracked/Pirated versions of the game are **NOT** supported.
## Compile from source
@@ -27,13 +25,11 @@ A client for Call of Duty: Modern Warfare Remastered.
| `--copy-to=PATH` | Optional, copy the EXE to a custom folder after build, define the path here if wanted. |
| `--dev-build` | Enable development builds of the client. |
-## Credits:
+## Credits
-- [XLabsProject](https://github.com/XLabsProject) - codebase and iw6x/s1x research
-- [quaK](https://github.com/Joelrau) - lots of insight and help
-- [fed](https://github.com/fedddddd) - fixed DW/networking, work from [h2-mod](https://github.com/fedddddd/h2-mod)
-- [Skull](https://github.com/skkuull) + [mjkzy](https://github.com/mjkzy) - porting code from s1x
-- [momo5502](https://github.com/momo5502) - Arxan/Steam research, developer of XLabsProject :D
+- [S1x](https://github.com/XLabsProject/s1x-client) - codebase and research (predecessor of MWR)
+- [h2-mod](https://github.com/fedddddd/h2-mod) - research (successor of MWR)
+- [momo5502](https://github.com/momo5502) - Arxan/Steam research, developer of [XLabsProject](https://github.com/XLabsProject)
## Disclaimer
diff --git a/data/cdata/scripts/mp/classes.gsc b/data/cdata/scripts/mp/classes.gsc
new file mode 100644
index 00000000..0286f8e7
--- /dev/null
+++ b/data/cdata/scripts/mp/classes.gsc
@@ -0,0 +1,34 @@
+main()
+{
+ replacefunc(maps\mp\gametypes\_menus::getclasschoice, ::getclasschoice);
+}
+
+getclasschoice(choice)
+{
+ if (choice <= 100)
+ {
+ if (getdvar("sv_disableCustomClasses") == "1")
+ {
+ return "class0";
+ }
+
+ choice = "custom" + choice;
+ }
+ else if (choice <= 200)
+ {
+ choice -= 101;
+ choice = "class" + choice;
+ }
+ else if ( choice <= 206 )
+ {
+ choice -= 200;
+ choice = "axis_recipe" + choice;
+ }
+ else
+ {
+ choice -= 206;
+ choice = "allies_recipe" + choice;
+ }
+
+ return choice;
+}
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..cbbd16cd
--- /dev/null
+++ b/data/cdata/scripts/mp_patches/custom_weapons.gsc
@@ -0,0 +1,383 @@
+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 = tablegetrowcount(csv);
+
+ for (i = 0; i < rows; i++)
+ {
+ if (tablelookupbyrow(csv, i, 0) == weap)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+get_attachment_override(weapon, attachment)
+{
+ csv = "mp/attachoverrides.csv";
+ rows = tablegetrowcount(csv);
+
+ if (!issubstr(weapon, "_mp"))
+ {
+ weapon += "_mp";
+ }
+
+ for (i = 0; i < rows; i++)
+ {
+ if (tablelookupbyrow(csv, i, 0) == weapon && tablelookupbyrow(csv, i, 1) == attachment)
+ {
+ return tablelookupbyrow(csv, i, 2);
+ }
+ }
+}
+
+get_attachment_name(weapon, attachment)
+{
+ name = tablelookup("mp/attachkits.csv", 1, attachment, 2);
+ override = get_attachment_override(weapon, name);
+
+ if (isdefined(override) && override != "")
+ {
+ return override;
+ }
+
+ return name;
+}
+
+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, "h1_"))
+ {
+ if (var_9)
+ {
+ name = get_attachment_name(var_0, var_1);
+ if (isdefined(name) && name != "")
+ {
+ var_7 += "_" + name;
+ }
+ }
+ }
+ 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, var_2)
+{
+ if (maps\mp\_utility::is_true(var_1))
+ {
+ return isvalidprimary(var_0);
+ }
+
+ if (maps\mp\_utility::islootweapon(var_0))
+ {
+ var_0 = maps\mp\gametypes\_class::getbasefromlootversion(var_0);
+ }
+
+ 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/scripts/sp/battlechatter_patch.gsc b/data/cdata/scripts/sp/battlechatter_patch.gsc
new file mode 100644
index 00000000..8dcb59d2
--- /dev/null
+++ b/data/cdata/scripts/sp/battlechatter_patch.gsc
@@ -0,0 +1,242 @@
+main()
+{
+ replacefunc(animscripts\battlechatter::init_battlechatter, ::init_battlechatter);
+}
+
+init_battlechatter()
+{
+ if ( isdefined( anim.chatinitialized ) && anim.chatinitialized )
+ return;
+
+ if ( getdvar( "bcs_enable" ) == "" )
+ setdvar( "bcs_enable", "on" );
+
+ if ( getdvar( "bcs_enable" ) == "off" )
+ {
+ anim.chatinitialized = 0;
+ anim.player.chatinitialized = 0;
+ return;
+ }
+
+ anim.chatinitialized = 1;
+ anim.player.chatinitialized = 0;
+
+ if ( getdvar( "bcs_filterThreat" ) == "" )
+ setdvar( "bcs_filterThreat", "off" );
+
+ if ( getdvar( "bcs_filterInform" ) == "" )
+ setdvar( "bcs_filterInform", "off" );
+
+ if ( getdvar( "bcs_filterOrder" ) == "" )
+ setdvar( "bcs_filterOrder", "off" );
+
+ if ( getdvar( "bcs_filterReaction" ) == "" )
+ setdvar( "bcs_filterReaction", "off" );
+
+ if ( getdvar( "bcs_filterResponse" ) == "" )
+ setdvar( "bcs_filterResponse", "off" );
+
+ if ( getdvar( "bcs_threatLimitTargettedBySelf" ) == "" )
+ setdvar( "bcs_threatLimitTargettedBySelf", "off" );
+
+ if ( getdvar( "bcs_threatLimitTargetingPlayer" ) == "" )
+ setdvar( "bcs_threatLimitTargetingPlayer", "off" );
+
+ if ( getdvar( "bcs_threatLimitInPlayerFOV" ) == "" )
+ setdvar( "bcs_threatLimitInPlayerFOV", "on" );
+
+ if ( getdvar( "bcs_threatLimitInLocation" ) == "" )
+ setdvar( "bcs_threatLimitInLocation", "on" );
+
+ if ( getdvar( "bcs_threatLimitSpeakerDist" ) == "" )
+ setdvar( "bcs_threatLimitSpeakerDist", "512" );
+
+ if ( getdvar( "bcs_threatLimitThreatDist" ) == "" )
+ setdvar( "bcs_threatLimitThreatDist", "2048" );
+
+ if ( getdvar( "bcs_threatPlayerRelative" ) == "" )
+ setdvar( "bcs_threatPlayerRelative", "off" );
+
+ if ( getdvar( "debug_bcprint" ) == "" )
+ setdvar( "debug_bcprint", "off" );
+
+ if ( getdvar( "debug_bcshowqueue" ) == "" )
+ setdvar( "debug_bcshowqueue", "off" );
+
+ if ( getdvar( "debug_bcprintdump" ) == "" )
+ setdvar( "debug_bcprintdump", "off" );
+
+ anim.countryids["british"] = "UK";
+ anim.countryids["american"] = "US";
+ anim.countryids["russian"] = "RU";
+ anim.countryids["arab"] = "AB";
+ anim.usedids = [];
+ anim.usedids["russian"] = [];
+ anim.usedids["russian"][0] = spawnstruct();
+ anim.usedids["russian"][0].count = 0;
+ anim.usedids["russian"][0].npcid = "1";
+ anim.usedids["russian"][1] = spawnstruct();
+ anim.usedids["russian"][1].count = 0;
+ anim.usedids["russian"][1].npcid = "2";
+ anim.usedids["russian"][2] = spawnstruct();
+ anim.usedids["russian"][2].count = 0;
+ anim.usedids["russian"][2].npcid = "3";
+ anim.usedids["british"] = [];
+ anim.usedids["british"][0] = spawnstruct();
+ anim.usedids["british"][0].count = 0;
+ anim.usedids["british"][0].npcid = "0";
+ anim.usedids["british"][1] = spawnstruct();
+ anim.usedids["british"][1].count = 0;
+ anim.usedids["british"][1].npcid = "1";
+ anim.usedids["british"][2] = spawnstruct();
+ anim.usedids["british"][2].count = 0;
+ anim.usedids["british"][2].npcid = "2";
+ anim.usedids["british"][3] = spawnstruct();
+ anim.usedids["british"][3].count = 0;
+ anim.usedids["british"][3].npcid = "3";
+ anim.usedids["american"] = [];
+ anim.usedids["american"][0] = spawnstruct();
+ anim.usedids["american"][0].count = 0;
+ anim.usedids["american"][0].npcid = "0"; //0 - US_7
+ anim.usedids["american"][1] = spawnstruct();
+ anim.usedids["american"][1].count = 0;
+ anim.usedids["american"][1].npcid = "1";
+ anim.usedids["american"][2] = spawnstruct();
+ anim.usedids["american"][2].count = 0;
+ anim.usedids["american"][2].npcid = "2";
+ anim.usedids["american"][3] = spawnstruct();
+ anim.usedids["american"][3].count = 0;
+ anim.usedids["american"][3].npcid = "3";
+ anim.usedids["american"][4] = spawnstruct();
+ anim.usedids["american"][4].count = 0;
+ anim.usedids["american"][4].npcid = "4"; //4 - US_6
+ anim.usedids["arab"] = [];
+ anim.usedids["arab"][0] = spawnstruct();
+ anim.usedids["arab"][0].count = 0;
+ anim.usedids["arab"][0].npcid = "0";
+ anim.usedids["arab"][1] = spawnstruct();
+ anim.usedids["arab"][1].count = 0;
+ anim.usedids["arab"][1].npcid = "1";
+ anim.usedids["arab"][2] = spawnstruct();
+ anim.usedids["arab"][2].count = 0;
+ anim.usedids["arab"][2].npcid = "2";
+ anim.usedids["arab"][3] = spawnstruct();
+ anim.usedids["arab"][3].count = 0;
+ anim.usedids["arab"][3].npcid = "3";
+ anim.eventtypeminwait = [];
+ anim.eventtypeminwait["threat"] = [];
+ anim.eventtypeminwait["response"] = [];
+ anim.eventtypeminwait["reaction"] = [];
+ anim.eventtypeminwait["order"] = [];
+ anim.eventtypeminwait["inform"] = [];
+ anim.eventtypeminwait["custom"] = [];
+ anim.eventtypeminwait["direction"] = [];
+
+ if ( isdefined( level._stealth ) )
+ {
+ anim.eventactionminwait["threat"]["self"] = 20000;
+ anim.eventactionminwait["threat"]["squad"] = 30000;
+ }
+ else
+ {
+ anim.eventactionminwait["threat"]["self"] = 12000;
+ anim.eventactionminwait["threat"]["squad"] = 8000;
+ }
+
+ anim.eventactionminwait["response"]["self"] = 1000;
+ anim.eventactionminwait["response"]["squad"] = 1000;
+ anim.eventactionminwait["reaction"]["self"] = 1000;
+ anim.eventactionminwait["reaction"]["squad"] = 1000;
+ anim.eventactionminwait["order"]["self"] = 8000;
+ anim.eventactionminwait["order"]["squad"] = 40000;
+ anim.eventactionminwait["inform"]["self"] = 6000;
+ anim.eventactionminwait["inform"]["squad"] = 8000;
+ anim.eventactionminwait["custom"]["self"] = 0;
+ anim.eventactionminwait["custom"]["squad"] = 5000;
+ anim.eventtypeminwait["reaction"]["casualty"] = 20000;
+ anim.eventtypeminwait["reaction"]["taunt"] = 200000;
+ anim.eventtypeminwait["inform"]["reloading"] = 20000;
+ anim.eventpriority["threat"]["infantry"] = 0.5;
+ anim.eventpriority["threat"]["vehicle"] = 0.7;
+ anim.eventpriority["response"]["killfirm"] = 0.8;
+ anim.eventpriority["response"]["ack"] = 0.9;
+ anim.eventpriority["reaction"]["casualty"] = 0.5;
+ anim.eventpriority["reaction"]["taunt"] = 0.9;
+ anim.eventpriority["order"]["cover"] = 0.9;
+ anim.eventpriority["order"]["action"] = 0.5;
+ anim.eventpriority["order"]["move"] = 0.9;
+ anim.eventpriority["order"]["displace"] = 0.5;
+ anim.eventpriority["inform"]["killfirm"] = 0.6;
+ anim.eventpriority["inform"]["attack"] = 0.9;
+ anim.eventpriority["inform"]["incoming"] = 0.8;
+ anim.eventpriority["inform"]["reloading"] = 0.2;
+ anim.eventpriority["inform"]["suppressed"] = 0.2;
+ anim.eventpriority["custom"]["generic"] = 1.0;
+ anim.eventduration["threat"]["infantry"] = 1000;
+ anim.eventduration["threat"]["vehicle"] = 1000;
+ anim.eventduration["response"]["killfirm"] = 3000;
+ anim.eventduration["response"]["ack"] = 2000;
+ anim.eventduration["reaction"]["casualty"] = 2000;
+ anim.eventduration["reaction"]["taunt"] = 2000;
+ anim.eventduration["order"]["cover"] = 3000;
+ anim.eventduration["order"]["action"] = 3000;
+ anim.eventduration["order"]["move"] = 3000;
+ anim.eventduration["order"]["displace"] = 3000;
+ anim.eventduration["inform"]["killfirm"] = 1000;
+ anim.eventduration["inform"]["attack"] = 1000;
+ anim.eventduration["inform"]["incoming"] = 1000;
+ anim.eventduration["inform"]["reloading"] = 1000;
+ anim.eventduration["inform"]["suppressed"] = 2000;
+ anim.eventduration["custom"]["generic"] = 1000;
+ anim.chatcount = 0;
+ anim.moveorigin = spawn( "script_origin", ( 0.0, 0.0, 0.0 ) );
+ anim.areas = getentarray( "trigger_location", "targetname" );
+ anim.locations = getentarray( "trigger_location", "targetname" );
+ anim.landmarks = getentarray( "trigger_landmark", "targetname" );
+ anim.squadcreatefuncs[anim.squadcreatefuncs.size] = animscripts\battlechatter::init_squadbattlechatter;
+ anim.squadcreatestrings[anim.squadcreatestrings.size] = "::init_squadBattleChatter";
+ anim.isteamspeaking["allies"] = 0;
+ anim.isteamsaying["allies"]["threat"] = 0;
+ anim.isteamsaying["allies"]["order"] = 0;
+ anim.isteamsaying["allies"]["reaction"] = 0;
+ anim.isteamsaying["allies"]["response"] = 0;
+ anim.isteamsaying["allies"]["inform"] = 0;
+ anim.isteamsaying["allies"]["custom"] = 0;
+ anim.isteamspeaking["axis"] = 0;
+ anim.isteamsaying["axis"]["threat"] = 0;
+ anim.isteamsaying["axis"]["order"] = 0;
+ anim.isteamsaying["axis"]["reaction"] = 0;
+ anim.isteamsaying["axis"]["response"] = 0;
+ anim.isteamsaying["axis"]["inform"] = 0;
+ anim.isteamsaying["axis"]["custom"] = 0;
+ anim.isteamspeaking["neutral"] = 0;
+ anim.isteamsaying["neutral"]["threat"] = 0;
+ anim.isteamsaying["neutral"]["order"] = 0;
+ anim.isteamsaying["neutral"]["reaction"] = 0;
+ anim.isteamsaying["neutral"]["response"] = 0;
+ anim.isteamsaying["neutral"]["inform"] = 0;
+ anim.isteamsaying["neutral"]["custom"] = 0;
+
+ if ( !isdefined( level.battlechatter ) )
+ {
+ level.battlechatter = [];
+ level.battlechatter["allies"] = 1;
+ level.battlechatter["axis"] = 1;
+ level.battlechatter["neutral"] = 1;
+ }
+
+ anim.lastteamspeaktime = [];
+ anim.lastteamspeaktime["allies"] = -5000;
+ anim.lastteamspeaktime["axis"] = -5000;
+
+ for ( var_0 = 0; var_0 < anim.squadindex.size; var_0++ )
+ {
+ if ( isdefined( anim.squadindex[var_0].chatinitialized ) && anim.squadindex[var_0].chatinitialized )
+ continue;
+
+ anim.squadindex[var_0] animscripts\battlechatter::init_squadbattlechatter();
+ }
+
+ level notify( "battlechatter initialized" );
+ anim notify( "battlechatter initialized" );
+}
\ No newline at end of file
diff --git a/data/cdata/ui_scripts/classes/__init__.lua b/data/cdata/ui_scripts/classes/__init__.lua
new file mode 100644
index 00000000..f1c997ee
--- /dev/null
+++ b/data/cdata/ui_scripts/classes/__init__.lua
@@ -0,0 +1,13 @@
+if game:issingleplayer() or (Engine.InFrontend()) then
+ return
+end
+
+local getclasscount = Cac.GetCustomClassCount
+Cac.GetCustomClassCount = function(...)
+ local value = Engine.GetDvarBool("sv_disableCustomClasses")
+ if (value) then
+ return 0
+ end
+
+ return getclasscount(...)
+end
\ No newline at end of file
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..7adcd8fb
--- /dev/null
+++ b/data/cdata/ui_scripts/custom_weapons/__init__.lua
@@ -0,0 +1,26 @@
+if game:issingleplayer() or (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/data/cdata/ui_scripts/discord/__init__.lua b/data/cdata/ui_scripts/discord/__init__.lua
index fe988a68..a4d0520f 100644
--- a/data/cdata/ui_scripts/discord/__init__.lua
+++ b/data/cdata/ui_scripts/discord/__init__.lua
@@ -2,7 +2,7 @@ if (game:issingleplayer() or Engine.InFrontend()) then
return
end
-local container = LUI.UIVerticalList.new({
+local container = LUI.UIElement.new({
topAnchor = true,
rightAnchor = true,
top = 20,
@@ -11,20 +11,6 @@ local container = LUI.UIVerticalList.new({
spacing = 5
})
-function canasktojoin(userid)
- history = history or {}
- if (history[userid] ~= nil) then
- return false
- end
-
- history[userid] = true
- game:ontimeout(function()
- history[userid] = nil
- end, 15000)
-
- return true
-end
-
function truncatename(name, length)
if (#name <= length - 3) then
return name
@@ -33,27 +19,57 @@ function truncatename(name, length)
return name:sub(1, length - 3) .. "..."
end
+local requestlist = {}
+local requestcount = 0
+
function addrequest(request)
- if (not canasktojoin(request.userid)) then
- return
+ for i = 1, #requestlist do
+ if (requestlist[i].userid == request.userid or #requestlist > 5) then
+ return
+ end
end
- if (container.temp) then
- container:removeElement(container.temp)
- container.temp = nil
- end
+ request.id = requestcount
+ requestcount = requestcount + 1
+ local yoffset = #requestlist * (75 + 5)
local invite = LUI.UIElement.new({
leftAnchor = true,
rightAnchor = true,
- height = 75
+ height = 75,
+ top = yoffset
})
+ local getcurrentindex = function()
+ for i = 1, #requestlist do
+ if (requestlist[i].id == request.id) then
+ return i
+ end
+ end
+
+ return 0
+ end
+
+ invite:registerEventHandler("update_position", function()
+ yoffset = (getcurrentindex() - 1) * (75 + 5)
+ local state = {
+ leftAnchor = true,
+ height = 75,
+ width = 200,
+ left = -220,
+ top = yoffset
+ }
+
+ invite:registerAnimationState("default", state)
+ invite:animateToState("default", 50)
+ end)
+
invite:registerAnimationState("move_in", {
leftAnchor = true,
height = 75,
width = 200,
- left = -220
+ left = -220,
+ top = yoffset
})
invite:animateToState("move_in", 100)
@@ -105,7 +121,7 @@ function addrequest(request)
width = 32,
height = 32,
left = 1,
- material = RegisterMaterial(avatarmaterial)
+ material = avatarmaterial
})
local username = LUI.UIText.new({
@@ -119,8 +135,9 @@ function addrequest(request)
font = CoD.TextSettings.BodyFontBold.Font
})
- username:setText(string.format("%s^7#%s requested to join your game!", truncatename(request.username, 18),
- request.discriminator))
+ local requesttext = Engine.Localize("LUA_MENU_DISCORD_REQUEST", truncatename(request.displayname, 18))
+
+ username:setText(requesttext)
local buttons = LUI.UIElement.new({
leftAnchor = true,
@@ -156,51 +173,54 @@ function addrequest(request)
return button
end
- buttons:addElement(createbutton("[F1] Accept", true))
- buttons:addElement(createbutton("[F2] Deny"))
+ local accepttext = Engine.Localize("LUA_MENU_DISCORD_ACCEPT", game:getcommandbind("discord_accept"))
+ local denytext = Engine.Localize("LUA_MENU_DISCORD_DENY", game:getcommandbind("discord_deny"))
+
+ buttons:addElement(createbutton(accepttext, true))
+ buttons:addElement(createbutton(denytext))
local fadeouttime = 50
local timeout = 10 * 1000 - fadeouttime
local function close()
- container:processEvent({
- name = "update_navigation",
- dispatchToChildren = true
+ table.remove(requestlist, getcurrentindex())
+
+ invite:registerAnimationState("fade_out", {
+ leftAnchor = true,
+ rightAnchor = true,
+ height = 75,
+ alpha = 0,
+ left = 0,
+ top = yoffset
})
+
invite:animateToState("fade_out", fadeouttime)
invite:addElement(LUI.UITimer.new(fadeouttime + 50, "remove"))
invite:registerEventHandler("remove", function()
container:removeElement(invite)
- if (container.temp) then
- container:removeElement(container.temp)
- container.temp = nil
- end
- local temp = LUI.UIElement.new({})
- container.temp = temp
- container:addElement(temp)
+ container:processEvent({
+ name = "update_position",
+ dispatchToChildren = true
+ })
end)
end
- buttons:registerEventHandler("keydown_", function(element, event)
- if (event.key == "F1") then
- close()
- discord.respond(request.userid, discord.reply.yes)
+ local closed = false
+ request.handleresponse = function(event)
+ if (closed) then
+ return
end
- if (event.key == "F2") then
- close()
+ if (event.accept) then
+ discord.respond(request.userid, discord.reply.yes)
+ else
discord.respond(request.userid, discord.reply.no)
end
- end)
- invite:registerAnimationState("fade_out", {
- leftAnchor = true,
- rightAnchor = true,
- height = 75,
- alpha = 0,
- left = 0
- })
+ closed = true
+ close()
+ end
invite:addElement(LUI.UITimer.new(timeout, "end_invite"))
invite:registerEventHandler("end_invite", function()
@@ -236,7 +256,7 @@ function addrequest(request)
avatar:registerEventHandler("update", function()
local avatarmaterial = discord.getavatarmaterial(request.userid)
- avatar:setImage(RegisterMaterial(avatarmaterial))
+ avatar:setImage(avatarmaterial)
end)
avatar:addElement(LUI.UITimer.new(100, "update"))
@@ -250,19 +270,17 @@ function addrequest(request)
padding:addElement(buttons)
container:addElement(invite)
+
+ table.insert(requestlist, request)
end
-container:registerEventHandler("keydown", function(element, event)
- local first = container:getFirstChild()
-
- if (not first) then
+LUI.roots.UIRoot0:registerEventHandler("discord_response", function(element, event)
+ if (#requestlist <= 0) then
return
end
- first:processEvent({
- name = "keydown_",
- key = event.key
- })
+ local request = requestlist[1]
+ request.handleresponse(event)
end)
LUI.roots.UIRoot0:registerEventHandler("discord_join_request", function(element, event)
diff --git a/data/cdata/ui_scripts/patches/__init__.lua b/data/cdata/ui_scripts/patches/__init__.lua
index 6e0cc090..c8776663 100644
--- a/data/cdata/ui_scripts/patches/__init__.lua
+++ b/data/cdata/ui_scripts/patches/__init__.lua
@@ -1,5 +1,6 @@
require("language")
require("background_effects")
+require("pausequit")
if game:issingleplayer() then
require("sp_unlockall")
diff --git a/data/cdata/ui_scripts/patches/language.lua b/data/cdata/ui_scripts/patches/language.lua
index 1d322afa..809af8bf 100644
--- a/data/cdata/ui_scripts/patches/language.lua
+++ b/data/cdata/ui_scripts/patches/language.lua
@@ -49,8 +49,8 @@ LUI.MenuBuilder.registerType("choose_language_menu", function(a1)
menu:AddButton(Engine.Localize(string.format("MENU_%s", available_languages[i])), function()
LUI.yesnopopup({
title = Engine.Localize("@MENU_NOTICE"),
- text = Engine.Localize("MENU_" .. current_language) .. " → " ..
- Engine.Localize("MENU_" .. available_languages[i]) .. "\n\n" ..
+ text = "^2" .. Engine.Localize("MENU_" .. current_language) .. "^7 → ^5" ..
+ Engine.Localize("MENU_" .. available_languages[i]) .. "\n\n^7" ..
Engine.Localize("@LUA_MENU_CONFIRM_LANGUAGE") .. " " ..
Engine.Localize("@MENU_APPLY_LANGUAGE_SETTINGS"),
callback = function(result)
diff --git a/data/cdata/ui_scripts/patches/pausequit.lua b/data/cdata/ui_scripts/patches/pausequit.lua
new file mode 100644
index 00000000..224e944a
--- /dev/null
+++ b/data/cdata/ui_scripts/patches/pausequit.lua
@@ -0,0 +1,32 @@
+if (Engine.InFrontend()) then
+ return
+end
+
+if game:issingleplayer() and Engine.GetDvarString("mapname") == "coup" then
+ LUI.onmenuopen("sp_pause_menu", function(element)
+ local menu = element:getFirstChild()
+ menu:AddButton("@MENU_SP_SKIP_MISSION", function()
+ Engine.Exec("map blackout")
+ end)
+ end)
+end
+
+if game:issingleplayer() then
+ LUI.onmenuopen("sp_pause_menu", function(element)
+ local menu = element:getFirstChild()
+ menu:AddButton("@MENU_QUIT_TO_DESKTOP", function()
+ LUI.FlowManager.RequestAddMenu(nil, "quit_popmenu")
+ end)
+ end)
+end
+
+if not game:issingleplayer() then
+ local quitToDesktop = function()
+ LUI.FlowManager.RequestAddMenu(nil, "quit_popmenu")
+ end
+ local addQuitButton = function(element)
+ local menu = element
+ menu:AddButton("@MENU_QUIT_TO_DESKTOP", quitToDesktop)
+ end
+ LUI.onmenuopen("mp_pause_menu", addQuitButton)
+end
diff --git a/data/cdata/ui_scripts/patches/shader_dialog.lua b/data/cdata/ui_scripts/patches/shader_dialog.lua
index d4723fe0..3b18e53a 100644
--- a/data/cdata/ui_scripts/patches/shader_dialog.lua
+++ b/data/cdata/ui_scripts/patches/shader_dialog.lua
@@ -15,7 +15,7 @@ local function dialog(...)
yes_text = Engine.Localize("@MENU_YES"),
no_text = Engine.Localize("@MENU_NO_DONT_ASK"),
no_action = function()
- Engine.SetDvarInt("r_preloadShadersFrontendAllow", 0)
+ Engine.SetDvarBool("r_preloadShadersFrontendAllow", false)
end,
default_focus_index = 2,
cancel_will_close = false
diff --git a/data/cdata/ui_scripts/server_list/lobby.lua b/data/cdata/ui_scripts/server_list/lobby.lua
index 8d71585c..e2d48619 100644
--- a/data/cdata/ui_scripts/server_list/lobby.lua
+++ b/data/cdata/ui_scripts/server_list/lobby.lua
@@ -1,7 +1,7 @@
local Lobby = luiglobals.Lobby
local MPLobbyOnline = LUI.mp_menus.MPLobbyOnline
-function LeaveLobby(f5_arg0)
+function LeaveLobby()
LeaveXboxLive()
if Lobby.IsInPrivateParty() == false or Lobby.IsPrivatePartyHost() then
LUI.FlowManager.RequestLeaveMenuByName("menu_xboxlive")
@@ -9,7 +9,7 @@ function LeaveLobby(f5_arg0)
end
end
-function menu_xboxlive(f16_arg0, f16_arg1)
+function menu_xboxlive(f16_arg0)
local menu = LUI.MPLobbyBase.new(f16_arg0, {
menu_title = "@PLATFORM_UI_HEADER_PLAY_MP_CAPS",
memberListState = Lobby.MemberListStates.Prelobby
@@ -17,7 +17,7 @@ function menu_xboxlive(f16_arg0, f16_arg1)
menu:setClass(LUI.MPLobbyOnline)
- local serverListButton = menu:AddButton("@LUA_MENU_SERVERLIST", function(a1, a2)
+ local serverListButton = menu:AddButton("@LUA_MENU_SERVERLIST", function(a1)
LUI.FlowManager.RequestAddMenu(a1, "menu_systemlink_join", true, nil)
end)
serverListButton:setDisabledRefreshRate(500)
@@ -27,9 +27,7 @@ function menu_xboxlive(f16_arg0, f16_arg1)
menu:AddPersonalizationButton()
menu:AddDepotButton()
- -- kinda a weird place to do this, but it's whatever
- -- add "MODS" button below depot button
- local modsButton = menu:AddButton("@MENU_MODS", function(a1, a2)
+ menu:AddButton("@MENU_MODS", function(a1)
LUI.FlowManager.RequestAddMenu(a1, "mods_menu", true, nil)
end)
end
@@ -58,6 +56,8 @@ function menu_xboxlive(f16_arg0, f16_arg1)
menu:addElement(self)
end
+ menu:AddMenuDescription(1)
+ menu:AddMarketingPanel(LUI.MarketingLocation.Featured, LUI.ComScore.ScreenID.PlayOnline)
menu.isSignInMenu = true
menu:registerEventHandler("gain_focus", LUI.MPLobbyOnline.OnGainFocus)
menu:registerEventHandler("player_joined", luiglobals.Cac.PlayerJoinedEvent)
@@ -81,7 +81,16 @@ function menu_xboxlive(f16_arg0, f16_arg1)
end
end)
+ menu:AddHelp({
+ name = "add_button_helper_text",
+ button_ref = "",
+ helper_text = " ",
+ side = "left",
+ priority = -9000,
+ clickable = false
+ })
+
return menu
end
-LUI.MenuBuilder.m_types_build["menu_xboxlive"] = menu_xboxlive
+LUI.MenuBuilder.m_types_build["menu_xboxlive"] = menu_xboxlive
\ No newline at end of file
diff --git a/data/cdata/ui_scripts/server_list/serverlist.lua b/data/cdata/ui_scripts/server_list/serverlist.lua
index e6c80757..bf9525e8 100644
--- a/data/cdata/ui_scripts/server_list/serverlist.lua
+++ b/data/cdata/ui_scripts/server_list/serverlist.lua
@@ -10,19 +10,23 @@ local columns = {{
text = "@MENU_HOST_NAME",
dataindex = 0
}, {
- offset = 500,
+ offset = 440,
text = "@MENU_MAP",
dataindex = 1
}, {
- offset = 725,
+ offset = 615,
text = "@MENU_TYPE1",
dataindex = 3
}, {
- offset = 920,
+ offset = 780,
+ text = "@EXE_SV_INFO_MOD",
+ dataindex = 6
+}, {
+ offset = 980,
text = "@MENU_NUMPLAYERS",
dataindex = 2
}, {
- offset = 1070,
+ offset = 1100,
text = "@MENU_PING",
dataindex = 4
}, {
diff --git a/data/zone_source/h1_mod_common.csv b/data/zone_source/h1_mod_common.csv
index b69a5031..a0e9c4f5 100644
--- a/data/zone_source/h1_mod_common.csv
+++ b/data/zone_source/h1_mod_common.csv
@@ -1,2 +1,3 @@
localize,english
+ttf,fonts/bank_h1.ttf
ttf,fonts/default.otf
\ No newline at end of file
diff --git a/data/zonetool/fra_h1_mod_common_mp/localizedstrings/french.json b/data/zonetool/fra_h1_mod_common_mp/localizedstrings/french.json
index 1b006bfd..fbabba13 100644
--- a/data/zonetool/fra_h1_mod_common_mp/localizedstrings/french.json
+++ b/data/zonetool/fra_h1_mod_common_mp/localizedstrings/french.json
@@ -2,7 +2,7 @@
"LUA_MENU_SERVERLIST": "Liste des serveurs",
"PLATFORM_SYSTEM_LINK_TITLE": "LISTE DES SERVEURS",
"MENU_NUMPLAYERS": "Joueurs",
- "MENU_PING": "Latence",
+ "MENU_PING": "Ping",
"SERVERLIST_PLAYER_COUNT": "&&1 Joueurs",
"SERVERLIST_SERVER_COUNT": "&&1 Serveurs",
@@ -25,8 +25,8 @@
"LUA_MENU_EDIT_STATS": "Modifier les statistiques",
"UPDATER_POPUP_NO_UPDATES_AVAILABLE": "Aucune mise à jour disponible",
- "UPDATER_POPUP_AVAILABLE_UPDATE_TEXT": "Une mise à jour est disponible,\npoursuivre l'installation ?",
+ "MENU_CCS_NEW_PATCH_NOTICE": "Une mise à jour est disponible,\npoursuivre l'installation ?",
"UPDATER_POPUP_SUCCESSFUL": "Mise à jour réussie",
- "UPDATER_POPUP_RESTART_POPUP_TEXT": "La mise à jour nécessite un redémarrage",
+ "MENU_CCS_RESTART_CONFIRMATION_TEXT": "La mise à jour nécessite un redémarrage",
"UPDATER_POPUP_CHECKING_FOR_UPDATES": "Vérification des mises à jour..."
}
\ No newline at end of file
diff --git a/data/zonetool/h1_mod_common/fonts/bank_h1.ttf b/data/zonetool/h1_mod_common/fonts/bank_h1.ttf
new file mode 100644
index 00000000..71ffc345
Binary files /dev/null and b/data/zonetool/h1_mod_common/fonts/bank_h1.ttf differ
diff --git a/data/zonetool/h1_mod_common/fonts/default.otf b/data/zonetool/h1_mod_common/fonts/default.otf
index ad4f12ef..6bdf48ec 100644
Binary files a/data/zonetool/h1_mod_common/fonts/default.otf and b/data/zonetool/h1_mod_common/fonts/default.otf differ
diff --git a/data/zonetool/h1_mod_common/localizedstrings/english.json b/data/zonetool/h1_mod_common/localizedstrings/english.json
index 6ee0187a..989ede95 100644
--- a/data/zonetool/h1_mod_common/localizedstrings/english.json
+++ b/data/zonetool/h1_mod_common/localizedstrings/english.json
@@ -31,6 +31,7 @@
"PLATFORM_SYSTEM_LINK_TITLE": "SERVER LIST",
"MENU_NUMPLAYERS": "Players",
"MENU_PING": "Ping",
+ "MENU_MOD": "Mod",
"SERVERLIST_PLAYER_COUNT": "&&1 Players",
"SERVERLIST_SERVER_COUNT": "&&1 Servers",
"EXE_SAY": "^3Match^7",
@@ -55,9 +56,10 @@
"LUA_MENU_EDIT_STATS": "Edit Stats",
"UPDATER_POPUP_NO_UPDATES_AVAILABLE": "No updates available",
- "UPDATER_POPUP_AVAILABLE_UPDATE_TEXT": "An update is available, proceed with installation?",
+ "MENU_CCS_NEW_PATCH_NOTICE": "An update is available, proceed with installation?",
+ "MENU_DOWNLOAD_AUTOUPDATE_PATCH": " ",
"UPDATER_POPUP_SUCCESSFUL": "Update successful",
- "UPDATER_POPUP_RESTART_POPUP_TEXT": "Update requires restart",
+ "MENU_CCS_RESTART_CONFIRMATION_TEXT": "Update requires restart",
"UPDATER_POPUP_CHECKING_FOR_UPDATES": "Checking for updates...",
"MPHUD_FPS": "FPS: ",
@@ -93,5 +95,6 @@
"LOCALE_RUSSIAN": "Russian",
"LOCALE_SIMPLIFIED_CHINESE": "Simplified Chinese",
"LOCALE_SPANISH": "Spanish",
- "LOCALE_TRADITIONAL_CHINESE": "Traditional Chinese"
+ "LOCALE_TRADITIONAL_CHINESE": "Traditional Chinese",
+ "MENU_QUIT_TO_DESKTOP": "Quit to Desktop"
}
\ No newline at end of file
diff --git a/data/zonetool/localizedstrings/english.json b/data/zonetool/localizedstrings/english.json
index f0b9b8dc..726e5cf0 100644
--- a/data/zonetool/localizedstrings/english.json
+++ b/data/zonetool/localizedstrings/english.json
@@ -2,5 +2,10 @@
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Unlock All Missions and Intel",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "Cancel Unlock All Missions",
"LUA_MENU_CHOOSE_LANGUAGE_DESC": "Choose your language.",
- "MENU_APPLY_LANGUAGE_SETTINGS": "Apply language settings?"
+ "MENU_APPLY_LANGUAGE_SETTINGS": "Apply language settings?",
+
+ "LUA_MENU_DISCORD_REQUEST": "&&1^7 requested to join your game!",
+ "LUA_MENU_DISCORD_REQUEST_DISCRIMINATOR": "&&1^7#&&2 requested to join your game!",
+ "LUA_MENU_DISCORD_ACCEPT": "[&&1] Accept",
+ "LUA_MENU_DISCORD_DENY": "[&&1] Deny"
}
\ No newline at end of file
diff --git a/data/zonetool/localizedstrings/french.json b/data/zonetool/localizedstrings/french.json
index f32229e3..d29a48fc 100644
--- a/data/zonetool/localizedstrings/french.json
+++ b/data/zonetool/localizedstrings/french.json
@@ -9,7 +9,7 @@
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Débloquer toutes les missions",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "Annuler déblocage",
- "MENU_MODS": "MODS",
+ "MENU_MODS": "Mods",
"MENU_MODS_DESC": "Charger les mods installés.",
"LUA_MENU_MOD_DESC_DEFAULT": "Charger &&1.",
"LUA_MENU_MOD_DESC": "&&1\nAuteur: &&2\nVersion: &&3",
@@ -22,9 +22,9 @@
"MENU_NO_DONT_ASK": "Non, ne plus me le demander",
"UPDATER_POPUP_NO_UPDATES_AVAILABLE": "Aucune mise à jour disponible",
- "UPDATER_POPUP_AVAILABLE_UPDATE_TEXT": "Une mise à jour est disponible,\npoursuivre l'installation ?",
+ "MENU_CCS_NEW_PATCH_NOTICE": "Une mise à jour est disponible,\npoursuivre l'installation ?",
"UPDATER_POPUP_SUCCESSFUL": "Mise à jour réussie",
- "UPDATER_POPUP_RESTART_POPUP_TEXT": "La mise à jour nécessite un redémarrage",
+ "MENU_CCS_RESTART_CONFIRMATION_TEXT": "La mise à jour nécessite un redémarrage",
"UPDATER_POPUP_CHECKING_FOR_UPDATES": "Vérification des mises à jour...",
"MPHUD_FPS": "IPS: ",
@@ -45,6 +45,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "Chinois simplifié",
"LOCALE_SPANISH": "Espagnol",
"LOCALE_TRADITIONAL_CHINESE": "Chinois traditionnel",
+ "MENU_QUIT_TO_DESKTOP": "Retour au bureau",
"LUA_MENU_CHOOSE_LANGUAGE": "Choisissez la langue",
"LUA_MENU_CHOOSE_LANGUAGE_DESC": "Choisissez la langue."
diff --git a/data/zonetool/localizedstrings/german.json b/data/zonetool/localizedstrings/german.json
index ab9a9770..c7d87f34 100644
--- a/data/zonetool/localizedstrings/german.json
+++ b/data/zonetool/localizedstrings/german.json
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "Vereinfachtes Chinesisch",
"LOCALE_SPANISH": "Spanisch",
"LOCALE_TRADITIONAL_CHINESE": "Traditionelles Chinesisch",
+ "MENU_QUIT_TO_DESKTOP": "Zum Desktop",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Alle Missionen freischalten",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "Freischalten abbrechen"
}
\ No newline at end of file
diff --git a/data/zonetool/localizedstrings/italian.json b/data/zonetool/localizedstrings/italian.json
index af0a304d..cd50ace6 100644
--- a/data/zonetool/localizedstrings/italian.json
+++ b/data/zonetool/localizedstrings/italian.json
@@ -26,5 +26,6 @@
"LOCALE_RUSSIAN": "Russo",
"LOCALE_SIMPLIFIED_CHINESE": "Cinese semplificato",
"LOCALE_SPANISH": "Spagnolo",
- "LOCALE_TRADITIONAL_CHINESE": "Cinese tradizionale"
+ "LOCALE_TRADITIONAL_CHINESE": "Cinese tradizionale",
+ "MENU_QUIT_TO_DESKTOP": "Esci e torna al desktop"
}
\ No newline at end of file
diff --git a/data/zonetool/localizedstrings/japanese_partial.json b/data/zonetool/localizedstrings/japanese_partial.json
index e9066310..dacb82da 100644
--- a/data/zonetool/localizedstrings/japanese_partial.json
+++ b/data/zonetool/localizedstrings/japanese_partial.json
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "簡体字中国語",
"LOCALE_SPANISH": "スペイン語",
"LOCALE_TRADITIONAL_CHINESE": "繁体字中国語",
+ "MENU_QUIT_TO_DESKTOP": "デスクトップに戻る",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "全ミッションをアンロック",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "アンロックをキャンセル"
diff --git a/data/zonetool/localizedstrings/korean.json b/data/zonetool/localizedstrings/korean.json
index 96972e89..00a32abc 100644
--- a/data/zonetool/localizedstrings/korean.json
+++ b/data/zonetool/localizedstrings/korean.json
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "중국어(간체)",
"LOCALE_SPANISH": "스페인어",
"LOCALE_TRADITIONAL_CHINESE": "중국어(번체)",
+ "MENU_QUIT_TO_DESKTOP": "데스크탑으로 나가기",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "모든 임무 잠금 해제",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "잠금 해제 취소"
diff --git a/data/zonetool/localizedstrings/polish.json b/data/zonetool/localizedstrings/polish.json
index e8323a37..30318415 100644
--- a/data/zonetool/localizedstrings/polish.json
+++ b/data/zonetool/localizedstrings/polish.json
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "Chiński uproszczony",
"LOCALE_SPANISH": "Hiszpański",
"LOCALE_TRADITIONAL_CHINESE": "Chiński tradycyjny",
+ "MENU_QUIT_TO_DESKTOP": "Wyjdź do pulpitu",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Odblokuj wszystkie misje",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "Anuluj odblokowanie"
diff --git a/data/zonetool/localizedstrings/portuguese.json b/data/zonetool/localizedstrings/portuguese.json
index cfcbf86d..aed9edfb 100644
--- a/data/zonetool/localizedstrings/portuguese.json
+++ b/data/zonetool/localizedstrings/portuguese.json
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "Chinês simplificado",
"LOCALE_SPANISH": "Español",
"LOCALE_TRADITIONAL_CHINESE": "Chinês tradicional",
+ "MENU_QUIT_TO_DESKTOP": "Sair para a área de trabalho",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Desbloquear todas as missões",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "Cancelar desbloqueio"
diff --git a/data/zonetool/localizedstrings/russian.json b/data/zonetool/localizedstrings/russian.json
index 5ac4fe31..a49b64ba 100644
--- a/data/zonetool/localizedstrings/russian.json
+++ b/data/zonetool/localizedstrings/russian.json
@@ -11,7 +11,7 @@
"LUA_MENU_CHOOSE_LANGUAGE": "Выбор языка",
"LUA_MENU_CHOOSE_LANGUAGE_DESC": "Поменять язык интерфейса и озвучки.",
- "MENU_MODS": "МОДЫ",
+ "MENU_MODS": "Моды",
"MENU_MODS_DESC": "Запуск установленных модов.",
"LUA_MENU_MOD_DESC_DEFAULT": "Запустить &&1.",
"LUA_MENU_MOD_DESC": "&&1\nАвтор: &&2\nВерсия: &&3.",
@@ -24,9 +24,9 @@
"MENU_NO_DONT_ASK": "Нет, больше не спрашивать",
"UPDATER_POPUP_NO_UPDATES_AVAILABLE": "У вас установлены все последние обновления",
- "UPDATER_POPUP_AVAILABLE_UPDATE_TEXT": "Доступно обновление клиента игры,\nначать установку сейчас?",
+ "MENU_CCS_NEW_PATCH_NOTICE": "Доступно обновление клиента игры,\nначать установку сейчас?",
"UPDATER_POPUP_SUCCESSFUL": "Обновление завершено",
- "UPDATER_POPUP_RESTART_POPUP_TEXT": "Для применения изменений необходим перезапуск игры",
+ "MENU_CCS_RESTART_CONFIRMATION_TEXT": "Для применения изменений необходим перезапуск игры",
"UPDATER_POPUP_CHECKING_FOR_UPDATES": "Проверка наличия обновлений...",
"MPHUD_FPS": "К/С: ",
@@ -48,6 +48,7 @@
"LOCALE_SPANISH": "Испанский",
"LOCALE_TRADITIONAL_CHINESE": "Китайский традиционный",
"LUA_MENU_DOWNLOAD": "Скачать",
+ "MENU_QUIT_TO_DESKTOP": "Выйти на рабочий стол",
"MPUI_MP44": "MP-44",
"WEAPON_AT4": "AT4",
diff --git a/data/zonetool/localizedstrings/simplified_chinese.json b/data/zonetool/localizedstrings/simplified_chinese.json
index 5560337e..069d21a5 100644
--- a/data/zonetool/localizedstrings/simplified_chinese.json
+++ b/data/zonetool/localizedstrings/simplified_chinese.json
@@ -2,7 +2,7 @@
"LUA_MENU_SERVERLIST": "服务器列表",
"PLATFORM_SYSTEM_LINK_TITLE": "服务器列表",
"MENU_NUMPLAYERS": "玩家",
- "MENU_PING": "Ping",
+ "MENU_PING": "时延",
"LOCALE_ENGLISH": "英语",
"LOCALE_ENGLISH_SAFE": "英语 (审查制度)",
"LOCALE_FRENCH": "法语",
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "简体中文",
"LOCALE_SPANISH": "西班牙语",
"LOCALE_TRADITIONAL_CHINESE": "繁体中文",
+ "MENU_QUIT_TO_DESKTOP": "退至桌面",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "解锁全部任务",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "取消解锁"
diff --git a/data/zonetool/localizedstrings/spanish.json b/data/zonetool/localizedstrings/spanish.json
index bdebeb16..d4d420be 100644
--- a/data/zonetool/localizedstrings/spanish.json
+++ b/data/zonetool/localizedstrings/spanish.json
@@ -16,6 +16,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "Chino simplificado",
"LOCALE_SPANISH": "Español",
"LOCALE_TRADITIONAL_CHINESE": "Chino tradicional",
+ "MENU_QUIT_TO_DESKTOP": "Salir al escritorio",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Desbloquear todas las misiones",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "Cancelar desbloqueo"
diff --git a/data/zonetool/localizedstrings/traditional_chinese.json b/data/zonetool/localizedstrings/traditional_chinese.json
index 16f3ca3d..7c7ea96f 100644
--- a/data/zonetool/localizedstrings/traditional_chinese.json
+++ b/data/zonetool/localizedstrings/traditional_chinese.json
@@ -2,7 +2,7 @@
"LUA_MENU_SERVERLIST": "伺服器列表",
"PLATFORM_SYSTEM_LINK_TITLE": "伺服器列表",
"MENU_NUMPLAYERS": "玩家",
- "MENU_PING": "網路延遲",
+ "MENU_PING": "延遲",
"MENU_TYPE1": "類型",
"LOCALE_ENGLISH": "英文",
"LOCALE_ENGLISH_SAFE": "英文 (審查制度)",
@@ -17,6 +17,7 @@
"LOCALE_SIMPLIFIED_CHINESE": "簡體中文",
"LOCALE_SPANISH": "西班牙文",
"LOCALE_TRADITIONAL_CHINESE": "繁體中文",
+ "MENU_QUIT_TO_DESKTOP": "返回桌面",
"LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "解鎖所有任務",
"LUA_MENU_CANCEL_UNLOCK_CAPS": "取消解鎖"
diff --git a/data/zonetool/rus_h1_mod_common_mp/localizedstrings/russian.json b/data/zonetool/rus_h1_mod_common_mp/localizedstrings/russian.json
index b568d035..d2973591 100644
--- a/data/zonetool/rus_h1_mod_common_mp/localizedstrings/russian.json
+++ b/data/zonetool/rus_h1_mod_common_mp/localizedstrings/russian.json
@@ -117,6 +117,7 @@
"EXE_SV_INFO_FRIENDLY_FIRE": "Огонь по своим",
"EXE_SV_INFO_GAMETYPE": "Режим игры",
"EXE_SV_INFO_KILLCAM": "Повтор",
+ "EXE_SV_INFO_MOD": "Мод",
"EXE_SV_INFO_NAME": "имя",
"EXE_SV_INFO_PASSWORD": "С паролем",
"EXE_SV_INFO_PING": "пинг",
diff --git a/deps/GSL b/deps/GSL
index 6c6111ac..e64c97fc 160000
--- a/deps/GSL
+++ b/deps/GSL
@@ -1 +1 @@
-Subproject commit 6c6111acb7b5d687ac006969ac96e5b1f21374cd
+Subproject commit e64c97fc2cfc11992098bb38eda932de275e3f4d
diff --git a/deps/asmjit b/deps/asmjit
index 5b5b0b38..7c10a14d 160000
--- a/deps/asmjit
+++ b/deps/asmjit
@@ -1 +1 @@
-Subproject commit 5b5b0b38775938df4d3779604ff1db60b9a9dcbf
+Subproject commit 7c10a14d347879f889c6d11a9398f1d453acc690
diff --git a/deps/curl b/deps/curl
index 4ab601d9..102de7aa 160000
--- a/deps/curl
+++ b/deps/curl
@@ -1 +1 @@
-Subproject commit 4ab601d93a07cee665ec2458a51fccd0767c03f1
+Subproject commit 102de7aa8d5bfc6ed5fe85e89c7b943d0c186f03
diff --git a/deps/discord-rpc b/deps/discord-rpc
index 963aa9f3..b3383798 160000
--- a/deps/discord-rpc
+++ b/deps/discord-rpc
@@ -1 +1 @@
-Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33
+Subproject commit b3383798b353c31ea6770fee673740c27f6e3489
diff --git a/deps/extra/gsc-tool/interface.cpp b/deps/extra/gsc-tool/interface.cpp
deleted file mode 100644
index 28b7ad73..00000000
--- a/deps/extra/gsc-tool/interface.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "stdafx.hpp"
-
-#include
-
-#include "interface.hpp"
-
-namespace gsc
-{
- std::unique_ptr compiler()
- {
- auto compiler = std::make_unique();
- compiler->mode(xsk::gsc::build::prod);
- return compiler;
- }
-
- std::unique_ptr decompiler()
- {
- return std::make_unique();
- }
-
- std::unique_ptr assembler()
- {
- return std::make_unique();
- }
-
- std::unique_ptr disassembler()
- {
- return std::make_unique();
- }
-}
diff --git a/deps/extra/gsc-tool/interface.hpp b/deps/extra/gsc-tool/interface.hpp
deleted file mode 100644
index 133e6ae2..00000000
--- a/deps/extra/gsc-tool/interface.hpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-
-namespace gsc
-{
- std::unique_ptr compiler();
- std::unique_ptr decompiler();
- std::unique_ptr assembler();
- std::unique_ptr disassembler();
-}
diff --git a/deps/gsc-tool b/deps/gsc-tool
index 7d374025..cbfcce1d 160000
--- a/deps/gsc-tool
+++ b/deps/gsc-tool
@@ -1 +1 @@
-Subproject commit 7d374025b7675bada64c247ebe9378dd335a33da
+Subproject commit cbfcce1dd67534c2115331e41d4cb6893e96196c
diff --git a/deps/json b/deps/json
index 4c6cde72..a259ecc5 160000
--- a/deps/json
+++ b/deps/json
@@ -1 +1 @@
-Subproject commit 4c6cde72e533158e044252718c013a48bcff346c
+Subproject commit a259ecc51e1951e12f757ce17db958e9881e9c6c
diff --git a/deps/libtomcrypt b/deps/libtomcrypt
index 29986d04..7e863d21 160000
--- a/deps/libtomcrypt
+++ b/deps/libtomcrypt
@@ -1 +1 @@
-Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4
+Subproject commit 7e863d21429f94ed6a720e24499a12a3f852bb31
diff --git a/deps/libtommath b/deps/libtommath
index 03de03de..8314bde5 160000
--- a/deps/libtommath
+++ b/deps/libtommath
@@ -1 +1 @@
-Subproject commit 03de03dee753442d4b23166982514639c4ccbc39
+Subproject commit 8314bde5e5c8e5d9331460130a9d1066e324f091
diff --git a/deps/lua b/deps/lua
index be908a7d..7923dbbf 160000
--- a/deps/lua
+++ b/deps/lua
@@ -1 +1 @@
-Subproject commit be908a7d4d8130264ad67c5789169769f824c5d1
+Subproject commit 7923dbbf72da303ca1cca17efd24725668992f15
diff --git a/deps/minhook b/deps/minhook
index 49d03ad1..f5485b84 160000
--- a/deps/minhook
+++ b/deps/minhook
@@ -1 +1 @@
-Subproject commit 49d03ad118cf7f6768c79a8f187e14b8f2a07f94
+Subproject commit f5485b8454544c2f034c78f8f127c1d03dea3636
diff --git a/deps/premake/gsc-tool.lua b/deps/premake/gsc-tool.lua
index 08da07de..87cc40c5 100644
--- a/deps/premake/gsc-tool.lua
+++ b/deps/premake/gsc-tool.lua
@@ -1,5 +1,5 @@
gsc_tool = {
- source = path.join(dependencies.basePath, "gsc-tool/src")
+ source = path.join(dependencies.basePath, "gsc-tool")
}
function gsc_tool.import()
@@ -9,60 +9,54 @@ end
function gsc_tool.includes()
includedirs {
- path.join(gsc_tool.source, "utils"),
- path.join(gsc_tool.source, "h1"),
- path.join(dependencies.basePath, "extra/gsc-tool") -- https://github.com/GEEKiDoS/open-teknomw3/blob/master/deps/extra/gsc-tool
+ path.join(gsc_tool.source, "include")
}
end
--- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L95
function gsc_tool.project()
project "xsk-gsc-utils"
- kind "StaticLib"
- language "C++"
+ kind "StaticLib"
+ language "C++"
+ warnings "Off"
- pchheader "stdafx.hpp"
- pchsource(path.join(gsc_tool.source, "utils/stdafx.cpp"))
+ files {
+ path.join(gsc_tool.source, "include/xsk/utils/*.hpp"),
+ path.join(gsc_tool.source, "src/utils/*.cpp")
+ }
- files {
- path.join(gsc_tool.source, "utils/**.h"),
- path.join(gsc_tool.source, "utils/**.hpp"),
- path.join(gsc_tool.source, "utils/**.cpp")
- }
+ includedirs {
+ path.join(gsc_tool.source, "include")
+ }
- includedirs {
- path.join(gsc_tool.source, "utils"),
- gsc_tool.source
- }
-
- zlib.includes()
+ zlib.includes()
project "xsk-gsc-h1"
- kind "StaticLib"
- language "C++"
+ kind "StaticLib"
+ language "C++"
+ warnings "Off"
- pchheader "stdafx.hpp"
- pchsource(path.join(gsc_tool.source, "h1/stdafx.cpp"))
-
- files {
- path.join(gsc_tool.source, "h1/**.h"),
- path.join(gsc_tool.source, "h1/**.hpp"),
- path.join(gsc_tool.source, "h1/**.cpp"),
- path.join(dependencies.basePath, "extra/gsc-tool/interface.cpp")
- }
-
- includedirs {
- path.join(gsc_tool.source, "h1"),
- gsc_tool.source,
- path.join(dependencies.basePath, "extra/gsc-tool")
- }
-
- -- https://github.com/xensik/gsc-tool/blob/dev/premake5.lua#L25
- -- adding these build options fixes a bunch of parser stuff
filter "action:vs*"
- buildoptions "/bigobj"
buildoptions "/Zc:__cplusplus"
filter {}
+
+ files {
+ path.join(gsc_tool.source, "include/xsk/stdinc.hpp"),
+
+ path.join(gsc_tool.source, "include/xsk/gsc/engine/h1.hpp"),
+ path.join(gsc_tool.source, "src/gsc/engine/h1.cpp"),
+
+ path.join(gsc_tool.source, "src/gsc/engine/h1_code.cpp"),
+ path.join(gsc_tool.source, "src/gsc/engine/h1_func.cpp"),
+ path.join(gsc_tool.source, "src/gsc/engine/h1_meth.cpp"),
+ path.join(gsc_tool.source, "src/gsc/engine/h1_token.cpp"), path.join(gsc_tool.source, "src/gsc/*.cpp"),
+
+ path.join(gsc_tool.source, "src/gsc/common/*.cpp"),
+ path.join(gsc_tool.source, "include/xsk/gsc/common/*.hpp")
+ }
+
+ includedirs {
+ path.join(gsc_tool.source, "include")
+ }
end
table.insert(dependencies, gsc_tool)
diff --git a/deps/protobuf b/deps/protobuf
index 7ce9c415..5a3dac89 160000
--- a/deps/protobuf
+++ b/deps/protobuf
@@ -1 +1 @@
-Subproject commit 7ce9c415455c098409222702b3b4572b47232882
+Subproject commit 5a3dac894157bf3618b2c906a8b9073b4cad62b6
diff --git a/deps/rapidjson b/deps/rapidjson
index a98e9999..6089180e 160000
--- a/deps/rapidjson
+++ b/deps/rapidjson
@@ -1 +1 @@
-Subproject commit a98e99992bd633a2736cc41f96ec85ef0c50e44d
+Subproject commit 6089180ecb704cb2b136777798fa1be303618975
diff --git a/deps/sol2 b/deps/sol2
index f81643aa..9c882a28 160000
--- a/deps/sol2
+++ b/deps/sol2
@@ -1 +1 @@
-Subproject commit f81643aa0c0c507c0cd8400b8cfedc74a34a19f6
+Subproject commit 9c882a28fdb6f4ad79a53a4191b43ce48a661175
diff --git a/deps/stb b/deps/stb
index 8b5f1f37..f4a71b13 160000
--- a/deps/stb
+++ b/deps/stb
@@ -1 +1 @@
-Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55
+Subproject commit f4a71b13373436a2866c5d68f8f80ac6f0bc1ffe
diff --git a/deps/zlib b/deps/zlib
index e5546956..643e17b7 160000
--- a/deps/zlib
+++ b/deps/zlib
@@ -1 +1 @@
-Subproject commit e554695638228b846d49657f31eeff0ca4680e8a
+Subproject commit 643e17b7498d12ab8d15565662880579692f769d
diff --git a/src/client/component/branding.cpp b/src/client/component/branding.cpp
index 342b8b0f..8e6b6512 100644
--- a/src/client/component/branding.cpp
+++ b/src/client/component/branding.cpp
@@ -31,20 +31,30 @@ namespace branding
void draw_branding()
{
const auto font = game::R_RegisterFont("fonts/fira_mono_bold.ttf", 22);
- if (font)
+ if (!font)
{
-#ifdef DEBUG
- game::R_AddCmdDrawText("h1-mod: " VERSION " (" __DATE__ " " __TIME__ ")",
- 0x7FFFFFFF, font, 10.f,
- 5.f + static_cast(font->pixelHeight),
- 1.f, 1.f, 0.0f, color, 0);
-#else
- game::R_AddCmdDrawText("h1-mod",
- 0x7FFFFFFF, font, 10.f,
- 5.f + static_cast(font->pixelHeight),
- 1.f, 1.f, 0.0f, color, 0);
-#endif
+ return;
}
+
+#ifdef DEBUG
+ const auto text = "h1-mod: " VERSION " (" __DATE__ " " __TIME__ ")";
+#else
+ const auto text = "h1-mod: " VERSION;
+#endif
+
+ const auto placement = game::ScrPlace_GetViewPlacement();
+ float text_color[4] = {0.6f, 0.6f, 0.6f, 0.6f};
+
+ game::rectDef_s rect{};
+ rect.x = 0;
+ rect.y = 0;
+ rect.w = 500;
+ rect.horzAlign = 0;
+ rect.vertAlign = 0;
+
+ game::rectDef_s text_rect{};
+
+ game::UI_DrawWrappedText(placement, text, &rect, font, -102.5f, 10.f, 0.17f, text_color, 0, 0, &text_rect, 0);
}
}
diff --git a/src/client/component/command.cpp b/src/client/component/command.cpp
index 58c0918d..b3b576c0 100644
--- a/src/client/component/command.cpp
+++ b/src/client/component/command.cpp
@@ -4,11 +4,12 @@
#include "command.hpp"
#include "console.hpp"
#include "dvars.hpp"
-#include "game_console.hpp"
#include "fastfiles.hpp"
#include "filesystem.hpp"
-#include "scheduler.hpp"
+#include "game_console.hpp"
#include "logfile.hpp"
+#include "mods.hpp"
+#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
@@ -28,8 +29,6 @@ namespace command
std::unordered_map> handlers;
std::unordered_map> handlers_sv;
- std::optional saved_fs_game;
-
void main_handler()
{
params params = {};
@@ -557,43 +556,11 @@ namespace command
}
}
- void register_fs_game_path()
- {
- const auto* fs_game = game::Dvar_FindVar("fs_game");
- const auto new_mod_path = fs_game->current.string;
-
- // check if the last saved fs_game value isn't empty and if it doesn't equal the new fs_game
- if (saved_fs_game.has_value() && saved_fs_game != new_mod_path)
- {
- // unregister path to be used as a fs directory
- filesystem::unregister_path(saved_fs_game.value());
- }
-
- if (new_mod_path && !new_mod_path[0])
- {
- return;
- }
-
- // register fs_game value as a fs directory used for many things
- filesystem::register_path(new_mod_path);
- saved_fs_game = new_mod_path;
- }
-
class component final : public component_interface
{
public:
void post_unpack() override
{
- // it might be overdone to change the filesystem path on every new value change, but to be fair,
- // for the mods that don't need full restarts, this is good because it'll adjust and work like so
- // in my opinion, this is fine. if a user tries to modify the dvar themselves, they'll have problems
- // but i seriously doubt it'll be bad.
- dvars::callback::on_new_value("fs_game", []()
- {
- console::warn("fs_game value changed, filesystem paths will be adjusted to new dvar value.");
- register_fs_game_path();
- });
-
if (game::environment::is_sp())
{
add_commands_sp();
@@ -702,7 +669,6 @@ namespace command
const auto name = params.get(1);
const auto dvar = game::Dvar_FindVar(name);
-
if (dvar == nullptr)
{
console::info("%s doesn't exist\n", name);
diff --git a/src/client/component/command.hpp b/src/client/component/command.hpp
index a891b802..06228c9d 100644
--- a/src/client/component/command.hpp
+++ b/src/client/component/command.hpp
@@ -49,6 +49,4 @@ namespace command
void add_sv(const char* name, std::function callback);
void execute(std::string command, bool sync = false);
-
- void register_fs_game_path();
}
\ No newline at end of file
diff --git a/src/client/component/dedicated_info.cpp b/src/client/component/dedicated_info.cpp
index 8164345b..d8fbb1d6 100644
--- a/src/client/component/dedicated_info.cpp
+++ b/src/client/component/dedicated_info.cpp
@@ -21,7 +21,7 @@ namespace dedicated_info
scheduler::loop([]
{
- auto* sv_running = game::Dvar_FindVar("sv_running");
+ const auto sv_running = game::Dvar_FindVar("sv_running");
if (!sv_running || !sv_running->current.enabled || (*game::mp::svs_clients) == nullptr)
{
SetConsoleTitle("H1-Mod Dedicated Server");
diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp
index 7677662f..689d0006 100644
--- a/src/client/component/discord.cpp
+++ b/src/client/component/discord.cpp
@@ -5,7 +5,6 @@
#include "command.hpp"
#include "discord.hpp"
#include "materials.hpp"
-#include "network.hpp"
#include "party.hpp"
#include "scheduler.hpp"
@@ -28,119 +27,181 @@ namespace discord
{
namespace
{
- DiscordRichPresence discord_presence;
-
- void update_discord()
+ struct discord_presence_state_t
{
- if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded())
+ int start_timestamp;
+ int party_size;
+ int party_max;
+ };
+
+ struct discord_presence_strings_t
+ {
+ std::string state;
+ std::string details;
+ std::string small_image_key;
+ std::string small_image_text;
+ std::string large_image_key;
+ std::string large_image_text;
+ std::string party_id;
+ std::string join_secret;
+ };
+
+ DiscordRichPresence discord_presence{};
+ discord_presence_strings_t discord_strings;
+
+ std::mutex avatar_map_mutex;
+ std::unordered_map avatar_material_map;
+ game::Material* default_avatar_material{};
+
+ void update_discord_frontend()
+ {
+ discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer");
+ discord_presence.startTimestamp = 0;
+
+ static const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange");
+ if (in_firing_range != nullptr && in_firing_range->current.enabled == 1)
{
- discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer");
- discord_presence.state = "Main Menu";
-
- const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange");
- if (in_firing_range && in_firing_range->current.enabled == 1)
- {
- discord_presence.state = "Firing Range";
- }
-
- discord_presence.partySize = 0;
- discord_presence.partyMax = 0;
- discord_presence.startTimestamp = 0;
- discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer");
-
- // set to blank when in lobby
- discord_presence.matchSecret = "";
- discord_presence.joinSecret = "";
- discord_presence.partyId = "";
+ discord_presence.state = "Firing Range";
+ discord_presence.largeImageKey = "mp_vlobby_room";
}
else
{
- static char details[0x80] = {0};
- const auto map = game::Dvar_FindVar("mapname")->current.string;
- const auto key = utils::string::va("PRESENCE_%s%s", SELECT_VALUE("SP_", ""), map);
- const char* mapname = map;
-
- if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE, key) && !game::DB_IsXAssetDefault(game::ASSET_TYPE_LOCALIZE, key))
- {
- mapname = game::UI_SafeTranslateString(key);
- }
-
- if (game::environment::is_mp())
- {
- static char clean_gametype[0x80] = {0};
- const auto gametype = game::UI_GetGameTypeDisplayName(
- game::Dvar_FindVar("g_gametype")->current.string);
- utils::string::strip(gametype,
- clean_gametype, sizeof(clean_gametype));
- strcpy_s(details, 0x80, utils::string::va("%s on %s", clean_gametype, mapname));
-
- static char clean_hostname[0x80] = {0};
- utils::string::strip(game::Dvar_FindVar("sv_hostname")->current.string,
- clean_hostname, sizeof(clean_hostname));
- auto max_clients = party::server_client_count();
-
- if (game::SV_Loaded())
- {
- strcpy_s(clean_hostname, "Private Match");
- max_clients = game::Dvar_FindVar("sv_maxclients")->current.integer;
- discord_presence.partyPrivacy = DISCORD_PARTY_PRIVATE;
- }
- else
- {
- const auto server_net_info = party::get_state_host();
- const auto server_ip_port = utils::string::va("%i.%i.%i.%i:%i",
- static_cast(server_net_info.ip[0]),
- static_cast(server_net_info.ip[1]),
- static_cast(server_net_info.ip[2]),
- static_cast(server_net_info.ip[3]),
- static_cast(ntohs(server_net_info.port))
- );
-
- static char join_secret[0x80] = {0};
- strcpy_s(join_secret, 0x80, server_ip_port);
-
- static char party_id[0x80] = {0};
- const auto server_ip_port_hash = utils::cryptography::sha1::compute(server_ip_port, true).substr(0, 8);
- strcpy_s(party_id, 0x80, server_ip_port_hash.data());
-
- discord_presence.partyId = party_id;
- discord_presence.joinSecret = join_secret;
- discord_presence.partyPrivacy = DISCORD_PARTY_PUBLIC;
- }
-
- const auto client_state = *game::mp::client_state;
- if (client_state != nullptr)
- {
- discord_presence.partySize = client_state->num_players;
- }
- else
- {
- discord_presence.partySize = 0;
- }
-
- discord_presence.partyMax = max_clients;
- discord_presence.state = clean_hostname;
- discord_presence.largeImageKey = map;
- }
- else if (game::environment::is_sp())
- {
- discord_presence.state = "";
- discord_presence.largeImageKey = map;
- strcpy_s(details, 0x80, mapname);
- }
-
- discord_presence.details = details;
-
- if (!discord_presence.startTimestamp)
- {
- discord_presence.startTimestamp = std::chrono::duration_cast(
- std::chrono::system_clock::now().time_since_epoch()).count();
- }
+ discord_presence.state = "Main Menu";
+ discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer");
}
Discord_UpdatePresence(&discord_presence);
}
+ void update_discord_ingame()
+ {
+ static const auto mapname_dvar = game::Dvar_FindVar("mapname");
+ auto mapname = mapname_dvar->current.string;
+
+ discord_strings.large_image_key = mapname;
+
+ const auto presence_key = utils::string::va("PRESENCE_%s%s", SELECT_VALUE("SP_", ""), mapname);
+ if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE, presence_key) &&
+ !game::DB_IsXAssetDefault(game::ASSET_TYPE_LOCALIZE, presence_key))
+ {
+ mapname = game::UI_SafeTranslateString(presence_key);
+ }
+
+ if (game::environment::is_mp())
+ {
+ static const auto gametype_dvar = game::Dvar_FindVar("g_gametype");
+ static const auto max_clients_dvar = game::Dvar_FindVar("sv_maxclients");
+ static const auto hostname_dvar = game::Dvar_FindVar("sv_hostname");
+
+ const auto gametype_display_name = game::UI_GetGameTypeDisplayName(gametype_dvar->current.string);
+ const auto gametype = utils::string::strip(gametype_display_name);
+
+ discord_strings.details = std::format("{} on {}", gametype, mapname);
+
+ const auto client_state = *game::mp::client_state;
+ if (client_state != nullptr)
+ {
+ discord_presence.partySize = client_state->num_players;
+ }
+
+ if (game::SV_Loaded())
+ {
+ discord_strings.state = "Private Match";
+ discord_presence.partyMax = max_clients_dvar->current.integer;
+ discord_presence.partyPrivacy = DISCORD_PARTY_PRIVATE;
+ }
+ else
+ {
+ discord_strings.state = utils::string::strip(hostname_dvar->current.string);
+
+ const auto server_connection_state = party::get_server_connection_state();
+ const auto server_ip_port = std::format("{}.{}.{}.{}:{}",
+ static_cast(server_connection_state.host.ip[0]),
+ static_cast(server_connection_state.host.ip[1]),
+ static_cast(server_connection_state.host.ip[2]),
+ static_cast(server_connection_state.host.ip[3]),
+ static_cast(ntohs(server_connection_state.host.port))
+ );
+
+ discord_strings.party_id = utils::cryptography::sha1::compute(server_ip_port, true).substr(0, 8);
+ discord_presence.partyMax = server_connection_state.max_clients;
+ discord_presence.partyPrivacy = DISCORD_PARTY_PUBLIC;
+ discord_strings.join_secret = server_ip_port;
+ }
+
+ auto server_discord_info = party::get_server_discord_info();
+ if (server_discord_info.has_value())
+ {
+ discord_strings.small_image_key = server_discord_info->image;
+ discord_strings.small_image_text = server_discord_info->image_text;
+ }
+ }
+ else if (game::environment::is_sp())
+ {
+ discord_strings.details = mapname;
+ }
+
+ if (discord_presence.startTimestamp == 0)
+ {
+ discord_presence.startTimestamp = std::chrono::duration_cast(
+ std::chrono::system_clock::now().time_since_epoch()).count();
+ }
+
+ discord_presence.state = discord_strings.state.data();
+ discord_presence.details = discord_strings.details.data();
+ discord_presence.smallImageKey = discord_strings.small_image_key.data();
+ discord_presence.smallImageText = discord_strings.small_image_text.data();
+ discord_presence.largeImageKey = discord_strings.large_image_key.data();
+ discord_presence.largeImageText = discord_strings.large_image_text.data();
+ discord_presence.partyId = discord_strings.party_id.data();
+ discord_presence.joinSecret = discord_strings.join_secret.data();
+
+ Discord_UpdatePresence(&discord_presence);
+ }
+
+ void update_discord()
+ {
+ const auto saved_time = discord_presence.startTimestamp;
+ discord_presence = {};
+ discord_presence.startTimestamp = saved_time;
+
+ if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded())
+ {
+ update_discord_frontend();
+ }
+ else
+ {
+ update_discord_ingame();
+ }
+ }
+
+ game::Material* create_avatar_material(const std::string& name, const std::string& data)
+ {
+ const auto material = materials::create_material(name);
+ try
+ {
+ if (!materials::setup_material_image(material, data))
+ {
+ materials::free_material(material);
+ return nullptr;
+ }
+
+ {
+ std::lock_guard _0(avatar_map_mutex);
+ avatar_material_map.insert(std::make_pair(name, material));
+ }
+
+ return material;
+ }
+ catch (const std::exception& e)
+ {
+ materials::free_material(material);
+ console::error("Failed to load user avatar image: %s\n", e.what());
+ }
+
+ return nullptr;
+ }
+
void download_user_avatar(const std::string& id, const std::string& avatar)
{
const auto data = utils::http::get_data(
@@ -156,10 +217,10 @@ namespace discord
return;
}
- materials::add(utils::string::va(AVATAR, id.data()), value.buffer);
+ const auto name = utils::string::va(AVATAR, id.data());
+ create_avatar_material(name, value.buffer);
}
- bool has_default_avatar = false;
void download_default_avatar()
{
const auto data = utils::http::get_data(DEFAULT_AVATAR_URL);
@@ -174,25 +235,146 @@ namespace discord
return;
}
- has_default_avatar = true;
- materials::add(DEFAULT_AVATAR, value.buffer);
+ default_avatar_material = create_avatar_material(DEFAULT_AVATAR, value.buffer);
+ }
+
+ void ready(const DiscordUser* request)
+ {
+ DiscordRichPresence presence{};
+ presence.instance = 1;
+ presence.state = "";
+ console::info("Discord: Ready on %s (%s)\n", request->username, request->userId);
+ Discord_UpdatePresence(&presence);
+ }
+
+ void errored(const int error_code, const char* message)
+ {
+ console::error("Discord: %s (%i)\n", message, error_code);
+ }
+
+ void join_game(const char* join_secret)
+ {
+ console::debug("Discord: join_game called with secret '%s'\n", join_secret);
+
+ scheduler::once([=]
+ {
+ game::netadr_s target{};
+ if (game::NET_StringToAdr(join_secret, &target))
+ {
+ console::info("Discord: Connecting to server '%s'\n", join_secret);
+ party::connect(target);
+ }
+ }, scheduler::pipeline::main);
+ }
+
+ std::string get_display_name(const DiscordUser* user)
+ {
+ if (user->discriminator != nullptr && user->discriminator != "0"s)
+ {
+ return std::format("{}#{}", user->username, user->discriminator);
+ }
+ else if (user->globalName[0] != 0)
+ {
+ return user->globalName;
+ }
+ else
+ {
+ return user->username;
+ }
+ }
+
+ void join_request(const DiscordUser* request)
+ {
+ console::debug("Discord: Join request from %s (%s)\n", request->username, request->userId);
+
+ if (game::Com_InFrontend() || !ui_scripting::lui_running())
+ {
+ Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
+ return;
+ }
+
+ static std::unordered_map last_requests;
+
+ const std::string user_id = request->userId;
+ const std::string avatar = request->avatar;
+ const std::string discriminator = request->discriminator;
+ const std::string username = request->username;
+ const auto display_name = get_display_name(request);
+
+ const auto now = std::chrono::high_resolution_clock::now();
+ auto iter = last_requests.find(user_id);
+ if (iter != last_requests.end())
+ {
+ if ((now - iter->second) < 15s)
+ {
+ return;
+ }
+ else
+ {
+ iter->second = now;
+ }
+ }
+ else
+ {
+ last_requests.insert(std::make_pair(user_id, now));
+ }
+
+ scheduler::once([=]
+ {
+ const ui_scripting::table request_table{};
+ request_table.set("avatar", avatar);
+ request_table.set("discriminator", discriminator);
+ request_table.set("userid", user_id);
+ request_table.set("username", username);
+ request_table.set("displayname", display_name);
+
+ ui_scripting::notify("discord_join_request",
+ {
+ {"request", request_table}
+ });
+ }, scheduler::pipeline::lui);
+
+ const auto material_name = utils::string::va(AVATAR, user_id.data());
+ if (!avatar.empty() && !avatar_material_map.contains(material_name))
+ {
+ download_user_avatar(user_id, avatar);
+ }
+ }
+
+ void set_default_bindings()
+ {
+ const auto set_binding = [](const std::string& command, const game::keyNum_t key)
+ {
+ const auto binding = game::Key_GetBindingForCmd(command.data());
+ for (auto i = 0; i < 256; i++)
+ {
+ if (game::playerKeys[0].keys[i].binding == binding)
+ {
+ return;
+ }
+ }
+
+ if (game::playerKeys[0].keys[key].binding == 0)
+ {
+ game::Key_SetBinding(0, key, binding);
+ }
+ };
+
+ set_binding("discord_accept", game::K_F1);
+ set_binding("discord_deny", game::K_F2);
}
}
- std::string get_avatar_material(const std::string& id)
+ game::Material* get_avatar_material(const std::string& id)
{
- const auto avatar_name = utils::string::va(AVATAR, id.data());
- if (materials::exists(avatar_name))
+ const auto material_name = utils::string::va(AVATAR, id.data());
+ const auto iter = avatar_material_map.find(material_name);
+ if (iter == avatar_material_map.end())
{
- return avatar_name;
+ return default_avatar_material;
}
- if (has_default_avatar)
- {
- return DEFAULT_AVATAR;
- }
-
- return "black";
+ return iter->second;
}
void respond(const std::string& id, int reply)
@@ -206,15 +388,14 @@ namespace discord
class component final : public component_interface
{
public:
- void post_load() override
+ void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
- DiscordEventHandlers handlers;
- ZeroMemory(&handlers, sizeof(handlers));
+ DiscordEventHandlers handlers{};
handlers.ready = ready;
handlers.errored = errored;
handlers.disconnected = errored;
@@ -233,16 +414,29 @@ namespace discord
Discord_Initialize("947125042930667530", &handlers, 1, nullptr);
- scheduler::once(download_default_avatar, scheduler::pipeline::async);
-
- scheduler::once([]
+ if (game::environment::is_mp())
{
- scheduler::once(update_discord, scheduler::pipeline::async);
- scheduler::loop(update_discord, scheduler::pipeline::async, 5s);
- scheduler::loop(Discord_RunCallbacks, scheduler::pipeline::async, 1s);
- }, scheduler::pipeline::main);
+ scheduler::on_game_initialized([]
+ {
+ scheduler::once(download_default_avatar, scheduler::async);
+ set_default_bindings();
+ }, scheduler::main);
+ }
+
+ scheduler::loop(Discord_RunCallbacks, scheduler::async, 500ms);
+ scheduler::loop(update_discord, scheduler::async, 5s);
initialized_ = true;
+
+ command::add("discord_accept", []()
+ {
+ ui_scripting::notify("discord_response", {{"accept", true}});
+ });
+
+ command::add("discord_deny", []()
+ {
+ ui_scripting::notify("discord_response", {{"accept", false}});
+ });
}
void pre_destroy() override
@@ -257,67 +451,6 @@ namespace discord
private:
bool initialized_ = false;
-
- static void ready(const DiscordUser* request)
- {
- ZeroMemory(&discord_presence, sizeof(discord_presence));
- discord_presence.instance = 1;
- console::info("Discord: Ready on %s (%s)\n", request->username, request->userId);
- Discord_UpdatePresence(&discord_presence);
- }
-
- static void errored(const int error_code, const char* message)
- {
- console::error("Discord: Error (%i): %s\n", error_code, message);
- }
-
- static void join_game(const char* join_secret)
- {
- scheduler::once([=]
- {
- game::netadr_s target{};
- if (game::NET_StringToAdr(join_secret, &target))
- {
- console::info("Discord: Connecting to server: %s\n", join_secret);
- party::connect(target);
- }
- }, scheduler::pipeline::main);
- }
-
- static void join_request(const DiscordUser* request)
- {
- console::info("Discord: Join request from %s (%s)\n", request->username, request->userId);
-
- if (game::Com_InFrontend() || !ui_scripting::lui_running())
- {
- Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
- return;
- }
-
- std::string user_id = request->userId;
- std::string avatar = request->avatar;
- std::string discriminator = request->discriminator;
- std::string username = request->username;
-
- scheduler::once([=]
- {
- const ui_scripting::table request_table{};
- request_table.set("avatar", avatar);
- request_table.set("discriminator", discriminator);
- request_table.set("userid", user_id);
- request_table.set("username", username);
-
- ui_scripting::notify("discord_join_request",
- {
- {"request", request_table}
- });
- }, scheduler::pipeline::lui);
-
- if (!materials::exists(utils::string::va(AVATAR, user_id.data())))
- {
- download_user_avatar(user_id, avatar);
- }
- }
};
}
diff --git a/src/client/component/discord.hpp b/src/client/component/discord.hpp
index 5399f952..54c75ca9 100644
--- a/src/client/component/discord.hpp
+++ b/src/client/component/discord.hpp
@@ -1,7 +1,9 @@
#pragma once
+#include "game/game.hpp"
+
namespace discord
{
- std::string get_avatar_material(const std::string& id);
+ game::Material* get_avatar_material(const std::string& id);
void respond(const std::string& id, int reply);
}
diff --git a/src/client/component/download.cpp b/src/client/component/download.cpp
index 9d4ce938..18e152ee 100644
--- a/src/client/component/download.cpp
+++ b/src/client/component/download.cpp
@@ -8,6 +8,8 @@
#include "game/ui_scripting/execution.hpp"
+#include "utils/hash.hpp"
+
#include
#include
#include
@@ -23,10 +25,16 @@ namespace download
bool active{};
};
+ std::atomic_bool kill_downloads = false;
utils::concurrency::container globals;
bool download_aborted()
{
+ if (kill_downloads)
+ {
+ return true;
+ }
+
return globals.access([](globals_t& globals_)
{
return globals_.abort;
@@ -159,10 +167,10 @@ namespace download
const auto url = utils::string::va("%s/%s", base.data(), file.name.data());
console::debug("Downloading %s from %s: %s\n", file.name.data(), base.data(), url);
- const auto data = utils::http::get_data(url, {}, {}, &progress_callback);
+ auto data = utils::http::get_data(url, {}, {}, &progress_callback);
if (!data.has_value())
{
- menu_error("Download failed: An unknown error occurred, please try again.");
+ menu_error(utils::string::va("Download failed: An unknown error occurred when getting data from '%s', please try again.", url));
return;
}
@@ -171,9 +179,16 @@ namespace download
return;
}
- const auto& result = data.value();
+ auto& result = data.value();
if (result.code != CURLE_OK)
{
+ if (result.code == CURLE_COULDNT_CONNECT)
+ {
+ menu_error(utils::string::va("Download failed: Couldn't connect to server '%s' (%i)\n",
+ url, result.code));
+ return;
+ }
+
menu_error(utils::string::va("Download failed: %s (%i)\n",
curl_easy_strerror(result.code), result.code));
return;
@@ -181,15 +196,15 @@ namespace download
if (result.response_code >= 400)
{
- menu_error(utils::string::va("Download failed: Server returned bad response code %i\n",
+ menu_error(utils::string::va("Download failed: Server returned bad response code (%i)\n",
result.response_code));
return;
}
- const auto hash = utils::cryptography::sha1::compute(result.buffer, true);
+ const auto hash = utils::hash::get_buffer_hash(result.buffer, file.name);
if (hash != file.hash)
{
- menu_error(utils::string::va("Download failed: file hash doesn't match the server's (%s: %s != %s)\n",
+ menu_error(utils::string::va("Download failed: File hash doesn't match the server's (%s: %s != %s)\n",
file.name.data(), hash.data(), file.hash.data()));
return;
}
@@ -225,7 +240,18 @@ namespace download
scheduler::once([]
{
ui_scripting::notify("mod_download_done", {});
- party::menu_error("Download for server mod has been cancelled.");
+ party::menu_error("Download failed: Aborted");
}, scheduler::pipeline::lui);
}
+
+ class component final : public component_interface
+ {
+ public:
+ void pre_destroy() override
+ {
+ kill_downloads = true;
+ }
+ };
}
+
+REGISTER_COMPONENT(download::component)
diff --git a/src/client/component/dvars.cpp b/src/client/component/dvars.cpp
index 3fec1fd4..d6cfc5aa 100644
--- a/src/client/component/dvars.cpp
+++ b/src/client/component/dvars.cpp
@@ -2,8 +2,10 @@
#include "loader/component_loader.hpp"
#include "dvars.hpp"
+#include "console.hpp"
#include "game/game.hpp"
+#include "game/dvars.hpp"
#include
@@ -255,13 +257,13 @@ namespace dvars
namespace callback
{
- static std::unordered_map> new_value_callbacks;
+ static std::unordered_map> dvar_new_value_callbacks;
static std::unordered_map> dvar_on_register_function_map;
- void on_new_value(const std::string& name, const std::function callback)
+ void on_new_value(const std::string& name, const std::function callback)
{
- new_value_callbacks[game::generateHashValue(name.data())] = callback;
+ dvar_new_value_callbacks[game::generateHashValue(name.data())] = callback;
}
void on_register(const std::string& name, const std::function& callback)
@@ -532,15 +534,34 @@ namespace dvars
{
dvar_set_variant_hook.invoke(dvar, value, source);
- if (callback::new_value_callbacks.find(dvar->hash) != callback::new_value_callbacks.end())
+ if (callback::dvar_new_value_callbacks.contains(dvar->hash))
{
- callback::new_value_callbacks[dvar->hash]();
+ callback::dvar_new_value_callbacks[dvar->hash](value);
}
}
class component final : public component_interface
{
public:
+ void post_start() override
+ {
+ try
+ {
+ const auto list_json = utils::nt::load_resource(DVAR_LIST);
+ const auto list = nlohmann::json::parse(list_json);
+ for (const auto& [_0, dvar_info] : list.items())
+ {
+ const auto name = dvar_info[0].get();
+ const auto description = dvar_info[1].get();
+ dvars::insert_dvar_info(name, description);
+ }
+ }
+ catch (const std::exception& e)
+ {
+ console::error("Failed to parse dvar list: %s\n", e.what());
+ }
+ }
+
void post_unpack() override
{
dvar_register_bool_hook.create(SELECT_VALUE(0x419220_b, 0x182340_b), &dvar_register_bool);
diff --git a/src/client/component/dvars.hpp b/src/client/component/dvars.hpp
index 3c9831c8..707f229a 100644
--- a/src/client/component/dvars.hpp
+++ b/src/client/component/dvars.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "game/game.hpp"
+
namespace dvars
{
namespace disable
@@ -29,7 +31,7 @@ namespace dvars
namespace callback
{
- void on_new_value(const std::string& name, const std::function callback);
+ void on_new_value(const std::string& name, const std::function callback);
void on_register(const std::string& name, const std::function& callback);
}
diff --git a/src/client/component/experimental.cpp b/src/client/component/experimental.cpp
new file mode 100644
index 00000000..e9fc4d21
--- /dev/null
+++ b/src/client/component/experimental.cpp
@@ -0,0 +1,30 @@
+#include
+#include "loader/component_loader.hpp"
+
+#include "dvars.hpp"
+#include "scheduler.hpp"
+
+#include "game/game.hpp"
+#include "game/dvars.hpp"
+
+#include
+
+namespace experimental
+{
+ class component final : public component_interface
+ {
+ public:
+ void post_unpack() override
+ {
+ // fix static model's lighting going black sometimes
+ //dvars::override::register_int("r_smodelInstancedThreshold", 0, 0, 128, 0x0);
+
+ // change minimum cap to -2000 instead of -1000 (culling issue)
+ dvars::override::register_float("r_lodBiasRigid", 0, -2000, 0, game::DVAR_FLAG_SAVED);
+ }
+ };
+}
+
+#ifdef DEBUG
+REGISTER_COMPONENT(experimental::component)
+#endif
\ No newline at end of file
diff --git a/src/client/component/fastfiles.cpp b/src/client/component/fastfiles.cpp
index a85bfb8b..36e04289 100644
--- a/src/client/component/fastfiles.cpp
+++ b/src/client/component/fastfiles.cpp
@@ -5,6 +5,8 @@
#include "console.hpp"
#include "fastfiles.hpp"
#include "filesystem.hpp"
+#include "imagefiles.hpp"
+#include "weapon.hpp"
#include "game/dvars.hpp"
@@ -22,13 +24,17 @@ namespace fastfiles
{
utils::hook::detour db_try_load_x_file_internal_hook;
utils::hook::detour db_find_xasset_header_hook;
- game::dvar_t* g_dump_scripts;
- std::vector fastfile_handles;
+ game::dvar_t* g_dump_scripts;
+ game::dvar_t* db_print_default_assets;
+
+ utils::concurrency::container> fastfile_handles;
+ bool is_mod_pre_gfx = false;
void db_try_load_x_file_internal(const char* zone_name, const int flags)
{
console::info("Loading fastfile %s\n", zone_name);
+ is_mod_pre_gfx = zone_name == "mod_pre_gfx"s;
current_fastfile.access([&](std::string& fastfile)
{
fastfile = zone_name;
@@ -68,12 +74,13 @@ namespace fastfiles
dump_gsc_script(name, result);
}
+ const std::string override_asset_name = "override/"s + name;
+
if (type == game::XAssetType::ASSET_TYPE_RAWFILE)
{
if (result.rawfile)
{
- const std::string override_rawfile_name = "override/"s + name;
- const auto override_rawfile = db_find_xasset_header_hook.invoke(type, override_rawfile_name.data(), 0);
+ const auto override_rawfile = db_find_xasset_header_hook.invoke(type, override_asset_name.data(), 0);
if (override_rawfile.rawfile)
{
result.rawfile = override_rawfile.rawfile;
@@ -82,6 +89,25 @@ namespace fastfiles
}
}
+ if (type == game::XAssetType::ASSET_TYPE_STRINGTABLE)
+ {
+ if (result.stringTable)
+ {
+ const auto override_stringtable = db_find_xasset_header_hook.invoke(type, override_asset_name.data(), 0);
+ if (override_stringtable.stringTable)
+ {
+ result.stringTable = override_stringtable.stringTable;
+ console::debug("using override asset for stringtable: \"%s\"\n", name);
+ }
+ }
+ }
+
+ if (db_print_default_assets->current.enabled && game::DB_IsXAssetDefault(type, name))
+ {
+ console::warn("Waited %i msec for default asset \"%s\" of type \"%s\"\n",
+ diff, name, game::g_assetNames[type]);
+ }
+
if (diff > 100)
{
console::print(
@@ -214,7 +240,10 @@ namespace fastfiles
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr);
if (handle != INVALID_HANDLE_VALUE)
{
- fastfile_handles.push_back(handle);
+ fastfile_handles.access([&](std::vector& handles)
+ {
+ handles.push_back(handle);
+ });
}
return handle;
@@ -231,8 +260,9 @@ namespace fastfiles
const auto& usermap_value = usermap.value();
const std::string usermap_file = utils::string::va("%s.ff", usermap_value.data());
const std::string usermap_load_file = utils::string::va("%s_load.ff", usermap_value.data());
+ const std::string usermap_pak_file = utils::string::va("%s.pak", usermap_value.data());
- if (mapname == usermap_file || mapname == usermap_load_file)
+ if (mapname == usermap_file || mapname == usermap_load_file || mapname == usermap_pak_file)
{
const auto path = utils::string::va("usermaps\\%s\\%s",
usermap_value.data(), mapname.data());
@@ -288,7 +318,7 @@ namespace fastfiles
}
}
- if (name.ends_with(".ff"))
+ if (name.ends_with(".ff") || name.ends_with(".pak"))
{
handle = find_fastfile(name, true);
}
@@ -336,11 +366,17 @@ namespace fastfiles
void load_pre_gfx_zones(game::XZoneInfo* zoneInfo, unsigned int zoneCount, game::DBSyncMode syncMode)
{
+ imagefiles::close_custom_handles();
+
std::vector data;
merge(&data, zoneInfo, zoneCount);
// code_pre_gfx
+ weapon::clear_modifed_enums();
+ try_load_zone("mod_pre_gfx", true);
+ try_load_zone("h1_mod_pre_gfx", true);
+
game::DB_LoadXAssets(data.data(), static_cast(data.size()), syncMode);
}
@@ -429,6 +465,642 @@ namespace fastfiles
console::warn("No aipaths found for this map\n");
}
}
+
+ int format_bsp_name(char* filename, int size, const char* mapname)
+ {
+ std::string name = mapname;
+ auto fmt = "maps/%s.d3dbsp";
+ if (name.starts_with("mp_"))
+ {
+ fmt = "maps/mp/%s.d3dbsp";
+ }
+
+ return game::Com_sprintf(filename, size, fmt, mapname);
+ }
+
+ void get_bsp_filename_stub(char* filename, int size, const char* mapname)
+ {
+ auto base_mapname = mapname;
+ game::Com_IsAddonMap(mapname, &base_mapname);
+ format_bsp_name(filename, size, base_mapname);
+ }
+
+ utils::hook::detour image_file_decrypt_value_hook;
+ bool image_file_decrypt_value_stub(char* value, int size, char* buffer)
+ {
+ auto is_all_zero = true;
+ for (auto i = 0; i < size; i++)
+ {
+ if (value[i] != 0)
+ {
+ is_all_zero = false;
+ }
+ }
+
+ if (is_all_zero)
+ {
+ return true;
+ }
+
+ return image_file_decrypt_value_hook.invoke(value, size, buffer);
+ }
+
+ int com_sprintf_stub(char* dest, int size, const char* /*fmt*/, const char* mapname)
+ {
+ return format_bsp_name(dest, size, mapname);
+ }
+
+ const char* get_zone_name(const unsigned int index)
+ {
+ if (game::environment::is_sp())
+ {
+ return game::sp::g_zones[index].name;
+ }
+ else
+ {
+ return game::mp::g_zones[index].name;
+ }
+ }
+
+ utils::hook::detour db_unload_x_zones_hook;
+ void db_unload_x_zones_stub(const unsigned short* unload_zones,
+ const unsigned int unload_count, const bool create_default)
+ {
+ for (auto i = 0u; i < unload_count; i++)
+ {
+ const auto zone_name = get_zone_name(unload_zones[i]);
+ if (zone_name[0] != '\0')
+ {
+ imagefiles::close_handle(zone_name);
+ }
+ }
+
+ db_unload_x_zones_hook.invoke(unload_zones, unload_count, create_default);
+ }
+
+ namespace mp
+ {
+ constexpr unsigned int get_asset_type_size(const game::XAssetType type)
+ {
+ constexpr int asset_type_sizes[] =
+ {
+ 96, 88, 128, 56, 40, 216,
+ 56, 680, 592, 32, 32, 32,
+ 32, 32, 2112, 1936, 104,
+ 32, 24, 152, 152, 152, 16,
+ 64, 640, 40, 16, 136, 24,
+ 296, 176, 2864, 48, 0, 24,
+ 200, 88, 16, 144, 3616, 56,
+ 64, 16, 16, 0, 0, 0, 0, 24,
+ 40, 24, 48, 40, 24, 16, 80,
+ 128, 2256, 136, 32, 72,
+ 24, 64, 88, 48, 32, 96, 152,
+ 64, 32, 32,
+ };
+
+ return asset_type_sizes[type];
+ }
+
+ constexpr unsigned int get_pool_type_size(const game::XAssetType type)
+ {
+ constexpr int asset_pool_sizes[] =
+ {
+ 128, 256, 16, 1, 128, 5000,
+ 5248, 4352, 17536, 256, 49152,
+ 12288, 12288, 72864, 512,
+ 2750, 23264, 16000, 256, 64,
+ 64, 64, 64, 8000, 1, 1, 1, 1,
+ 1, 2, 1, 1, 32, 0, 128, 910,
+ 16, 14100, 128, 200, 1, 2048,
+ 4, 6, 0, 0, 0, 0, 1024, 768,
+ 400, 128, 128, 24, 24, 24,
+ 32, 32, 2, 128, 64, 384, 128,
+ 1, 128, 64, 32, 32, 16, 32, 16
+ };
+
+ return asset_pool_sizes[type];
+ }
+
+ template
+ char* reallocate_asset_pool()
+ {
+ constexpr auto element_size = get_asset_type_size(Type);
+ static char new_pool[element_size * Size] = {0};
+ static_assert(element_size != 0);
+ assert(element_size == game::DB_GetXAssetTypeSize(Type));
+
+ std::memmove(new_pool, game::g_assetPool[Type], game::g_poolSize[Type] * element_size);
+
+ game::g_assetPool[Type] = new_pool;
+ game::g_poolSize[Type] = Size;
+
+ return new_pool;
+ }
+
+ template
+ char* reallocate_asset_pool_multiplier()
+ {
+ constexpr auto pool_size = get_pool_type_size(Type);
+ return reallocate_asset_pool();
+ }
+
+#define RVA(ptr) static_cast(reinterpret_cast(ptr) - 0_b)
+
+ struct buffer_info
+ {
+ void* ptr;
+ size_t size;
+ };
+
+ std::vector string_buffers;
+ void memset_stub(void* place, int value, size_t size)
+ {
+ for (const auto& buffer : string_buffers)
+ {
+ std::memset(buffer.ptr, 0, buffer.size);
+ }
+
+ std::memset(place, value, size);
+ }
+
+ void reallocate_weapon_pool()
+ {
+ constexpr auto multiplier = 2;
+ constexpr auto pool_size = get_pool_type_size(game::ASSET_TYPE_WEAPON) * multiplier;
+ static void* weapon_complete_defs[pool_size]{};
+ static void* weapon_strings[pool_size]{};
+
+ string_buffers.emplace_back(weapon_strings, pool_size * sizeof(void*));
+
+ utils::hook::set(0x1186A4_b + 4, RVA(weapon_strings));
+ utils::hook::set(0x1186B5_b + 4, RVA(weapon_strings));
+ utils::hook::set(0x104BD2_b + 4, RVA(weapon_strings) - 0x38F1750);
+
+ reallocate_asset_pool();
+
+ utils::hook::inject(0x2E3005_b + 3,
+ reinterpret_cast(reinterpret_cast(weapon_complete_defs) + 8));
+
+ utils::hook::inject(0xED734_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1D59F4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DCEDB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7BB5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7D35_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2ECCD0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x429B84_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E1DFD_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E21AB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1E8BC9_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DDBA6_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x549FF0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x563D20_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x563E04_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x618464_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DB218_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41ECDC_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x42C882_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xEFE22_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1199DD_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x11D857_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x128E28_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DB83B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DC5BC_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E2549_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E29DF_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6337_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7963_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2F0BA3_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x3044C3_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x305118_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41B385_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x42544A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x425EAB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x426971_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x42ACA7_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x10A173_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D922C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DD2D0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DE6C4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2F041F_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41E2BC_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41F054_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x427487_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x461657_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A351_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A524_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x567328_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DE83F_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DF050_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EBFE0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DF290_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A6A0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xE97A0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xE97F0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xE9900_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xE9954_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xEDAEC_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1BA6FC_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1E99B2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1E9AD2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2818CA_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2845FD_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x284C2D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x285694_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x285C2C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2C606A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2CD275_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2CD2B4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D50A4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D8B20_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DC824_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DCDE1_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DEA7C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E1463_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E14EF_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E15EB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E17FB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E18EB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E19EB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E339E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E360A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E3CE0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E56C8_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E5840_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E58BB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E5FE2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6890_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E68F0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6960_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6AB0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6CA0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7640_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E76A0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7700_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7760_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E77C0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7A80_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E8753_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EA650_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EB870_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EC488_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EFD84_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x427D5C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4288F8_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x428C89_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x43748E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4376AE_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x43796E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54953B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A21F_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A5F0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A7E7_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A8D9_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54ADA0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54BAC0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x472198_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x285FF2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2C3154_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2C3AC0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DD193_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DECC4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DEE68_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E16EB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E1ACB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E1CFB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E1EDB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E2015_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E20AB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7530_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E8950_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EBA5C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x30307B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x30308E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x30917E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41AE27_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x549354_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54A867_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D4FDB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xEB3BA_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xFDC77_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1072EB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x11C14E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1270D5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x12868F_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x128848_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2C4160_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7B12_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EA7C6_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EAE75_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EB077_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41BB9D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41E64B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41E868_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41EBCB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x426172_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4262A0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x439669_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x45E912_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46284E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46D658_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46DF93_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xD597B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xF3375_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x121F3A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1BA9C8_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1D3F28_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2C4220_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D70DB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DB108_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E2FC1_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EB8D9_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EBB85_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x40C304_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x42A795_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x472530_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E9939_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E7F79_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x117129_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DE589_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E4D2E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E4E73_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E578E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6686_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6DCD_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41E05A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41E1D3_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x428ECE_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54BB26_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x6183A6_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D6FF6_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EBCED_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E041A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xF38FE_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xF7880_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x102153_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1021FB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x10415B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1168F5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x126C09_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x180552_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1CCFD0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1D929F_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1D9575_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1E8E0E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1E98CC_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D4EF3_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D638B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DC023_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E31D7_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E3EC8_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E9364_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2FCEDF_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x324376_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x440497_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x460237_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46025A_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x461200_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46EBE4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46EE70_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46F4E1_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46FD48_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x5F2479_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x6641D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1074F2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1C7B8D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DE40E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DE8B5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DF63C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DF7DF_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E0CD9_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E2ADD_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E2B7C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E2DB1_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E4C10_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E9F50_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EA0B0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EA535_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2ED0A5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x305581_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x3236BD_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x3F2CDA_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x407BE5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4155E0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41C61D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41D96E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41DA71_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x42A442_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x42F56F_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x5482BB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54AA1D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1075A9_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xC5394_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xEEC4D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x11C2E2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x11E6F2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1994C0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x1E9DE0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x285E44_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E1BE1_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E3F34_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E474C_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E6B2E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E9530_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E95B0_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EC08D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2ECC4D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EFD59_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2FCEFE_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2FCF15_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x328444_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x40F47E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4169AB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41C481_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41E742_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41EE62_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x41EF3E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x45FD83_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46181E_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x46EDD8_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54924D_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x54AFA5_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x617F77_b + 3, weapon_complete_defs);
+ utils::hook::inject(0xF30DF_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2BC095_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D605B_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2D8CD1_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DA7DB_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2DC379_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E3121_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2E5953_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EA8C2_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x2EE9F7_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x303318_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x424D33_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x429149_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4299F4_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x4417C9_b + 3, weapon_complete_defs);
+ utils::hook::inject(0x471083_b + 3, weapon_complete_defs);
+
+ utils::hook::inject(0x2E0440_b + 3, weapon_complete_defs);
+
+ utils::hook::set(0x427EB1_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x4240A8_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x54B8B1_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x2D274B_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x311DA8_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x311EB8_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x323BD1_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x2E0864_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x2F170C_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42CB3B_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x136327_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x4671FF_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x46722F_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x46754C_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x565FA2_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x56600E_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x2EA352_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x2EA369_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x56483F_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x2EA337_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x402261_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x4022A9_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x41CED5_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42B540_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42B560_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x5660CB_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42B523_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x117C82_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x411438_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x12AB34_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x129F9B_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x12AC16_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x12AC9D_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x424087_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x54B897_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x129F5C_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42406A_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x54B87B_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x565D1B_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x123FF5_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42CB1B_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x42CAFE_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x136310_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x46752E_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x1362F3_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0xF454D_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x41CC61_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x41D7FB_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x427E8F_b + 4, RVA(&weapon_complete_defs));
+ utils::hook::set(0x427E72_b + 4, RVA(&weapon_complete_defs));
+
+ utils::hook::set(0x11865F_b + 4, RVA(&weapon_complete_defs));
+ }
+
+ void reallocate_attachment_pool()
+ {
+ constexpr auto multiplier = 2;
+ constexpr auto pool_size = get_pool_type_size(game::ASSET_TYPE_ATTACHMENT) * multiplier;
+ reallocate_asset_pool();
+
+ static void* attachment_array[pool_size]{};
+ static void* attachment_strings[pool_size]{};
+
+ string_buffers.emplace_back(attachment_strings, pool_size * sizeof(void*));
+
+ utils::hook::inject(0x118599_b + 3, attachment_strings);
+ utils::hook::inject(0x441187_b + 3, attachment_strings);
+ utils::hook::set(0x104C8A_b + 4, RVA(attachment_strings) - 0x38F1750);
+
+ const auto sub_118540_stub = [](utils::hook::assembler& a)
+ {
+ a.mov(rax, reinterpret_cast(&attachment_array[0]));
+ a.mov(r8d, pool_size);
+ a.mov(rdx, rax);
+ a.mov(ecx, game::ASSET_TYPE_ATTACHMENT);
+ a.call(0x59D460_b);
+ a.jmp(0x118573_b);
+ };
+
+ const auto loc_1185A0_stub = [](utils::hook::assembler& a)
+ {
+ a.mov(rax, reinterpret_cast(&attachment_array[0]));
+ a.push(rbx);
+ a.imul(rbx, 8);
+ a.mov(rcx, qword_ptr(rax, rbx));
+ a.pop(rbx);
+ a.cmp(qword_ptr(rcx, 8), 0);
+ a.lea(rsi, qword_ptr(rcx, 8));
+ a.jmp(0x1185AE_b);
+ };
+
+ utils::hook::jump(0x11855F_b, utils::hook::assemble(sub_118540_stub), true);
+ utils::hook::jump(0x1185A0_b, utils::hook::assemble(loc_1185A0_stub), true);
+ }
+
+ void reallocate_attachment_and_weapon()
+ {
+ // weapon & attachment strings are reset here (we need to also reset the reallocated ones)
+ utils::hook::call(0x13ABBA_b, memset_stub);
+ utils::hook::call(0x13AC5C_b, memset_stub);
+ utils::hook::call(0x13ACF0_b, memset_stub);
+ utils::hook::call(0x17D1C5_b, memset_stub);
+
+ reallocate_weapon_pool();
+ reallocate_attachment_pool();
+ }
+
+ void reallocate_sound_pool()
+ {
+ constexpr auto original_pool_size = get_pool_type_size(game::ASSET_TYPE_SOUND);
+ constexpr auto multiplier = 2;
+ constexpr auto pool_size = original_pool_size * multiplier;
+
+ const auto pool = reallocate_asset_pool();
+ utils::hook::inject(0x39621D_b + 3, reinterpret_cast(reinterpret_cast(pool) + 8));
+
+ static unsigned short net_const_string_sound_map[pool_size]{};
+ utils::hook::inject(0x2B0CEA_b + 3, net_const_string_sound_map);
+ utils::hook::inject(0x2B0F52_b + 3, net_const_string_sound_map);
+ utils::hook::inject(0x2B1866_b + 3, net_const_string_sound_map);
+ utils::hook::inject(0x2B1CC7_b + 3, net_const_string_sound_map);
+ }
+
+ void reallocate_asset_pools()
+ {
+ reallocate_attachment_and_weapon();
+ reallocate_sound_pool();
+ reallocate_asset_pool_multiplier();
+ reallocate_asset_pool_multiplier();
+ reallocate_asset_pool_multiplier();
+ }
+ }
+
+ void reallocate_asset_pools()
+ {
+ if (!game::environment::is_sp())
+ {
+ mp::reallocate_asset_pools();
+ }
+ }
+
+ utils::hook::detour db_link_x_asset_entry_hook;
+ game::XAssetEntry* db_link_x_asset_entry_stub(game::XAssetType type, game::XAssetHeader* header)
+ {
+ if (!is_mod_pre_gfx)
+ {
+ return db_link_x_asset_entry_hook.invoke(type, header);
+ }
+
+ static game::XAssetEntry entry{};
+
+ if (type != game::ASSET_TYPE_STRINGTABLE)
+ {
+ return &entry;
+ }
+
+ return db_link_x_asset_entry_hook.invoke(type, header);
+ }
}
bool exists(const std::string& zone, bool ignore_usermap)
@@ -466,10 +1138,14 @@ namespace fastfiles
void close_fastfile_handles()
{
- for (const auto& handle : fastfile_handles)
+ fastfile_handles.access([&](std::vector& handles)
{
- CloseHandle(handle);
- }
+ for (const auto& handle : handles)
+ {
+ CloseHandle(handle);
+ }
+ });
+
}
void set_usermap(const std::string& usermap)
@@ -521,12 +1197,28 @@ namespace fastfiles
SELECT_VALUE(0x1F5700_b, 0x39A620_b), &db_try_load_x_file_internal);
db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub);
+ db_unload_x_zones_hook.create(SELECT_VALUE(0x1F6040_b,
+ 0x39B3C0_b), db_unload_x_zones_stub);
+
+ db_print_default_assets = dvars::register_bool("db_printDefaultAssets",
+ false, game::DVAR_FLAG_SAVED, "Print default asset usage");
+
+ if (!game::environment::is_sp())
+ {
+ db_link_x_asset_entry_hook.create(0x396E80_b, db_link_x_asset_entry_stub);
+ }
+
g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts");
- // Allow loading of unsigned fastfiles
+ reallocate_asset_pools();
+
+ // Allow loading of unsigned fastfiles & imagefiles
if (!game::environment::is_sp())
{
utils::hook::nop(0x368153_b, 2); // DB_InflateInit
+
+ image_file_decrypt_value_hook.create(0x367520_b, image_file_decrypt_value_stub);
+ utils::hook::set(0x366F00_b, 0xC301B0);
}
if (game::environment::is_sp())
@@ -536,6 +1228,12 @@ namespace fastfiles
// Don't sys_error if aipaths are missing
utils::hook::call(0x2F8EE9_b, db_find_aipaths_stub);
}
+ else
+ {
+ // Allow loading sp maps on mp
+ utils::hook::jump(0x15AFC0_b, get_bsp_filename_stub);
+ utils::hook::call(0x112ED8_b, com_sprintf_stub);
+ }
// Allow loading of mixed compressor types
utils::hook::nop(SELECT_VALUE(0x1C4BE7_b, 0x3687A7_b), 2);
@@ -595,6 +1293,53 @@ namespace fastfiles
console::warn("loadzone: zone \"%s\" could not be found!\n", name);
}
});
+
+ command::add("poolUsages", []()
+ {
+ for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++)
+ {
+ auto count = 0;
+ enum_assets(static_cast(i), [&](game::XAssetHeader header)
+ {
+ count++;
+ }, true);
+
+ console::info("%i %s: %i / %i\n", i, game::g_assetNames[i], count, game::g_poolSize[i]);
+ }
+ });
+
+ command::add("poolUsage", [](const command::params& params)
+ {
+ if (params.size() < 2)
+ {
+ console::info("Usage: poolUsage \n");
+ return;
+ }
+
+ const auto type = static_cast(std::atoi(params.get(1)));
+
+ auto count = 0;
+ enum_assets(type, [&](game::XAssetHeader header)
+ {
+ count++;
+ }, true);
+
+ console::info("%i %s: %i / %i\n", type, game::g_assetNames[type], count, game::g_poolSize[type]);
+ });
+
+ command::add("assetCount", [](const command::params& params)
+ {
+ auto count = 0;
+ for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++)
+ {
+ enum_assets(static_cast(i), [&](game::XAssetHeader header)
+ {
+ count++;
+ }, true);
+ }
+
+ console::info("assets: %i / %i\n", count, 155000);
+ });
}
};
}
diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp
index e007b036..8f231216 100644
--- a/src/client/component/filesystem.cpp
+++ b/src/client/component/filesystem.cpp
@@ -5,6 +5,7 @@
#include "console.hpp"
#include "filesystem.hpp"
#include "localized_strings.hpp"
+#include "mods.hpp"
#include "updater.hpp"
#include "game/game.hpp"
@@ -39,11 +40,10 @@ namespace filesystem
void fs_startup_stub(const char* name)
{
- console::debug("[FS] Startup\n");
+ console::info("[FS] Startup\n");
initialized = true;
- // hardcoded paths
filesystem::register_path(utils::properties::get_appdata_path() / CLIENT_DATA_FOLDER);
filesystem::register_path(L".");
filesystem::register_path(L"h1-mod");
@@ -53,9 +53,13 @@ namespace filesystem
filesystem::register_path(L"raw");
filesystem::register_path(L"main");
- fs_startup_hook.invoke(name);
+ const auto mod_path = utils::flags::get_flag("mod");
+ if (mod_path.has_value())
+ {
+ mods::set_mod(mod_path.value());
+ }
- command::register_fs_game_path();
+ fs_startup_hook.invoke(name);
}
std::vector get_paths(const std::filesystem::path& path)
@@ -175,7 +179,7 @@ namespace filesystem
{
if (can_insert_path(path_))
{
- console::debug("[FS] Registering path '%s'\n", path_.generic_string().data());
+ console::info("[FS] Registering path '%s'\n", path_.generic_string().data());
get_search_paths_internal().push_front(path_);
}
}
diff --git a/src/client/component/game_console.cpp b/src/client/component/game_console.cpp
index 91684ee2..49649f60 100644
--- a/src/client/component/game_console.cpp
+++ b/src/client/component/game_console.cpp
@@ -181,12 +181,12 @@ namespace game_console
{
input = utils::string::to_lower(input);
- for (const auto& dvar : dvars::dvar_list)
+ for (const auto& [hash, dvar] : dvars::dvar_map)
{
auto name = utils::string::to_lower(dvar.name);
if (game::Dvar_FindVar(name.data()) && utils::string::match_compare(input, name, exact))
{
- suggestions.push_back(dvar);
+ suggestions.emplace_back(dvar);
}
if (exact && suggestions.size() > 1)
@@ -197,7 +197,7 @@ namespace game_console
if (suggestions.size() == 0 && game::Dvar_FindVar(input.data()))
{
- suggestions.push_back({input, ""});
+ suggestions.emplace_back(input, "");
}
game::cmd_function_s* cmd = (*game::cmd_functions);
@@ -209,7 +209,7 @@ namespace game_console
if (utils::string::match_compare(input, name, exact))
{
- suggestions.push_back({cmd->name, ""});
+ suggestions.emplace_back(cmd->name, "");
}
if (exact && suggestions.size() > 1)
@@ -269,17 +269,13 @@ namespace game_console
if (matches.size() > 24)
{
draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector);
- draw_hint_text(0, utils::string::va("%i matches (too many to show here). Press SHIFT + TAB to show more", matches.size()),
- dvars::con_inputDvarMatchColor->current.vector);
-
- if (game::playerKeys[0].keys[game::keyNum_t::K_SHIFT].down && game::playerKeys[0].keys[game::keyNum_t::K_TAB].down)
- {
- console::info("]%s\n", con.buffer);
- for (size_t i = 0; i < matches.size(); i++)
- {
- console::info("\t%s\n", matches[i].name.data());
- }
- }
+ draw_hint_text(0,
+ utils::string::va(
+ "%i matches (too many to show here, "
+ "press shift+tilde to open full console, "
+ "press tab to print them all)",
+ matches.size()
+ ), dvars::con_inputDvarMatchColor->current.vector);
}
else if (matches.size() == 1)
{
@@ -717,6 +713,15 @@ namespace game_console
clear();
}
+
+ if (key == game::keyNum_t::K_TAB && std::strlen(con.buffer) >= 2 && matches.size() > 24)
+ {
+ console::info("]%s\n", con.buffer);
+ for (const auto& match : matches)
+ {
+ console::info("\t%s\n", match.name.data());
+ }
+ }
}
}
diff --git a/src/client/component/gsc/script_error.cpp b/src/client/component/gsc/script_error.cpp
index 20af68f2..6a1a90d6 100644
--- a/src/client/component/gsc/script_error.cpp
+++ b/src/client/component/gsc/script_error.cpp
@@ -2,11 +2,15 @@
#include "loader/component_loader.hpp"
#include "game/game.hpp"
+#include "script_extension.hpp"
#include "script_error.hpp"
#include "component/scripting.hpp"
#include
+#include
+
+using namespace utils::string;
namespace gsc
{
@@ -18,6 +22,37 @@ namespace gsc
std::string unknown_function_error;
+ std::array var_typename =
+ {
+ "undefined",
+ "object",
+ "string",
+ "localized string",
+ "vector",
+ "float",
+ "int",
+ "codepos",
+ "precodepos",
+ "function",
+ "builtin function",
+ "builtin method",
+ "stack",
+ "animation",
+ "pre animation",
+ "thread",
+ "thread",
+ "thread",
+ "thread",
+ "struct",
+ "removed entity",
+ "entity",
+ "array",
+ "removed thread",
+ "",
+ "thread list",
+ "endon list",
+ };
+
void scr_emit_function_stub(std::uint32_t filename, std::uint32_t thread_name, char* code_pos)
{
current_filename = filename;
@@ -43,13 +78,14 @@ namespace gsc
{
const auto& pos = function.value();
unknown_function_error = std::format(
- "while processing function '{}' in script '{}':\nunknown script '{}'",
- pos.first, pos.second, scripting::current_file
+ "while processing function '{}' in script '{}':\nunknown script '{}' ({})",
+ pos.first, pos.second, scripting::current_file, scripting::current_file_id
);
}
else
{
- unknown_function_error = std::format("unknown script '{}'", scripting::current_file);
+ unknown_function_error = std::format("unknown script '{}' ({})",
+ scripting::current_file, scripting::current_file_id);
}
}
@@ -59,29 +95,212 @@ namespace gsc
const auto name = scripting::get_token(thread_name);
unknown_function_error = std::format(
- "while processing script '{}':\nunknown function '{}::{}'",
+ "while processing script '{}':\nunknown function '{}::{}'",
scripting::current_file, filename, name
);
}
- void unknown_function_stub(const char* code_pos)
+ void compile_error_stub(const char* code_pos, [[maybe_unused]] const char* msg)
{
get_unknown_function_error(code_pos);
- game::Com_Error(game::ERR_DROP, "script link error\n%s",
- unknown_function_error.data());
+ game::Com_Error(game::ERR_SCRIPT_DROP, "script link error\n%s", unknown_function_error.data());
}
-
+
std::uint32_t find_variable_stub(std::uint32_t parent_id, std::uint32_t thread_name)
{
const auto res = game::FindVariable(parent_id, thread_name);
if (!res)
{
get_unknown_function_error(thread_name);
- game::Com_Error(game::ERR_DROP, "script link error\n%s",
- unknown_function_error.data());
+ game::Com_Error(game::ERR_SCRIPT_DROP, "script link error\n%s", unknown_function_error.data());
}
return res;
}
+
+ unsigned int scr_get_object(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ auto* value = game::scr_VmPub->top - index;
+ if (value->type == game::VAR_POINTER)
+ {
+ return value->u.pointerValue;
+ }
+
+ scr_error(va("Type %s is not an object", var_typename[value->type]));
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0;
+ }
+
+ unsigned int scr_get_const_string(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ auto* value = game::scr_VmPub->top - index;
+ if (game::Scr_CastString(value))
+ {
+ assert(value->type == game::VAR_STRING);
+ return value->u.stringValue;
+ }
+
+ game::Scr_ErrorInternal();
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0;
+ }
+
+ unsigned int scr_get_const_istring(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ auto* value = game::scr_VmPub->top - index;
+ if (value->type == game::VAR_ISTRING)
+ {
+ return value->u.stringValue;
+ }
+
+ scr_error(va("Type %s is not a localized string", var_typename[value->type]));
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0;
+ }
+
+ void scr_validate_localized_string_ref(int parm_index, const char* token, int token_len)
+ {
+ assert(token);
+ assert(token_len >= 0);
+
+ if (token_len < 2)
+ {
+ return;
+ }
+
+ for (auto char_iter = 0; char_iter < token_len; ++char_iter)
+ {
+ if (!std::isalnum(static_cast(token[char_iter])) && token[char_iter] != '_')
+ {
+ scr_error(va("Illegal localized string reference: %s must contain only alpha-numeric characters and underscores", token));
+ }
+ }
+ }
+
+ void scr_get_vector(unsigned int index, float* vector_value)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ auto* value = game::scr_VmPub->top - index;
+ if (value->type == game::VAR_VECTOR)
+ {
+ std::memcpy(vector_value, value->u.vectorValue, sizeof(std::float_t[3]));
+ return;
+ }
+
+ scr_error(va("Type %s is not a vector", var_typename[value->type]));
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ }
+
+ int scr_get_int(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ auto* value = game::scr_VmPub->top - index;
+ if (value->type == game::VAR_INTEGER)
+ {
+ return value->u.intValue;
+ }
+
+ scr_error(va("Type %s is not an int", var_typename[value->type]));
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0;
+ }
+
+ float scr_get_float(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ auto* value = game::scr_VmPub->top - index;
+ if (value->type == game::VAR_FLOAT)
+ {
+ return value->u.floatValue;
+ }
+
+ if (value->type == game::VAR_INTEGER)
+ {
+ return static_cast(value->u.intValue);
+ }
+
+ scr_error(va("Type %s is not a float", var_typename[value->type]));
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0.0f;
+ }
+
+ int scr_get_pointer_type(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ if ((game::scr_VmPub->top - index)->type == game::VAR_POINTER)
+ {
+ return static_cast(game::GetObjectType((game::scr_VmPub->top - index)->u.uintValue));
+ }
+
+ scr_error(va("Type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]));
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0;
+ }
+
+ int scr_get_type(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ return (game::scr_VmPub->top - index)->type;
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return 0;
+ }
+
+ const char* scr_get_type_name(unsigned int index)
+ {
+ if (index < game::scr_VmPub->outparamcount)
+ {
+ return var_typename[(game::scr_VmPub->top - index)->type];
+ }
+
+ scr_error(va("Parameter %u does not exist", index + 1));
+ return nullptr;
+ }
+
+ template
+ void safe_func()
+ {
+ static utils::hook::detour hook;
+ static const auto stub = []()
+ {
+ __try
+ {
+ hook.invoke();
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ game::Scr_ErrorInternal();
+ }
+ };
+
+ const auto ptr = rva + 0_b;
+ hook.create(reinterpret_cast(ptr), stub);
+ }
}
std::optional> find_function(const char* pos)
@@ -108,9 +327,27 @@ namespace gsc
{
scr_emit_function_hook.create(SELECT_VALUE(0x3BD680_b, 0x504660_b), &scr_emit_function_stub);
- 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
+ utils::hook::call(SELECT_VALUE(0x3BD626_b, 0x504606_b), compile_error_stub); // CompileError (LinkFile)
+ utils::hook::call(SELECT_VALUE(0x3BD672_b, 0x504652_b), compile_error_stub); // ^
+ utils::hook::call(SELECT_VALUE(0x3BD75A_b, 0x50473A_b), find_variable_stub); // Scr_EmitFunction
+
+ // Restore basic error messages for commonly used scr functions
+ utils::hook::jump(SELECT_VALUE(0x3C89F0_b, 0x50F9E0_b), scr_get_object);
+ utils::hook::jump(SELECT_VALUE(0x3C84C0_b, 0x50F560_b), scr_get_const_string);
+ utils::hook::jump(SELECT_VALUE(0x3C8280_b, 0x50F320_b), scr_get_const_istring);
+ utils::hook::jump(SELECT_VALUE(0x2D6950_b, 0x452EF0_b), scr_validate_localized_string_ref);
+ utils::hook::jump(SELECT_VALUE(0x3C8F30_b, 0x50FF20_b), scr_get_vector);
+ utils::hook::jump(SELECT_VALUE(0x3C8930_b, 0x50F920_b), scr_get_int);
+ utils::hook::jump(SELECT_VALUE(0x3C87D0_b, 0x50F870_b), scr_get_float);
+
+ utils::hook::jump(SELECT_VALUE(0x3C8C10_b, 0x50FC00_b), scr_get_pointer_type);
+ utils::hook::jump(SELECT_VALUE(0x3C8DE0_b, 0x50FDD0_b), scr_get_type);
+ utils::hook::jump(SELECT_VALUE(0x3C8E50_b, 0x50FE40_b), scr_get_type_name);
+
+ if (!game::environment::is_sp())
+ {
+ safe_func<0xBA7A0>(); // fix vlobby cac crash
+ }
}
void pre_destroy() override
diff --git a/src/client/component/gsc/script_error.hpp b/src/client/component/gsc/script_error.hpp
index e8742026..1cee00d2 100644
--- a/src/client/component/gsc/script_error.hpp
+++ b/src/client/component/gsc/script_error.hpp
@@ -1,4 +1,3 @@
-
#pragma once
namespace gsc
diff --git a/src/client/component/gsc/script_extension.cpp b/src/client/component/gsc/script_extension.cpp
index 9afcfd0b..5e174beb 100644
--- a/src/client/component/gsc/script_extension.cpp
+++ b/src/client/component/gsc/script_extension.cpp
@@ -3,23 +3,17 @@
#include "game/dvars.hpp"
#include "game/game.hpp"
-#include "game/scripting/execution.hpp"
-#include "game/scripting/function.hpp"
-#include "game/scripting/functions.hpp"
-#include "game/scripting/lua/error.hpp"
-
-#include
+#include "component/logfile.hpp"
#include "component/command.hpp"
#include "component/console.hpp"
#include "component/scripting.hpp"
-#include "component/logfile.hpp"
-#include
-#include
-
-#include "script_extension.hpp"
#include "script_error.hpp"
+#include "script_extension.hpp"
+#include "script_loading.hpp"
+
+#include
namespace gsc
{
@@ -33,8 +27,8 @@ namespace gsc
namespace
{
- std::unordered_map functions;
- std::unordered_map methods;
+ std::unordered_map functions;
+ std::unordered_map methods;
bool force_error_print = false;
std::optional gsc_error_msg;
@@ -70,10 +64,15 @@ namespace gsc
reinterpret_cast(pos - 2));
}
+ game::scr_entref_t get_entity_id_stub(std::uint32_t ent_id)
+ {
+ const auto ref = game::Scr_GetEntityIdRef(ent_id);
+ saved_ent_ref = ref;
+ return ref;
+ }
+
void execute_custom_function(const std::uint16_t id)
{
- auto error = false;
-
try
{
const auto& function = functions[id];
@@ -85,23 +84,33 @@ namespace gsc
return_value(result);
}
}
- catch (const std::exception& e)
+ catch (const std::exception& ex)
{
- error = true;
- force_error_print = true;
- gsc_error_msg = e.what();
+ scr_error(ex.what());
+ }
+ }
+
+ void vm_call_builtin_function_stub(builtin_function func)
+ {
+ const auto function_id = get_function_id();
+ const auto custom = functions.contains(static_cast(function_id));
+ if (custom)
+ {
+ execute_custom_function(function_id);
+ return;
}
- if (error)
+ if (func == nullptr)
{
- game::Scr_ErrorInternal();
+ scr_error(utils::string::va("builtin function \"%s\" doesn't exist", gsc_ctx->func_name(function_id).data()), true);
+ return;
}
+
+ func();
}
void execute_custom_method(const std::uint16_t id)
{
- auto error = false;
-
try
{
const auto& method = methods[id];
@@ -113,52 +122,29 @@ namespace gsc
return_value(result);
}
}
- catch (const std::exception& e)
+ catch (const std::exception& ex)
{
- error = true;
- force_error_print = true;
- gsc_error_msg = e.what();
- }
-
- if (error)
- {
- game::Scr_ErrorInternal();
+ scr_error(ex.what());
}
}
- void vm_call_builtin_function_stub(builtin_function function)
+ void vm_call_builtin_method_stub(builtin_method meth)
{
- const auto function_id = get_function_id();
-
- if (!functions.contains(function_id))
+ const auto method_id = get_function_id();
+ const auto custom = methods.contains(static_cast(method_id));
+ if (custom)
{
- function();
+ execute_custom_method(method_id);
+ return;
}
- else
- {
- execute_custom_function(function_id);
- }
- }
- game::scr_entref_t get_entity_id_stub(std::uint32_t ent_id)
- {
- const auto ref = game::Scr_GetEntityIdRef(ent_id);
- saved_ent_ref = ref;
- return ref;
- }
-
- void vm_call_builtin_method_stub(builtin_method method)
- {
- const auto function_id = get_function_id();
-
- if (!methods.contains(function_id))
+ if (meth == nullptr)
{
- method(saved_ent_ref);
- }
- else
- {
- execute_custom_method(function_id);
+ scr_error(utils::string::va("builtin method \"%s\" doesn't exist", gsc_ctx->meth_name(method_id).data()), true);
+ return;
}
+
+ meth(saved_ent_ref);
}
void builtin_call_error(const std::string& error)
@@ -167,13 +153,11 @@ namespace gsc
if (function_id > 0x1000)
{
- console::warn("in call to builtin method \"%s\"%s",
- xsk::gsc::h1::resolver::method_name(function_id).data(), error.data());
+ console::warn("in call to builtin method \"%s\"%s", gsc_ctx->meth_name(function_id).data(), error.data());
}
else
{
- console::warn("in call to builtin function \"%s\"%s",
- xsk::gsc::h1::resolver::function_name(function_id).data(), error.data());
+ console::warn("in call to builtin function \"%s\"%s", gsc_ctx->func_name(function_id).data(), error.data());
}
}
@@ -181,7 +165,8 @@ namespace gsc
{
try
{
- return {xsk::gsc::h1::resolver::opcode_name(opcode)};
+ const auto index = gsc_ctx->opcode_enum(opcode);
+ return { gsc_ctx->opcode_name(index) };
}
catch (...)
{
@@ -209,7 +194,8 @@ namespace gsc
void vm_error_stub(int mark_pos)
{
- if (!developer_script->current.enabled && !force_error_print)
+ const bool dev_script = developer_script ? developer_script->current.enabled : false;
+ if (!dev_script && !force_error_print)
{
utils::hook::invoke(SELECT_VALUE(0x415C90_b, 0x59DDA0_b), mark_pos);
return;
@@ -266,19 +252,27 @@ namespace gsc
}
}
+ void scr_error(const char* error, const bool force_print)
+ {
+ force_error_print = force_print;
+ gsc_error_msg = error;
+
+ game::Scr_ErrorInternal();
+ }
+
namespace function
{
void add(const std::string& name, script_function function)
{
- if (xsk::gsc::h1::resolver::find_function(name))
+ if (gsc_ctx->func_exists(name))
{
- const auto id = xsk::gsc::h1::resolver::function_id(name);
+ const auto id = gsc_ctx->func_id(name);
functions[id] = function;
}
else
{
const auto id = ++function_id_start;
- xsk::gsc::h1::resolver::add_function(name, static_cast(id));
+ gsc_ctx->func_add(name, static_cast(id));
functions[id] = function;
}
}
@@ -288,15 +282,15 @@ namespace gsc
{
void add(const std::string& name, script_method method)
{
- if (xsk::gsc::h1::resolver::find_method(name))
+ if (gsc_ctx->meth_exists(name))
{
- const auto id = xsk::gsc::h1::resolver::method_id(name);
+ const auto id = gsc_ctx->meth_id(name);
methods[id] = method;
}
else
{
const auto id = ++method_id_start;
- xsk::gsc::h1::resolver::add_method(name, static_cast(id));
+ gsc_ctx->meth_add(name, static_cast(id));
methods[id] = method;
}
}
@@ -332,6 +326,8 @@ namespace gsc
public:
void post_unpack() override
{
+ developer_script = dvars::register_bool("developer_script", false, 0, "Enable developer script comments");
+
utils::hook::set(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count
utils::hook::set(SELECT_VALUE(0x3BD872_b, 0x504852_b) + 4,
@@ -348,8 +344,6 @@ namespace gsc
utils::hook::inject(SELECT_VALUE(0x3BDC36_b, 0x504C66_b) + 3, &meth_table);
utils::hook::set(SELECT_VALUE(0x3BDC3F_b, 0x504C6F_b), sizeof(meth_table));
- developer_script = dvars::register_bool("developer_script", false, 0, "Enable developer script comments");
-
utils::hook::nop(SELECT_VALUE(0x3CB723_b, 0x512783_b), 8);
utils::hook::call(SELECT_VALUE(0x3CB723_b, 0x512783_b), vm_call_builtin_function_stub);
@@ -358,7 +352,7 @@ namespace gsc
utils::hook::nop(SELECT_VALUE(0x3CBA4E_b, 0x512AAE_b), 2);
utils::hook::call(SELECT_VALUE(0x3CBA46_b, 0x512AA6_b), vm_call_builtin_method_stub);
- utils::hook::call(SELECT_VALUE(0x3CC9F3_b, 0x513A53_b), vm_error_stub);
+ utils::hook::call(SELECT_VALUE(0x3CC9F3_b, 0x513A53_b), vm_error_stub); // LargeLocalResetToMark
if (game::environment::is_dedi())
{
@@ -424,7 +418,7 @@ namespace gsc
if (what.type != game::VAR_FUNCTION || with.type != game::VAR_FUNCTION)
{
- throw std::runtime_error("replaceFunc: parameter 1 must be a function");
+ throw std::runtime_error("replacefunc: parameter 1 must be a function");
}
logfile::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
@@ -455,8 +449,7 @@ namespace gsc
function::add("executecommand", [](const function_args& args)
{
- const auto cmd = args[0].as();
- command::execute(cmd, true);
+ command::execute(args[0].as(), false);
return scripting::script_value{};
});
diff --git a/src/client/component/gsc/script_extension.hpp b/src/client/component/gsc/script_extension.hpp
index 2aae4a2e..ad57e2b5 100644
--- a/src/client/component/gsc/script_extension.hpp
+++ b/src/client/component/gsc/script_extension.hpp
@@ -34,6 +34,8 @@ namespace gsc
extern const game::dvar_t* developer_script;
+ void scr_error(const char* error, const bool force_print = false);
+
namespace function
{
void add(const std::string& name, script_function function);
diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp
index 5d9c2cf5..ab24a55b 100644
--- a/src/client/component/gsc/script_loading.cpp
+++ b/src/client/component/gsc/script_loading.cpp
@@ -6,7 +6,7 @@
#include "component/filesystem.hpp"
#include "component/logfile.hpp"
#include "component/scripting.hpp"
-#include "component/gsc/script_loading.hpp"
+#include "component/memory.hpp"
#include "game/dvars.hpp"
@@ -14,39 +14,34 @@
#include "game/scripting/execution.hpp"
#include "game/scripting/function.hpp"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "script_extension.hpp"
+#include "script_loading.hpp"
+#include
#include
#include
#include
namespace gsc
{
+ std::unique_ptr gsc_ctx = std::make_unique();;
+
namespace
{
- auto compiler = ::gsc::compiler();
- auto decompiler = ::gsc::decompiler();
- auto assembler = ::gsc::assembler();
- auto disassembler = ::gsc::disassembler();
+ utils::hook::detour scr_begin_load_scripts_hook;
+ utils::hook::detour scr_end_load_scripts_hook;
std::unordered_map main_handles;
std::unordered_map init_handles;
utils::memory::allocator scriptfile_allocator;
- std::unordered_map loaded_scripts;
+ std::unordered_map loaded_scripts;
struct
{
char* buf = nullptr;
char* pos = nullptr;
- unsigned int size = 0x1000000;
+ const unsigned int size = memory::custom_script_mem_size;
} script_memory;
char* allocate_buffer(size_t size)
@@ -83,15 +78,14 @@ namespace gsc
free_script_memory();
}
- bool read_script_file(const std::string& name, std::string* data)
+ bool read_raw_script_file(const std::string& name, std::string* data)
{
if (filesystem::read_file(name, data))
{
return true;
}
- const auto name_str = name.data();
-
+ const auto* name_str = name.data();
if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) &&
!game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str))
{
@@ -110,41 +104,75 @@ 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 (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end())
+ if (const auto itr = loaded_scripts.find(file_name); itr != loaded_scripts.end())
{
return itr->second;
}
- std::string source_buffer;
- if (!read_script_file(real_name + ".gsc", &source_buffer) || source_buffer.empty())
+ if (game::VirtualLobby_Loaded() && !force_load)
{
return nullptr;
}
- if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, file_name) &&
+ std::string source_buffer{};
+ if (!read_raw_script_file(real_name + ".gsc", &source_buffer) || source_buffer.empty())
+ {
+ return nullptr;
+ }
+
+ // filter out "GSC rawfiles" that were used for development usage and are not meant for us.
+ // each "GSC rawfile" has a ScriptFile counterpart to be used instead
+ if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, file_name) &&
!game::DB_IsXAssetDefault(game::ASSET_TYPE_SCRIPTFILE, file_name))
{
- // filter out gsc rawfiles that contain developer code (has ScriptFile counterparts for ship, won't compile either)
- if ((real_name.starts_with("maps/createfx") || real_name.starts_with("maps/createart") || real_name.starts_with("maps/mp"))
+ if ((real_name.starts_with("maps/createfx") || real_name.starts_with("maps/createart") || real_name.starts_with("maps/mp"))
&& (real_name.ends_with("_fx") || real_name.ends_with("_fog") || real_name.ends_with("_hdr")))
{
+ console::debug("Refusing to compile rawfile '%s'\n", real_name.data());
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, file_name, false).scriptfile;
}
}
- std::vector data;
- data.assign(source_buffer.begin(), source_buffer.end());
+ console::debug("Loading custom gsc '%s.gsc'", real_name.data());
try
{
- compiler->compile(real_name, data);
+ auto& compiler = gsc_ctx->compiler();
+ auto& assembler = gsc_ctx->assembler();
+
+ std::vector data;
+ data.assign(source_buffer.begin(), source_buffer.end());
+
+ const auto assembly_ptr = compiler.compile(real_name, data);
+ const auto output_script = assembler.assemble(*assembly_ptr);
+
+ const auto bytecode = output_script.first; // formerly named "script"
+ const auto stack = output_script.second;
+
+ const auto script_file_ptr = static_cast(scriptfile_allocator.allocate(sizeof(game::ScriptFile)));
+ script_file_ptr->name = file_name;
+
+ script_file_ptr->len = static_cast(stack.size);
+ script_file_ptr->bytecodeLen = static_cast(bytecode.size);
+
+ const auto stack_size = static_cast(stack.size + 1);
+ const auto byte_code_size = static_cast(bytecode.size + 1);
+
+ script_file_ptr->buffer = static_cast(scriptfile_allocator.allocate(stack_size));
+ std::memcpy(const_cast(script_file_ptr->buffer), stack.data, stack.size);
+
+ script_file_ptr->bytecode = allocate_buffer(byte_code_size);
+ std::memcpy(script_file_ptr->bytecode, bytecode.data, bytecode.size);
+
+ script_file_ptr->compressedLen = 0;
+
+ loaded_scripts[file_name] = script_file_ptr;
+
+ return script_file_ptr;
}
catch (const std::exception& e)
{
@@ -153,46 +181,21 @@ namespace gsc
console::error("**********************************************\n");
return nullptr;
}
+ }
- auto assembly = compiler->output();
-
- try
+ std::string get_raw_script_file_name(const std::string& name)
+ {
+ if (name.ends_with(".gsh"))
{
- assembler->assemble(real_name, assembly);
- }
- catch (const std::exception& e)
- {
- console::error("*********** script compile error *************\n");
- console::error("failed to assemble '%s':\n%s", real_name.data(), e.what());
- console::error("**********************************************\n");
- return nullptr;
+ return name;
}
- const auto script_file_ptr = scriptfile_allocator.allocate();
- script_file_ptr->name = file_name;
-
- const auto stack = assembler->output_stack();
- script_file_ptr->len = static_cast(stack.size());
-
- const auto script = assembler->output_script();
- script_file_ptr->bytecodeLen = static_cast(script.size());
-
- script_file_ptr->buffer = game::Hunk_AllocateTempMemoryHigh(stack.size() + 1);
- std::memcpy(script_file_ptr->buffer, stack.data(), stack.size());
-
- script_file_ptr->bytecode = allocate_buffer(script.size() + 1);
- std::memcpy(script_file_ptr->bytecode, script.data(), script.size());
-
- script_file_ptr->compressedLen = 0;
-
- loaded_scripts[real_name] = script_file_ptr;
-
- return script_file_ptr;
+ return name + ".gsc";
}
std::string get_script_file_name(const std::string& name)
{
- const auto id = xsk::gsc::h1::resolver::token_id(name);
+ const auto id = gsc_ctx->token_id(name);
if (!id)
{
return name;
@@ -201,27 +204,25 @@ namespace gsc
return std::to_string(id);
}
- std::vector decompile_script_file(const std::string& name, const std::string& real_name)
+ std::pair> read_compiled_script_file(const std::string& name, const std::string& real_name)
{
const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile;
- if (!script_file)
+ if (script_file == nullptr)
{
throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name));
}
- console::info("Decompiling scriptfile '%s'\n", real_name.data());
+ console::debug("Decompiling scriptfile '%s'\n", real_name.data());
- std::vector stack{script_file->buffer, script_file->buffer + script_file->len};
- std::vector bytecode{script_file->bytecode, script_file->bytecode + script_file->bytecodeLen};
+ const auto len = script_file->compressedLen;
+ const std::string stack{script_file->buffer, static_cast(len)};
- auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast(stack.size()));
+ const auto decompressed_stack = utils::compression::zlib::decompress(stack);
- disassembler->disassemble(name, bytecode, decompressed_stack);
- auto output = disassembler->output();
+ std::vector stack_data;
+ stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
- decompiler->decompile(name, output);
-
- return decompiler->output();
+ return {{reinterpret_cast(script_file->bytecode), static_cast(script_file->bytecodeLen)}, stack_data};
}
void load_script(const std::string& name)
@@ -231,31 +232,31 @@ namespace gsc
return;
}
- const auto main_handle = game::Scr_GetFunctionHandle(name.data(), xsk::gsc::h1::resolver::token_id("main"));
- const auto init_handle = game::Scr_GetFunctionHandle(name.data(), xsk::gsc::h1::resolver::token_id("init"));
+ const auto main_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("main"));
+ const auto init_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("init"));
if (main_handle)
{
- console::info("Loaded '%s::main'\n", name.data());
+ console::debug("Loaded '%s::main'\n", name.data());
main_handles[name] = main_handle;
}
if (init_handle)
{
- console::info("Loaded '%s::init'\n", name.data());
+ console::debug("Loaded '%s::init'\n", name.data());
init_handles[name] = init_handle;
}
}
- void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& script_dir)
+ void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& subfolder)
{
- std::filesystem::path script_dir_path = root_dir / script_dir;
- if (!utils::io::directory_exists(script_dir_path.generic_string()))
+ std::filesystem::path script_dir = root_dir / subfolder;
+ if (!utils::io::directory_exists(script_dir.generic_string()))
{
return;
}
- const auto scripts = utils::io::list_files(script_dir_path.generic_string());
+ const auto scripts = utils::io::list_files(script_dir.generic_string());
for (const auto& script : scripts)
{
if (!script.ends_with(".gsc"))
@@ -281,25 +282,31 @@ namespace gsc
return game::DB_IsXAssetDefault(type, name);
}
- void gscr_load_gametype_script_stub(void* a1, void* a2)
+ void load_gametype_script_stub(void* a1, void* a2)
{
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/");
}
}
}
@@ -313,28 +320,48 @@ namespace gsc
return;
}
- utils::hook::invoke(SELECT_VALUE(0x1F1E00_b, 0x396080_b), rawfile, buf, size);
+ game::DB_GetRawBuffer(rawfile, buf, size);
}
- void pmem_init_stub()
+ void scr_begin_load_scripts_stub()
{
- utils::hook::invoke(SELECT_VALUE(0x420260_b, 0x5A5590_b));
+ const bool dev_script = developer_script ? developer_script->current.enabled : false;
+ const auto comp_mode = dev_script ?
+ xsk::gsc::build::dev:
+ xsk::gsc::build::prod;
- const auto type_0 = &game::g_scriptmem[0];
- const auto type_1 = &game::g_scriptmem[1];
+ gsc_ctx->init(comp_mode, [](const std::string& include_name)
+ -> std::pair>
+ {
+ const auto real_name = get_raw_script_file_name(include_name);
- const auto size_0 = 0x100000; // default size
- const auto size_1 = 0x100000 + script_memory.size;
+ std::string file_buffer;
+ if (!read_raw_script_file(real_name, &file_buffer) || file_buffer.empty())
+ {
+ const auto name = get_script_file_name(include_name);
+ if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
+ {
+ return read_compiled_script_file(name, real_name);
+ }
- const auto block = reinterpret_cast(VirtualAlloc(NULL, size_0 + size_1, MEM_RESERVE, PAGE_READWRITE));
+ throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name));
+ }
- type_0->buf = block;
- type_0->size = size_0;
+ std::vector script_data;
+ script_data.assign(file_buffer.begin(), file_buffer.end());
- type_1->buf = block + size_0;
- type_1->size = size_1;
+ return {{}, script_data};
+ });
- utils::hook::set(SELECT_VALUE(0x420252_b, 0x5A5582_b), size_0 + size_1);
+ scr_begin_load_scripts_hook.invoke();
+ }
+
+ void scr_end_load_scripts_stub()
+ {
+ // cleanup the compiler
+ gsc_ctx->cleanup();
+
+ scr_end_load_scripts_hook.invoke();
}
}
@@ -342,7 +369,7 @@ namespace gsc
{
for (auto& function_handle : main_handles)
{
- console::info("Executing '%s::main'\n", function_handle.first.data());
+ console::debug("Executing '%s::main'\n", function_handle.first.data());
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
}
}
@@ -351,7 +378,7 @@ namespace gsc
{
for (auto& function_handle : init_handles)
{
- console::info("Executing '%s::init'\n", function_handle.first.data());
+ console::debug("Executing '%s::init'\n", function_handle.first.data());
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
}
}
@@ -362,7 +389,7 @@ namespace gsc
const auto id = static_cast(std::atoi(name));
if (id)
{
- real_name = xsk::gsc::h1::resolver::token_name(id);
+ real_name = gsc_ctx->token_name(id);
}
auto* script = load_custom_script(name, real_name);
@@ -379,53 +406,36 @@ namespace gsc
public:
void post_unpack() override
{
- // allow custom scripts to include other custom scripts
- xsk::gsc::h1::resolver::init([](const auto& include_name)
- {
- const auto real_name = include_name + ".gsc";
+ // Load our scripts with an uncompressed stack
+ utils::hook::call(SELECT_VALUE(0x3C7280_b, 0x50E3C0_b), db_get_raw_buffer_stub);
- std::string file_buffer;
- if (!read_script_file(real_name, &file_buffer) || file_buffer.empty())
- {
- const auto name = get_script_file_name(include_name);
- if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
- {
- return decompile_script_file(name, real_name);
- }
- else
- {
- throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name));
- }
- }
+ scr_begin_load_scripts_hook.create(SELECT_VALUE(0x3BDB90_b, 0x504BC0_b), scr_begin_load_scripts_stub);
+ scr_end_load_scripts_hook.create(SELECT_VALUE(0x3BDCC0_b, 0x504CF0_b), scr_end_load_scripts_stub);
- std::vector result;
- result.assign(file_buffer.begin(), file_buffer.end());
-
- return result;
- });
-
- // hook xasset functions to return our own custom scripts
+ // ProcessScript: hook xasset functions to return our own custom scripts
utils::hook::call(SELECT_VALUE(0x3C7217_b, 0x50E357_b), find_script);
utils::hook::call(SELECT_VALUE(0x3C7227_b, 0x50E367_b), db_is_x_asset_default);
- // GScr_LoadScripts
- utils::hook::call(SELECT_VALUE(0x2BA152_b, 0x18C325_b), gscr_load_gametype_script_stub);
+ // GScr_LoadScripts: initial loading of scripts
+ utils::hook::call(SELECT_VALUE(0x2BA152_b, 0x18C325_b), load_gametype_script_stub);
- // loads scripts with an uncompressed stack
- utils::hook::call(SELECT_VALUE(0x3C7280_b, 0x50E3C0_b), db_get_raw_buffer_stub);
-
- // Increase script memory
- utils::hook::call(SELECT_VALUE(0x38639C_b, 0x15C4D6_b), pmem_init_stub);
+ // main is called from scripting.cpp
+ // init is called from scripting.cpp
scripting::on_shutdown([](bool free_scripts, bool post_shutdown)
{
if (free_scripts && post_shutdown)
{
- xsk::gsc::h1::resolver::cleanup();
clear();
}
});
}
+
+ void pre_destroy() override
+ {
+ scr_begin_load_scripts_hook.clear();
+ scr_end_load_scripts_hook.clear();
+ }
};
}
diff --git a/src/client/component/gsc/script_loading.hpp b/src/client/component/gsc/script_loading.hpp
index e42a7e45..8fcdff2c 100644
--- a/src/client/component/gsc/script_loading.hpp
+++ b/src/client/component/gsc/script_loading.hpp
@@ -1,7 +1,10 @@
#pragma once
+#include
namespace gsc
{
+ extern std::unique_ptr gsc_ctx;
+
void load_main_handles();
void load_init_handles();
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
diff --git a/src/client/component/imagefiles.cpp b/src/client/component/imagefiles.cpp
new file mode 100644
index 00000000..bd1cdf72
--- /dev/null
+++ b/src/client/component/imagefiles.cpp
@@ -0,0 +1,230 @@
+#include
+#include "loader/component_loader.hpp"
+
+#include "images.hpp"
+#include "console.hpp"
+#include "filesystem.hpp"
+#include "fastfiles.hpp"
+#include "scheduler.hpp"
+#include "imagefiles.hpp"
+
+#include "game/game.hpp"
+
+#include
+#include
+#include
+#include
+
+#define CUSTOM_IMAGE_FILE_INDEX 96
+
+namespace imagefiles
+{
+ namespace
+ {
+ utils::memory::allocator image_file_allocator;
+ std::unordered_map image_file_handles;
+
+ std::string get_image_file_name()
+ {
+ return fastfiles::get_current_fastfile();
+ }
+
+ namespace mp
+ {
+ struct image_file_unk
+ {
+ char __pad0[120];
+ };
+
+ std::unordered_map image_file_unk_map;
+
+ void* get_image_file_unk_mp(unsigned int index)
+ {
+ if (index != CUSTOM_IMAGE_FILE_INDEX)
+ {
+ return &reinterpret_cast(
+ SELECT_VALUE(0x4802090_b, 0x6306770_b))[index];
+ }
+
+ const auto name = get_image_file_name();
+ if (image_file_unk_map.find(name) == image_file_unk_map.end())
+ {
+ const auto unk = image_file_allocator.allocate();
+ image_file_unk_map[name] = unk;
+ return unk;
+ }
+
+ return image_file_unk_map[name];
+ }
+ }
+
+ namespace sp
+ {
+ struct image_file_unk
+ {
+ char __pad0[96];
+ };
+
+ std::unordered_map image_file_unk_map;
+
+ void* get_image_file_unk_mp(unsigned int index)
+ {
+ if (index != CUSTOM_IMAGE_FILE_INDEX)
+ {
+ return &reinterpret_cast(
+ SELECT_VALUE(0x4802090_b, 0x6306770_b))[index];
+ }
+
+ const auto name = get_image_file_name();
+ if (image_file_unk_map.find(name) == image_file_unk_map.end())
+ {
+ const auto unk = image_file_allocator.allocate();
+ image_file_unk_map[name] = unk;
+ return unk;
+ }
+
+ return image_file_unk_map[name];
+ }
+ }
+
+ game::DB_IFileSysFile* get_image_file_handle(unsigned int index)
+ {
+ if (index != CUSTOM_IMAGE_FILE_INDEX)
+ {
+ return reinterpret_cast(
+ SELECT_VALUE(0x4801D80_b, 0x6306180_b))[index];
+ }
+
+ const auto name = get_image_file_name();
+ return image_file_handles[name];
+ }
+
+ void db_create_gfx_image_stream_stub(utils::hook::assembler& a)
+ {
+ const auto check_image_file_handle = a.newLabel();
+ const auto handle_is_open = a.newLabel();
+
+ a.movzx(eax, cx);
+ a.push(rax);
+ a.push(rax);
+ a.pushad64();
+ a.mov(rcx, rax);
+ a.call_aligned(SELECT_VALUE(sp::get_image_file_unk_mp, mp::get_image_file_unk_mp));
+ a.mov(qword_ptr(rsp, 0x80), rax);
+ a.popad64();
+ a.pop(rax);
+ a.mov(rsi, rax);
+ a.pop(rax);
+
+ a.push(rax);
+ a.push(rax);
+ a.pushad64();
+ a.mov(rcx, rax);
+ a.call_aligned(get_image_file_handle);
+ a.mov(qword_ptr(rsp, 0x80), rax);
+ a.popad64();
+ a.pop(rax);
+ a.mov(r12, rax);
+ a.pop(rax);
+
+ a.cmp(r12, r13);
+ a.jnz(handle_is_open);
+ a.jmp(SELECT_VALUE(0x1FAD49_b, 0x3A0CA5_b));
+
+ a.bind(handle_is_open);
+ a.jmp(SELECT_VALUE(0x1FAD99_b, 0x3A0CF5_b));
+ }
+
+ void* pakfile_open_stub(void* /*handles*/, unsigned int count, int is_imagefile,
+ unsigned int index, int is_localized)
+ {
+ if (index != CUSTOM_IMAGE_FILE_INDEX)
+ {
+ return utils::hook::invoke(
+ SELECT_VALUE(0x42BC00_b, 0x5B2030_b),
+ SELECT_VALUE(0x4801D80_b, 0x6306180_b),
+ count, is_imagefile, index, is_localized
+ );
+ }
+
+ const auto name = get_image_file_name();
+ const auto db_fs = *game::db_fs;
+ const auto handle = db_fs->vftbl->OpenFile(db_fs,
+ game::SF_PAKFILE, utils::string::va("%s.pak", name.data()));
+ if (handle != nullptr)
+ {
+ image_file_handles[name] = handle;
+ }
+ return handle;
+ }
+
+ int com_sprintf_stub(char* buffer, const int len, const char* fmt, unsigned int index)
+ {
+ if (index != CUSTOM_IMAGE_FILE_INDEX)
+ {
+ return game::Com_sprintf(buffer, len, fmt, index);
+ }
+
+ const auto name = get_image_file_name();
+ return game::Com_sprintf(buffer, len, "%s.pak", name.data());
+ }
+ }
+
+ void close_custom_handles()
+ {
+ const auto db_fs = *game::db_fs;
+ for (const auto& handle : image_file_handles)
+ {
+ if (handle.second != nullptr)
+ {
+ db_fs->vftbl->Close(db_fs, handle.second);
+ }
+ }
+
+ image_file_handles.clear();
+ sp::image_file_unk_map.clear();
+ mp::image_file_unk_map.clear();
+ image_file_allocator.clear();
+ }
+
+ void close_handle(const std::string& fastfile)
+ {
+ if (!image_file_handles.contains(fastfile))
+ {
+ return;
+ }
+
+ const auto db_fs = *game::db_fs;
+ const auto handle = image_file_handles[fastfile];
+ if (handle != nullptr)
+ {
+ db_fs->vftbl->Close(db_fs, handle);
+ }
+
+ image_file_handles.erase(fastfile);
+ if (game::environment::is_sp())
+ {
+ image_file_allocator.free(sp::image_file_unk_map[fastfile]);
+ sp::image_file_unk_map.erase(fastfile);
+ }
+ else
+ {
+ image_file_allocator.free(mp::image_file_unk_map[fastfile]);
+ mp::image_file_unk_map.erase(fastfile);
+ }
+ }
+
+ class component final : public component_interface
+ {
+ public:
+ void post_unpack() override
+ {
+ utils::hook::jump(SELECT_VALUE(0x1FAD35_b, 0x3A0C95_b),
+ utils::hook::assemble(db_create_gfx_image_stream_stub), true);
+ utils::hook::call(SELECT_VALUE(0x1FAD7B_b, 0x3A0CD7_b), pakfile_open_stub);
+ utils::hook::call(SELECT_VALUE(0x1FAD5D_b, 0x3A0CB9_b), com_sprintf_stub);
+ }
+ };
+}
+
+REGISTER_COMPONENT(imagefiles::component)
diff --git a/src/client/component/imagefiles.hpp b/src/client/component/imagefiles.hpp
new file mode 100644
index 00000000..ff309625
--- /dev/null
+++ b/src/client/component/imagefiles.hpp
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace imagefiles
+{
+ void close_custom_handles();
+ void close_handle(const std::string& fastfile);
+}
diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp
index ad14ae43..36fbb49e 100644
--- a/src/client/component/input.cpp
+++ b/src/client/component/input.cpp
@@ -17,15 +17,6 @@ namespace input
void cl_char_event_stub(const int local_client_num, const int key)
{
- if (game::environment::is_sp() && ui_scripting::lui_running())
- {
- ui_scripting::notify("keypress",
- {
- {"keynum", key},
- {"key", game::Key_KeynumToString(key, 0, 1)},
- });
- }
-
if (!game_console::console_char_event(local_client_num, key))
{
return;
@@ -36,15 +27,6 @@ namespace input
void cl_key_event_stub(const int local_client_num, const int key, const int down)
{
- if (game::environment::is_sp() && ui_scripting::lui_running())
- {
- ui_scripting::notify(down ? "keydown" : "keyup",
- {
- {"keynum", key},
- {"key", game::Key_KeynumToString(key, 0, 1)},
- });
- }
-
if (!game_console::console_key_event(local_client_num, key, down))
{
return;
diff --git a/src/client/component/logger.cpp b/src/client/component/logger.cpp
index 80c59025..4db069f5 100644
--- a/src/client/component/logger.cpp
+++ b/src/client/component/logger.cpp
@@ -96,6 +96,12 @@ namespace logger
console::info("%s", buffer);
}
+
+ void r_warn_once_per_frame_vsnprintf_stub(char* buffer, size_t buffer_length, char* msg, va_list va)
+ {
+ vsnprintf(buffer, buffer_length, msg, va);
+ console::warn(buffer);
+ }
}
class component final : public component_interface
@@ -108,7 +114,15 @@ namespace logger
// lua stuff
utils::hook::jump(SELECT_VALUE(0x106010_b, 0x27CBB0_b), print_dev); // debug
utils::hook::jump(SELECT_VALUE(0x107680_b, 0x27E210_b), print_error); // error
- utils::hook::jump(SELECT_VALUE(0x0E6E30_b, 0x1F6140_b), printf); // print
+ utils::hook::jump(SELECT_VALUE(0x0E6E30_b, 0x1F6140_b), print); // print
+
+ if (game::environment::is_mp())
+ {
+ utils::hook::call(0x6BBB81_b, r_warn_once_per_frame_vsnprintf_stub);
+
+ utils::hook::jump(0x498BD0_b, print_warning); // dmWarn
+ utils::hook::jump(0x498AD0_b, print); // dmLog
+ }
}
com_error_hook.create(game::Com_Error, com_error_stub);
diff --git a/src/client/component/map_patches.cpp b/src/client/component/map_patches.cpp
index 7c59b80c..cac9de29 100644
--- a/src/client/component/map_patches.cpp
+++ b/src/client/component/map_patches.cpp
@@ -7,6 +7,49 @@
namespace map_patches
{
+ struct GfxLightGridTree
+ {
+ unsigned char index;
+ unsigned char maxDepth;
+ char unused[2];
+ int nodeCount;
+ int leafCount;
+ int coordMinGridSpace[3];
+ int coordMaxGridSpace[3];
+ int coordHalfSizeGridSpace[3];
+ int defaultColorIndexBitCount;
+ int defaultLightIndexBitCount;
+ unsigned int* p_nodeTable;
+ int leafTableSize;
+ unsigned char* p_leafTable;
+ };
+
+ enum leaf_table_version : std::int8_t
+ {
+ h2 = 0i8,
+ h1 = 0i8,
+ s1 = 0i8,
+ iw6 = 1i8,
+ };
+
+ utils::hook::detour r_decode_light_grid_block_hook;
+ void r_decode_light_grid_block_stub(GfxLightGridTree* p_tree, int child_mask,
+ char child_index, int encoded_node_address, char* p_node_raw, char* p_leaf_raw)
+ {
+ static const auto p_address = 0x6A032E_b + 1;
+ if (p_tree->unused[0] == leaf_table_version::iw6)
+ {
+ utils::hook::set(p_address, 6); // iw6
+ }
+ else
+ {
+ utils::hook::set(p_address, 7); // s1,h1,h2
+ }
+
+ r_decode_light_grid_block_hook.invoke(p_tree, child_mask,
+ child_index, encoded_node_address, p_node_raw, p_leaf_raw);
+ }
+
class component final : public component_interface
{
public:
@@ -20,6 +63,9 @@ namespace map_patches
// skip fx name prefix checks
utils::hook::set(0x2F377D_b, 0xEB); // createfx parse
utils::hook::set(0x4444E0_b, 0xEB); // scr_loadfx
+
+ // patch iw6 leafTable decoding
+ r_decode_light_grid_block_hook.create(0x69E7D0_b, r_decode_light_grid_block_stub);
}
};
}
diff --git a/src/client/component/map_rotation.cpp b/src/client/component/map_rotation.cpp
index 7b80bebf..1a39c0cb 100644
--- a/src/client/component/map_rotation.cpp
+++ b/src/client/component/map_rotation.cpp
@@ -3,6 +3,7 @@
#include "command.hpp"
#include "console.hpp"
+#include "map_rotation.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
@@ -15,20 +16,23 @@ namespace map_rotation
{
namespace
{
- DWORD previous_priority{};
+ rotation_data dedicated_rotation;
- void set_dvar(const std::string& dvar, const std::string& value)
- {
- command::execute(utils::string::va("%s \"%s\"", dvar.data(), value.data()), true);
- }
+ const game::dvar_t* sv_map_rotation;
+ const game::dvar_t* sv_map_rotation_current;
+ const game::dvar_t* sv_random_map_rotation;
void set_gametype(const std::string& gametype)
{
- set_dvar("g_gametype", gametype);
+ assert(!gametype.empty());
+
+ game::Dvar_SetFromStringByNameFromSource("g_gametype", gametype.data(), game::DVAR_SOURCE_INTERNAL);
}
void launch_map(const std::string& mapname)
{
+ assert(!mapname.empty());
+
command::execute(utils::string::va("map %s", mapname.data()), false);
}
@@ -46,53 +50,103 @@ namespace map_rotation
}
}
- std::string load_current_map_rotation()
+ void apply_rotation(rotation_data& rotation)
{
- auto* rotation = game::Dvar_FindVar("sv_mapRotationCurrent");
- if (!strlen(rotation->current.string))
+ assert(!rotation.empty());
+
+ std::size_t i = 0;
+ while (i < rotation.get_entries_size())
{
- rotation = game::Dvar_FindVar("sv_mapRotation");
- set_dvar("sv_mapRotationCurrent", rotation->current.string);
- }
-
- return rotation->current.string;
- }
-
- std::vector parse_current_map_rotation()
- {
- const auto rotation = load_current_map_rotation();
- return utils::string::split(rotation, ' ');
- }
-
- void store_new_rotation(const std::vector& elements, const size_t index)
- {
- std::string value{};
-
- for (auto i = index; i < elements.size(); ++i)
- {
- if (i != index)
+ const auto& entry = rotation.get_next_entry();
+ if (entry.first == "map"s)
{
- value.push_back(' ');
+ console::info("Loading new map: '%s'\n", entry.second.data());
+ if (!game::SV_MapExists(entry.second.data()))
+ {
+ console::info("map_rotation: '%s' map doesn't exist!\n", entry.second.data());
+ launch_default_map();
+ return;
+ }
+
+ launch_map(entry.second);
+
+ break;
}
- value.append(elements[i]);
- }
+ if (entry.first == "gametype"s)
+ {
+ console::info("Applying new gametype: '%s'\n", entry.second.data());
+ set_gametype(entry.second);
+ }
- set_dvar("sv_mapRotationCurrent", value);
+ ++i;
+ }
}
- void change_process_priority()
+ void load_rotation(const std::string& data)
{
- auto* const dvar = game::Dvar_FindVar("sv_autoPriority");
- if (dvar && dvar->current.enabled)
+ static auto loaded = false;
+ if (loaded)
{
- scheduler::on_game_initialized([]
- {
- SetPriorityClass(GetCurrentProcess(), previous_priority);
- }, scheduler::pipeline::main, 1s);
+ return;
+ }
- previous_priority = GetPriorityClass(GetCurrentProcess());
- SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
+ loaded = true;
+ try
+ {
+ dedicated_rotation.parse(data);
+ }
+ catch (const std::exception& ex)
+ {
+ console::error("%s: sv_map_rotation contains invalid data!\n", ex.what());
+ }
+
+ console::debug("dedicated_rotation size after parsing is '%llu'", dedicated_rotation.get_entries_size());
+ }
+
+ void load_map_rotation()
+ {
+ const std::string map_rotation = sv_map_rotation->current.string;
+ if (!map_rotation.empty())
+ {
+ console::debug("sv_map_rotation is not empty. Parsing...\n");
+ load_rotation(map_rotation);
+ }
+ }
+
+ void apply_map_rotation_current(const std::string& data)
+ {
+ assert(!data.empty());
+
+ rotation_data rotation_current;
+
+ try
+ {
+ rotation_current.parse(data);
+ }
+ catch (const std::exception& ex)
+ {
+ console::error("%s: sv_map_rotation_current contains invalid data!\n", ex.what());
+ }
+
+ game::Dvar_SetFromStringByNameFromSource("sv_map_rotation_current", "", game::DVAR_SOURCE_INTERNAL);
+
+ if (rotation_current.empty())
+ {
+ console::warn("sv_map_rotation_current is empty or contains invalid data\n");
+ launch_default_map();
+ return;
+ }
+
+ apply_rotation(rotation_current);
+ }
+
+ void randomize_map_rotation()
+ {
+ if (sv_random_map_rotation->current.enabled)
+ {
+ console::info("Randomizing map rotation\n");
+ dedicated_rotation.randomize();
}
}
@@ -104,37 +158,28 @@ namespace map_rotation
return;
}
- const auto rotation = parse_current_map_rotation();
+ console::info("Rotating map...\n");
- for (size_t i = 0; !rotation.empty() && i < (rotation.size() - 1); i += 2)
+ // This takes priority because of backwards compatibility
+ const std::string map_rotation_current = sv_map_rotation_current->current.string;
+ if (!map_rotation_current.empty())
{
- const auto& key = rotation[i];
- const auto& value = rotation[i + 1];
-
- if (key == "gametype")
- {
- set_gametype(value);
- }
- else if (key == "map")
- {
- store_new_rotation(rotation, i + 2);
- change_process_priority();
- if (!game::SV_MapExists(value.data()))
- {
- console::info("map_rotation: '%s' map doesn't exist!\n", value.data());
- launch_default_map();
- return;
- }
- launch_map(value);
- return;
- }
- else
- {
- console::info("Invalid map rotation key: %s\n", key.data());
- }
+ console::debug("Applying sv_map_rotation_current\n");
+ apply_map_rotation_current(map_rotation_current);
+ return;
}
- launch_default_map();
+ load_map_rotation();
+ if (dedicated_rotation.empty())
+ {
+ console::warn("sv_map_rotation is empty or contains invalid data. Restarting map\n");
+ launch_default_map();
+ return;
+ }
+
+ randomize_map_rotation();
+
+ apply_rotation(dedicated_rotation);
}
void trigger_map_rotation()
@@ -152,6 +197,68 @@ namespace map_rotation
}
}
+ rotation_data::rotation_data()
+ : index_(0)
+ {
+ }
+
+ void rotation_data::randomize()
+ {
+ std::random_device rd;
+ std::mt19937 gen(rd());
+
+ std::ranges::shuffle(this->rotation_entries_, gen);
+ }
+
+ void rotation_data::add_entry(const std::string& key, const std::string& value)
+ {
+ this->rotation_entries_.emplace_back(std::make_pair(key, value));
+ }
+
+ bool rotation_data::contains(const std::string& key, const std::string& value) const
+ {
+ return std::ranges::any_of(this->rotation_entries_, [&](const auto& entry)
+ {
+ return entry.first == key && entry.second == value;
+ });
+ }
+
+ bool rotation_data::empty() const noexcept
+ {
+ return this->rotation_entries_.empty();
+ }
+
+ std::size_t rotation_data::get_entries_size() const noexcept
+ {
+ return this->rotation_entries_.size();
+ }
+
+ rotation_data::rotation_entry& rotation_data::get_next_entry()
+ {
+ const auto index = this->index_;
+ ++this->index_ %= this->rotation_entries_.size();
+ return this->rotation_entries_.at(index);
+ }
+
+ void rotation_data::parse(const std::string& data)
+ {
+ const auto tokens = utils::string::split(data, ' ');
+ for (std::size_t i = 0; !tokens.empty() && i < (tokens.size() - 1); i += 2)
+ {
+ const auto& key = tokens[i];
+ const auto& value = tokens[i + 1];
+
+ if (key == "map"s || key == "gametype"s)
+ {
+ this->add_entry(key, value);
+ }
+ else
+ {
+ throw parse_rotation_error();
+ }
+ }
+ }
+
class component final : public component_interface
{
public:
@@ -164,17 +271,16 @@ namespace map_rotation
scheduler::once([]
{
- dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, "");
- dvars::register_string("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, "");
- dvars::register_string("sv_autoPriority", "", game::DVAR_FLAG_NONE, "Lowers the process priority during map changes to not cause lags on other servers.");
+ sv_map_rotation = dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, "");
+ sv_map_rotation_current = dvars::register_string("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, "");
}, scheduler::pipeline::main);
+ sv_random_map_rotation = dvars::register_bool("sv_randomMapRotation", false, game::DVAR_FLAG_NONE, "Randomize map rotation");
+
command::add("map_rotate", &perform_map_rotation);
// Hook GScr_ExitLevel
utils::hook::jump(0xE2670_b, &trigger_map_rotation, true); // not sure if working
-
- previous_priority = GetPriorityClass(GetCurrentProcess());
}
};
}
diff --git a/src/client/component/map_rotation.hpp b/src/client/component/map_rotation.hpp
new file mode 100644
index 00000000..ab5d6605
--- /dev/null
+++ b/src/client/component/map_rotation.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+namespace map_rotation
+{
+ struct parse_rotation_error : public std::exception
+ {
+ const char* what() const noexcept override { return "Rotation parse error"; }
+ };
+
+ class rotation_data
+ {
+ public:
+ using rotation_entry = std::pair;
+
+ rotation_data();
+
+ void randomize();
+
+ // In case a new way to enrich the map rotation is added (other than sv_mapRotation)
+ // this method should be called to add a new entry (gamemode/map & value)
+ void add_entry(const std::string& key, const std::string& value);
+
+ [[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
+ [[nodiscard]] bool empty() const noexcept;
+ [[nodiscard]] std::size_t get_entries_size() const noexcept;
+ [[nodiscard]] rotation_entry& get_next_entry();
+
+ void parse(const std::string& data);
+
+ private:
+ std::vector