Merge branch 'develop'

This commit is contained in:
fed 2023-12-22 00:27:13 +01:00
commit a40cd645b0
139 changed files with 15188 additions and 13204 deletions

View File

@ -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/*

4
.gitmodules vendored
View File

@ -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

View File

@ -10,9 +10,7 @@
<img src="assets/github/banner.png?raw=true" />
</p>
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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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" );
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -1,5 +1,6 @@
require("language")
require("background_effects")
require("pausequit")
if game:issingleplayer() then
require("sp_unlockall")

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}, {

View File

@ -1,2 +1,3 @@
localize,english
ttf,fonts/bank_h1.ttf
ttf,fonts/default.otf
1 localize english
2 ttf fonts/bank_h1.ttf
3 ttf fonts/default.otf

View File

@ -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..."
}

Binary file not shown.

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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."

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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": "アンロックをキャンセル"

View File

@ -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": "잠금 해제 취소"

View File

@ -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"

View File

@ -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"

View File

@ -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",

View File

@ -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": "取消解锁"

View File

@ -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"

View File

@ -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": "取消解鎖"

View File

@ -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": "пинг",

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit 6c6111acb7b5d687ac006969ac96e5b1f21374cd
Subproject commit e64c97fc2cfc11992098bb38eda932de275e3f4d

2
deps/asmjit vendored

@ -1 +1 @@
Subproject commit 5b5b0b38775938df4d3779604ff1db60b9a9dcbf
Subproject commit 7c10a14d347879f889c6d11a9398f1d453acc690

2
deps/curl vendored

@ -1 +1 @@
Subproject commit 4ab601d93a07cee665ec2458a51fccd0767c03f1
Subproject commit 102de7aa8d5bfc6ed5fe85e89c7b943d0c186f03

2
deps/discord-rpc vendored

@ -1 +1 @@
Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33
Subproject commit b3383798b353c31ea6770fee673740c27f6e3489

View File

@ -1,30 +0,0 @@
#include "stdafx.hpp"
#include <xsk/h1.hpp>
#include "interface.hpp"
namespace gsc
{
std::unique_ptr<xsk::gsc::compiler> compiler()
{
auto compiler = std::make_unique<xsk::gsc::h1::compiler>();
compiler->mode(xsk::gsc::build::prod);
return compiler;
}
std::unique_ptr<xsk::gsc::decompiler> decompiler()
{
return std::make_unique<xsk::gsc::h1::decompiler>();
}
std::unique_ptr<xsk::gsc::assembler> assembler()
{
return std::make_unique<xsk::gsc::h1::assembler>();
}
std::unique_ptr<xsk::gsc::disassembler> disassembler()
{
return std::make_unique<xsk::gsc::h1::disassembler>();
}
}

View File

@ -1,9 +0,0 @@
#pragma once
namespace gsc
{
std::unique_ptr<xsk::gsc::compiler> compiler();
std::unique_ptr<xsk::gsc::decompiler> decompiler();
std::unique_ptr<xsk::gsc::assembler> assembler();
std::unique_ptr<xsk::gsc::disassembler> disassembler();
}

2
deps/gsc-tool vendored

@ -1 +1 @@
Subproject commit 7d374025b7675bada64c247ebe9378dd335a33da
Subproject commit cbfcce1dd67534c2115331e41d4cb6893e96196c

2
deps/json vendored

@ -1 +1 @@
Subproject commit 4c6cde72e533158e044252718c013a48bcff346c
Subproject commit a259ecc51e1951e12f757ce17db958e9881e9c6c

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4
Subproject commit 7e863d21429f94ed6a720e24499a12a3f852bb31

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 03de03dee753442d4b23166982514639c4ccbc39
Subproject commit 8314bde5e5c8e5d9331460130a9d1066e324f091

2
deps/lua vendored

@ -1 +1 @@
Subproject commit be908a7d4d8130264ad67c5789169769f824c5d1
Subproject commit 7923dbbf72da303ca1cca17efd24725668992f15

2
deps/minhook vendored

@ -1 +1 @@
Subproject commit 49d03ad118cf7f6768c79a8f187e14b8f2a07f94
Subproject commit f5485b8454544c2f034c78f8f127c1d03dea3636

View File

@ -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)

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 7ce9c415455c098409222702b3b4572b47232882
Subproject commit 5a3dac894157bf3618b2c906a8b9073b4cad62b6

2
deps/rapidjson vendored

@ -1 +1 @@
Subproject commit a98e99992bd633a2736cc41f96ec85ef0c50e44d
Subproject commit 6089180ecb704cb2b136777798fa1be303618975

2
deps/sol2 vendored

@ -1 +1 @@
Subproject commit f81643aa0c0c507c0cd8400b8cfedc74a34a19f6
Subproject commit 9c882a28fdb6f4ad79a53a4191b43ce48a661175

2
deps/stb vendored

@ -1 +1 @@
Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55
Subproject commit f4a71b13373436a2866c5d68f8f80ac6f0bc1ffe

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit e554695638228b846d49657f31eeff0ca4680e8a
Subproject commit 643e17b7498d12ab8d15565662880579692f769d

View File

@ -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<float>(font->pixelHeight),
1.f, 1.f, 0.0f, color, 0);
#else
game::R_AddCmdDrawText("h1-mod",
0x7FFFFFFF, font, 10.f,
5.f + static_cast<float>(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);
}
}

View File

@ -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<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
std::optional<std::string> 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);

View File

@ -49,6 +49,4 @@ namespace command
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback);
void execute(std::string command, bool sync = false);
void register_fs_game_path();
}

View File

@ -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");

View File

@ -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<std::string, game::Material*> 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<int>(server_net_info.ip[0]),
static_cast<int>(server_net_info.ip[1]),
static_cast<int>(server_net_info.ip[2]),
static_cast<int>(server_net_info.ip[3]),
static_cast<int>(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::seconds>(
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<int>(server_connection_state.host.ip[0]),
static_cast<int>(server_connection_state.host.ip[1]),
static_cast<int>(server_connection_state.host.ip[2]),
static_cast<int>(server_connection_state.host.ip[3]),
static_cast<int>(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::seconds>(
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<std::string, std::chrono::high_resolution_clock::time_point> 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);
}
}
};
}

View File

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

View File

@ -8,6 +8,8 @@
#include "game/ui_scripting/execution.hpp"
#include "utils/hash.hpp"
#include <utils/concurrency.hpp>
#include <utils/http.hpp>
#include <utils/io.hpp>
@ -23,10 +25,16 @@ namespace download
bool active{};
};
std::atomic_bool kill_downloads = false;
utils::concurrency::container<globals_t> globals;
bool download_aborted()
{
if (kill_downloads)
{
return true;
}
return globals.access<bool>([](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)

View File

@ -2,8 +2,10 @@
#include "loader/component_loader.hpp"
#include "dvars.hpp"
#include "console.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
@ -255,13 +257,13 @@ namespace dvars
namespace callback
{
static std::unordered_map<int, std::function<void()>> new_value_callbacks;
static std::unordered_map<int, std::function<void(game::dvar_value*)>> dvar_new_value_callbacks;
static std::unordered_map<int, std::function<void()>> dvar_on_register_function_map;
void on_new_value(const std::string& name, const std::function<void()> callback)
void on_new_value(const std::string& name, const std::function<void(game::dvar_value*)> 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<void()>& callback)
@ -532,15 +534,34 @@ namespace dvars
{
dvar_set_variant_hook.invoke<void>(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<std::string>();
const auto description = dvar_info[1].get<std::string>();
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);

View File

@ -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<void()> callback);
void on_new_value(const std::string& name, const std::function<void(game::dvar_value* value)> callback);
void on_register(const std::string& name, const std::function<void()>& callback);
}

View File

@ -0,0 +1,30 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "dvars.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
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

View File

@ -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<HANDLE> fastfile_handles;
game::dvar_t* g_dump_scripts;
game::dvar_t* db_print_default_assets;
utils::concurrency::container<std::vector<HANDLE>> 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<game::XAssetHeader>(type, override_rawfile_name.data(), 0);
const auto override_rawfile = db_find_xasset_header_hook.invoke<game::XAssetHeader>(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<game::XAssetHeader>(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<HANDLE>& 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<game::XZoneInfo> 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<std::uint32_t>(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<bool>(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<void>(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 <game::XAssetType Type, size_t Size>
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 <game::XAssetType Type, size_t Multiplier>
char* reallocate_asset_pool_multiplier()
{
constexpr auto pool_size = get_pool_type_size(Type);
return reallocate_asset_pool<Type, pool_size * Multiplier>();
}
#define RVA(ptr) static_cast<uint32_t>(reinterpret_cast<size_t>(ptr) - 0_b)
struct buffer_info
{
void* ptr;
size_t size;
};
std::vector<buffer_info> 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<uint32_t>(0x1186A4_b + 4, RVA(weapon_strings));
utils::hook::set<uint32_t>(0x1186B5_b + 4, RVA(weapon_strings));
utils::hook::set<uint32_t>(0x104BD2_b + 4, RVA(weapon_strings) - 0x38F1750);
reallocate_asset_pool<game::ASSET_TYPE_WEAPON, pool_size>();
utils::hook::inject(0x2E3005_b + 3,
reinterpret_cast<void*>(reinterpret_cast<size_t>(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<uint32_t>(0x427EB1_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x4240A8_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x54B8B1_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x2D274B_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x311DA8_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x311EB8_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x323BD1_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x2E0864_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x2F170C_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42CB3B_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x136327_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x4671FF_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x46722F_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x46754C_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x565FA2_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x56600E_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x2EA352_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x2EA369_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x56483F_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x2EA337_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x402261_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x4022A9_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x41CED5_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42B540_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42B560_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x5660CB_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42B523_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x117C82_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x411438_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x12AB34_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x129F9B_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x12AC16_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x12AC9D_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x424087_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x54B897_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x129F5C_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42406A_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x54B87B_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x565D1B_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x123FF5_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42CB1B_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x42CAFE_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x136310_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x46752E_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x1362F3_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0xF454D_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x41CC61_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x41D7FB_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x427E8F_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(0x427E72_b + 4, RVA(&weapon_complete_defs));
utils::hook::set<uint32_t>(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<game::ASSET_TYPE_ATTACHMENT, pool_size>();
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<uint32_t>(0x104C8A_b + 4, RVA(attachment_strings) - 0x38F1750);
const auto sub_118540_stub = [](utils::hook::assembler& a)
{
a.mov(rax, reinterpret_cast<size_t>(&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<size_t>(&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<game::ASSET_TYPE_SOUND, pool_size>();
utils::hook::inject(0x39621D_b + 3, reinterpret_cast<void*>(reinterpret_cast<size_t>(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<game::ASSET_TYPE_XANIM, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LOADED_SOUND, 2>();
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LOCALIZE, 2>();
}
}
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<game::XAssetEntry*>(type, header);
}
static game::XAssetEntry entry{};
if (type != game::ASSET_TYPE_STRINGTABLE)
{
return &entry;
}
return db_link_x_asset_entry_hook.invoke<game::XAssetEntry*>(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<HANDLE>& 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<game::XAssetType>(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 <type>\n");
return;
}
const auto type = static_cast<game::XAssetType>(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<game::XAssetType>(i), [&](game::XAssetHeader header)
{
count++;
}, true);
}
console::info("assets: %i / %i\n", count, 155000);
});
}
};
}

View File

@ -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<void>(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<void>(name);
}
std::vector<std::filesystem::path> 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_);
}
}

View File

@ -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());
}
}
}
}

View File

@ -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 <utils/hook.hpp>
#include <utils/string.hpp>
using namespace utils::string;
namespace gsc
{
@ -18,6 +22,37 @@ namespace gsc
std::string unknown_function_error;
std::array<const char*, 27> 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",
"<free>",
"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<unsigned char>(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<float>(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<int>(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 <size_t rva>
void safe_func()
{
static utils::hook::detour hook;
static const auto stub = []()
{
__try
{
hook.invoke<void>();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
game::Scr_ErrorInternal();
}
};
const auto ptr = rva + 0_b;
hook.create(reinterpret_cast<void*>(ptr), stub);
}
}
std::optional<std::pair<std::string, std::string>> 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

View File

@ -1,4 +1,3 @@
#pragma once
namespace gsc

View File

@ -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 <utils/hook.hpp>
#include "component/logfile.hpp"
#include "component/command.hpp"
#include "component/console.hpp"
#include "component/scripting.hpp"
#include "component/logfile.hpp"
#include <xsk/gsc/types.hpp>
#include <xsk/resolver.hpp>
#include "script_extension.hpp"
#include "script_error.hpp"
#include "script_extension.hpp"
#include "script_loading.hpp"
#include <utils/hook.hpp>
namespace gsc
{
@ -33,8 +27,8 @@ namespace gsc
namespace
{
std::unordered_map<std::uint32_t, script_function> functions;
std::unordered_map<std::uint32_t, script_method> methods;
std::unordered_map<std::uint16_t, script_function> functions;
std::unordered_map<std::uint16_t, script_method> methods;
bool force_error_print = false;
std::optional<std::string> gsc_error_msg;
@ -70,10 +64,15 @@ namespace gsc
reinterpret_cast<size_t>(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<std::uint16_t>(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<std::uint16_t>(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<void>(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<std::uint16_t>(id));
gsc_ctx->func_add(name, static_cast<std::uint16_t>(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<std::uint16_t>(id));
gsc_ctx->meth_add(name, static_cast<std::uint16_t>(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<uint32_t>(SELECT_VALUE(0x3BD86C_b, 0x50484C_b), 0x1000); // change builtin func count
utils::hook::set<uint32_t>(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<uint32_t>(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<std::string>();
command::execute(cmd, true);
command::execute(args[0].as<std::string>(), false);
return scripting::script_value{};
});

View File

@ -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);

View File

@ -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 <xsk/gsc/types.hpp>
#include <xsk/gsc/interfaces/compiler.hpp>
#include <xsk/gsc/interfaces/decompiler.hpp>
#include <xsk/gsc/interfaces/assembler.hpp>
#include <xsk/gsc/interfaces/disassembler.hpp>
#include <xsk/utils/compression.hpp>
#include <xsk/resolver.hpp>
#include <interface.hpp>
#include "script_extension.hpp"
#include "script_loading.hpp"
#include <utils/compression.hpp>
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace gsc
{
std::unique_ptr<xsk::gsc::h1::context> gsc_ctx = std::make_unique<xsk::gsc::h1::context>();;
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<std::string, std::uint32_t> main_handles;
std::unordered_map<std::string, std::uint32_t> init_handles;
utils::memory::allocator scriptfile_allocator;
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
std::unordered_map<const char*, game::ScriptFile*> 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<std::uint8_t> 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<std::uint8_t> 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<game::ScriptFile*>(scriptfile_allocator.allocate(sizeof(game::ScriptFile)));
script_file_ptr->name = file_name;
script_file_ptr->len = static_cast<int>(stack.size);
script_file_ptr->bytecodeLen = static_cast<int>(bytecode.size);
const auto stack_size = static_cast<std::uint32_t>(stack.size + 1);
const auto byte_code_size = static_cast<std::uint32_t>(bytecode.size + 1);
script_file_ptr->buffer = static_cast<char*>(scriptfile_allocator.allocate(stack_size));
std::memcpy(const_cast<char*>(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<game::ScriptFile>();
script_file_ptr->name = file_name;
const auto stack = assembler->output_stack();
script_file_ptr->len = static_cast<int>(stack.size());
const auto script = assembler->output_script();
script_file_ptr->bytecodeLen = static_cast<int>(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<std::uint8_t> decompile_script_file(const std::string& name, const std::string& real_name)
std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> 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<std::uint8_t> stack{script_file->buffer, script_file->buffer + script_file->len};
std::vector<std::uint8_t> 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<std::uint32_t>(len)};
auto decompressed_stack = xsk::utils::zlib::decompress(stack, static_cast<std::uint32_t>(stack.size()));
const auto decompressed_stack = utils::compression::zlib::decompress(stack);
disassembler->disassemble(name, bytecode, decompressed_stack);
auto output = disassembler->output();
std::vector<std::uint8_t> stack_data;
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
decompiler->decompile(name, output);
return decompiler->output();
return {{reinterpret_cast<std::uint8_t*>(script_file->bytecode), static_cast<std::uint32_t>(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<void>(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<void>(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<void>(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<xsk::gsc::buffer, std::vector<std::uint8_t>>
{
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<char*>(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<std::uint8_t> 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<uint32_t>(SELECT_VALUE(0x420252_b, 0x5A5582_b), size_0 + size_1);
scr_begin_load_scripts_hook.invoke<void>();
}
void scr_end_load_scripts_stub()
{
// cleanup the compiler
gsc_ctx->cleanup();
scr_end_load_scripts_hook.invoke<void>();
}
}
@ -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::uint16_t>(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<std::uint8_t> 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();
}
};
}

View File

@ -1,7 +1,10 @@
#pragma once
#include <xsk/gsc/engine/h1.hpp>
namespace gsc
{
extern std::unique_ptr<xsk::gsc::h1::context> gsc_ctx;
void load_main_handles();
void load_init_handles();
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);

View File

@ -0,0 +1,230 @@
#include <std_include.hpp>
#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 <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
#include <utils/concurrency.hpp>
#define CUSTOM_IMAGE_FILE_INDEX 96
namespace imagefiles
{
namespace
{
utils::memory::allocator image_file_allocator;
std::unordered_map<std::string, game::DB_IFileSysFile*> 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<std::string, image_file_unk*> image_file_unk_map;
void* get_image_file_unk_mp(unsigned int index)
{
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return &reinterpret_cast<image_file_unk*>(
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>();
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<std::string, image_file_unk*> image_file_unk_map;
void* get_image_file_unk_mp(unsigned int index)
{
if (index != CUSTOM_IMAGE_FILE_INDEX)
{
return &reinterpret_cast<image_file_unk*>(
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>();
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<game::DB_IFileSysFile**>(
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<void*>(
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)

View File

@ -0,0 +1,7 @@
#pragma once
namespace imagefiles
{
void close_custom_handles();
void close_handle(const std::string& fastfile);
}

View File

@ -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;

View File

@ -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);

View File

@ -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<uint8_t>(p_address, 6); // iw6
}
else
{
utils::hook::set<uint8_t>(p_address, 7); // s1,h1,h2
}
r_decode_light_grid_block_hook.invoke<void>(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<uint8_t>(0x2F377D_b, 0xEB); // createfx parse
utils::hook::set<uint8_t>(0x4444E0_b, 0xEB); // scr_loadfx
// patch iw6 leafTable decoding
r_decode_light_grid_block_hook.create(0x69E7D0_b, r_decode_light_grid_block_stub);
}
};
}

View File

@ -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<std::string> parse_current_map_rotation()
{
const auto rotation = load_current_map_rotation();
return utils::string::split(rotation, ' ');
}
void store_new_rotation(const std::vector<std::string>& 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());
}
};
}

View File

@ -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<std::string, std::string>;
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<rotation_entry> rotation_entries_;
std::size_t index_;
};
}

View File

@ -3,17 +3,12 @@
#include "game/game.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <xsk/gsc/types.hpp>
#include <xsk/gsc/interfaces/compiler.hpp>
#include <xsk/gsc/interfaces/decompiler.hpp>
#include <xsk/gsc/interfaces/assembler.hpp>
#include <xsk/gsc/interfaces/disassembler.hpp>
#include <xsk/resolver.hpp>
#include <interface.hpp>
#include "gsc/script_loading.hpp"
namespace mapents
{
@ -30,6 +25,7 @@ namespace mapents
for (auto i = 0; i < lines.size(); i++)
{
auto line_num = i+1;
auto line = lines[i];
if (line.ends_with('\r'))
{
@ -67,7 +63,7 @@ namespace mapents
if (line[0] == '{' && in_map_ent)
{
console::error("[map_ents parser] Unexpected '{' on line %i\n", i);
console::error("[map_ents parser] Unexpected '{' on line %i\n", line_num);
return {};
}
@ -92,15 +88,15 @@ namespace mapents
if (line[0] == '}' && !in_map_ent)
{
console::error("[map_ents parser] Unexpected '}' on line %i\n", i);
console::error("[map_ents parser] Unexpected '}' on line %i\n", line_num);
return {};
}
std::regex expr(R"~((.+) "(.*)")~");
std::smatch match{};
if (!std::regex_search(line, match, expr))
if (!std::regex_search(line, match, expr) && !line.empty())
{
console::warn("[map_ents parser] Failed to parse line %i (%s)\n", i, line.data());
console::warn("[map_ents parser] Failed to parse line %i (%s)\n", line_num, line.data());
continue;
}
@ -109,7 +105,7 @@ namespace mapents
if (key.size() <= 0)
{
console::warn("[map_ents parser] Invalid key ('%s') on line %i (%s)\n", key.data(), i, line.data());
console::warn("[map_ents parser] Invalid key ('%s') on line %i (%s)\n", key.data(), line_num, line.data());
continue;
}
@ -128,10 +124,10 @@ namespace mapents
}
const auto key_ = key.substr(1, key.size() - 2);
const auto id = xsk::gsc::h1::resolver::token_id(key_);
const auto id = gsc::gsc_ctx->token_id(key_);
if (id == 0)
{
console::warn("[map_ents parser] Key '%s' not found, on line %i (%s)\n", key_.data(), i, line.data());
console::warn("[map_ents parser] Key '%s' not found, on line %i (%s)\n", key_.data(), line_num, line.data());
continue;
}
@ -141,16 +137,48 @@ namespace mapents
return {out_buffer};
}
std::string raw_ents;
bool load_raw_mapents()
{
auto mapents_name = utils::string::va("%s.ents", **reinterpret_cast<const char***>(SELECT_VALUE(0xB489D40_b, 0xA975F40_b)));
if (filesystem::exists(mapents_name))
{
try
{
console::debug("Reading raw ents file \"%s\"\n", mapents_name);
raw_ents = filesystem::read_file(mapents_name);
if (!raw_ents.empty())
{
return true;
}
}
catch (const std::exception& ex)
{
console::error("Failed to read raw ents file \"%s\"\n%s\n", mapents_name, ex.what());
}
}
return false;
}
std::string entity_string;
const char* cm_entity_string_stub()
{
if (!entity_string.empty())
const char* ents = nullptr;
if (load_raw_mapents())
{
return entity_string.data();
ents = raw_ents.data();
}
else
{
if (!entity_string.empty())
{
return entity_string.data();
}
ents = utils::hook::invoke<const char*>(SELECT_VALUE(0x3685C0_b, 0x4CD140_b));
}
const auto original = utils::hook::invoke<const char*>(SELECT_VALUE(0x3685C0_b, 0x4CD140_b));
const auto parsed = parse_mapents(original);
const auto parsed = parse_mapents(ents);
if (parsed.has_value())
{
entity_string = parsed.value();
@ -158,13 +186,14 @@ namespace mapents
}
else
{
return original;
return ents;
}
}
void cm_unload_stub(void* clip_map)
{
entity_string.clear();
raw_ents.clear();
utils::hook::invoke<void>(SELECT_VALUE(0x368560_b, 0x4CD0E0_b), clip_map);
}
}

View File

@ -4,6 +4,7 @@
#include "materials.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
@ -20,123 +21,17 @@ namespace materials
namespace
{
utils::hook::detour db_material_streaming_fail_hook;
utils::hook::detour material_register_handle_hook;
utils::hook::detour db_get_material_index_hook;
struct material_data_t
{
std::unordered_map<std::string, game::Material*> materials;
std::unordered_map<std::string, std::string> images;
};
#ifdef DEBUG
utils::hook::detour material_compare_hook;
utils::hook::detour set_pixel_texture_hook;
const game::dvar_t* debug_materials = nullptr;
#endif
char constant_table[0x20] = {};
utils::concurrency::container<material_data_t> material_data;
game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image)
{
image->imageFormat = 0x1000003;
image->resourceSize = -1;
D3D11_SUBRESOURCE_DATA data{};
data.SysMemPitch = raw_image.get_width() * 4;
data.SysMemSlicePitch = data.SysMemPitch * raw_image.get_height();
data.pSysMem = raw_image.get_buffer();
game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements,
image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, image->name, &data);
return image;
}
game::Material* create_material(const std::string& name, const std::string& data)
{
const auto white = material_register_handle_hook.invoke<game::Material*>("white");
const auto material = utils::memory::get_allocator()->allocate<game::Material>();
const auto texture_table = utils::memory::get_allocator()->allocate<game::MaterialTextureDef>();
const auto image = utils::memory::get_allocator()->allocate<game::GfxImage>();
std::memcpy(material, white, sizeof(game::Material));
std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef));
std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage));
material->constantTable = &constant_table;
material->name = utils::memory::get_allocator()->duplicate_string(name);
image->name = material->name;
material->textureTable = texture_table;
material->textureTable->u.image = setup_image(image, data);
return material;
}
void free_material(game::Material* material)
{
material->textureTable->u.image->textures.___u0.map->Release();
material->textureTable->u.image->textures.shaderView->Release();
utils::memory::get_allocator()->free(material->textureTable->u.image);
utils::memory::get_allocator()->free(material->textureTable);
utils::memory::get_allocator()->free(material->name);
utils::memory::get_allocator()->free(material);
}
game::Material* load_material(const std::string& name)
{
return material_data.access<game::Material*>([&](material_data_t& data_) -> game::Material*
{
if (const auto i = data_.materials.find(name); i != data_.materials.end())
{
return i->second;
}
std::string data{};
if (const auto i = data_.images.find(name); i != data_.images.end())
{
data = i->second;
}
if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
{
data_.materials[name] = nullptr;
return nullptr;
}
const auto material = create_material(name, data);
data_.materials[name] = material;
return material;
});
}
game::Material* try_load_material(const std::string& name)
{
if (name == "white")
{
return nullptr;
}
try
{
return load_material(name);
}
catch (const std::exception& e)
{
console::error("Failed to load material %s: %s\n", name.data(), e.what());
}
return nullptr;
}
game::Material* material_register_handle_stub(const char* name)
{
auto result = try_load_material(name);
if (result == nullptr)
{
result = material_register_handle_hook.invoke<game::Material*>(name);
}
return result;
}
int db_material_streaming_fail_stub(game::Material* material)
{
if (material->constantTable == &constant_table)
@ -156,40 +51,148 @@ namespace materials
return db_get_material_index_hook.invoke<unsigned int>(material);
}
}
void add(const std::string& name, const std::string& data)
{
material_data.access([&](material_data_t& data_)
#ifdef DEBUG
char material_compare_stub(unsigned int index_a, unsigned int index_b)
{
data_.images[name] = data;
});
}
char result = 0;
bool exists(const std::string& name)
{
return material_data.access<bool>([&](material_data_t& data_)
{
return data_.images.find(name) != data_.images.end();
});
}
void clear()
{
material_data.access([&](material_data_t& data_)
{
for (auto& material : data_.materials)
__try
{
if (material.second == nullptr)
{
continue;
}
free_material(material.second);
result = material_compare_hook.invoke<char>(index_a, index_b);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
const auto* material_a = utils::hook::invoke<game::Material*>(0x395FE0_b, index_a);
const auto* material_b = utils::hook::invoke<game::Material*>(0x395FE0_b, index_b);
console::error("Material_Compare: %s - %s (%d - %d)",
material_a->name, material_b->name, material_a->info.sortKey, material_b->info.sortKey);
}
data_.materials.clear();
});
return result;
}
void print_material(const game::Material* material)
{
if (!debug_materials || !debug_materials->current.enabled)
{
return;
}
console::debug("current material is \"%s\"\n", material->name);
}
void print_current_material_stub(utils::hook::assembler& a)
{
const auto loc_6AD59B = a.newLabel();
a.pushad64();
a.mov(rcx, r15);
a.call_aligned(print_material);
a.popad64();
a.cmp(byte_ptr(rbx), 5);
a.mov(rax, ptr(r15, 0x130));
a.jnz(loc_6AD59B);
a.nop(dword_ptr(rax, rax, 0x00000000));
a.jmp(0x6AD570_b);
a.bind(loc_6AD59B);
a.jmp(0x6AD59B_b);
}
void set_pixel_texture_stub(void* cmd_buf_state, unsigned int a2, const game::GfxImage* image)
{
if (!debug_materials || !debug_materials->current.enabled)
{
set_pixel_texture_hook.invoke<void>(cmd_buf_state, a2, image);
return;
}
if (image && image->name)
{
console::debug("set_pixel_texture_stub: \"%s\"\n", image->name);
}
else
{
console::error("set_pixel_texture_stub: texture has no name or is nullptr\n");
}
set_pixel_texture_hook.invoke<void>(cmd_buf_state, a2, image);
}
#endif
}
bool setup_material_image(game::Material* material, const std::string& data)
{
if (*game::d3d11_device == nullptr)
{
console::error("Tried to create texture while d3d11 device isn't initialized\n");
return false;
}
const auto image = material->textureTable->u.image;
image->imageFormat = 0x1000003;
image->resourceSize = -1;
auto raw_image = utils::image{data};
D3D11_SUBRESOURCE_DATA resource_data{};
resource_data.SysMemPitch = raw_image.get_width() * 4;
resource_data.SysMemSlicePitch = resource_data.SysMemPitch * raw_image.get_height();
resource_data.pSysMem = raw_image.get_buffer();
game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements,
image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, image->name, &resource_data);
return true;
}
game::Material* create_material(const std::string& name)
{
const auto white = game::Material_RegisterHandle("$white");
const auto material = utils::memory::allocate<game::Material>();
const auto texture_table = utils::memory::allocate<game::MaterialTextureDef>();
const auto image = utils::memory::allocate<game::GfxImage>();
std::memcpy(material, white, sizeof(game::Material));
std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef));
std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage));
material->constantTable = &constant_table;
material->name = utils::memory::duplicate_string(name);
image->name = material->name;
image->textures.map = nullptr;
image->textures.shaderView = nullptr;
image->textures.shaderViewAlternate = nullptr;
texture_table->u.image = image;
material->textureTable = texture_table;
return material;
}
void free_material(game::Material* material)
{
const auto try_release = []<typename T>(T** resource)
{
if (*resource != nullptr)
{
(*resource)->Release();
*resource = nullptr;
}
};
try_release(&material->textureTable->u.image->textures.map);
try_release(&material->textureTable->u.image->textures.shaderView);
try_release(&material->textureTable->u.image->textures.shaderViewAlternate);
utils::memory::free(material->textureTable->u.image);
utils::memory::free(material->textureTable);
utils::memory::free(material->name);
utils::memory::free(material);
}
class component final : public component_interface
@ -202,9 +205,23 @@ namespace materials
return;
}
material_register_handle_hook.create(game::Material_RegisterHandle, material_register_handle_stub);
db_material_streaming_fail_hook.create(SELECT_VALUE(0x1FB400_b, 0x3A1600_b), db_material_streaming_fail_stub);
db_get_material_index_hook.create(SELECT_VALUE(0x1F1D80_b, 0x396000_b), db_get_material_index_stub);
#ifdef DEBUG
if (!game::environment::is_sp())
{
material_compare_hook.create(0x693B90_b, material_compare_stub);
set_pixel_texture_hook.create(0x6B33E0_b, set_pixel_texture_stub);
utils::hook::jump(0x6AD55C_b, utils::hook::assemble(print_current_material_stub), true);
scheduler::once([]
{
debug_materials = dvars::register_bool("debug_materials", false, game::DVAR_FLAG_NONE, "Print current material and images");
}, scheduler::main);
}
#endif
}
};
}

View File

@ -1,8 +1,10 @@
#pragma once
#include "game/game.hpp"
namespace materials
{
void add(const std::string& name, const std::string& data);
bool exists(const std::string& name);
void clear();
bool setup_material_image(game::Material* material, const std::string& data);
game::Material* create_material(const std::string& name);
void free_material(game::Material* material);
}

View File

@ -0,0 +1,143 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "memory.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/flags.hpp>
namespace memory
{
namespace
{
constexpr auto mem_low_size = 0x80000000ui64 * 2; // default: 0x80000000
constexpr auto mem_high_size = 0x80000000ui64 * 2; // default: 0x80000000
constexpr auto script_mem_low_size = 0x100000ui64; // default: 0x100000
constexpr auto script_mem_high_size = 0x100000ui64 + custom_script_mem_size; // default: 0x100000
constexpr auto phys_mem_low_size = 0x700000000ui64; // default: 0x700000000
constexpr auto phys_mem_high_size = 0x300000000i64; // default: 0x300000000
constexpr auto pmem_alloc_size =
mem_low_size +
mem_high_size +
script_mem_low_size +
script_mem_high_size +
phys_mem_low_size +
phys_mem_high_size;
constexpr auto mem_low_buf = 0;
constexpr auto mem_high_buf = mem_low_buf + mem_low_size;
constexpr auto script_mem_low_buf = mem_high_buf + mem_high_size;
constexpr auto script_mem_high_buf = script_mem_low_buf + script_mem_low_size;
constexpr auto phys_mem_low_buf = script_mem_high_buf + script_mem_high_size;
constexpr auto phys_mem_high_buf = phys_mem_low_buf + phys_mem_low_size;
constexpr auto stream_mem_size = 0x2000000ui64;
void pmem_init()
{
const auto size = pmem_alloc_size;
const auto allocated_buffer = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_READWRITE);
auto buffer = reinterpret_cast<unsigned char*>(allocated_buffer);
*game::pmem_size = size;
*game::pmem_buffer = buffer;
memset(game::g_mem, 0, sizeof(*game::g_mem));
game::g_mem->prim[game::PHYS_ALLOC_LOW].buf = buffer + mem_low_buf;
game::g_mem->prim[game::PHYS_ALLOC_LOW].pos = mem_low_size;
game::g_mem->prim[game::PHYS_ALLOC_HIGH].buf = buffer + mem_high_buf;
game::g_mem->prim[game::PHYS_ALLOC_HIGH].pos = mem_high_size;
game::g_mem->prim[game::PHYS_ALLOC_LOW].unk1 = 0;
game::g_mem->prim[game::PHYS_ALLOC_HIGH].unk1 = 0;
memset(game::g_scriptmem, 0, sizeof(*game::g_scriptmem));
game::g_scriptmem->prim[game::PHYS_ALLOC_LOW].buf = buffer + script_mem_low_buf;
game::g_scriptmem->prim[game::PHYS_ALLOC_LOW].pos = script_mem_low_size;
game::g_scriptmem->prim[game::PHYS_ALLOC_HIGH].buf = buffer + script_mem_high_buf;
game::g_scriptmem->prim[game::PHYS_ALLOC_HIGH].pos = script_mem_high_size;
game::g_scriptmem->prim[game::PHYS_ALLOC_LOW].unk1 = 0;
game::g_scriptmem->prim[game::PHYS_ALLOC_HIGH].unk1 = 0;
memset(game::g_physmem, 0, sizeof(*game::g_physmem));
game::g_physmem->prim[game::PHYS_ALLOC_LOW].buf = buffer + phys_mem_low_buf;
game::g_physmem->prim[game::PHYS_ALLOC_LOW].pos = phys_mem_low_size;
game::g_physmem->prim[game::PHYS_ALLOC_HIGH].buf = buffer + phys_mem_high_buf;
game::g_physmem->prim[game::PHYS_ALLOC_HIGH].pos = phys_mem_high_size;
game::g_physmem->prim[game::PHYS_ALLOC_LOW].unk1 = 2;
game::g_physmem->prim[game::PHYS_ALLOC_HIGH].unk1 = 2;
*game::stream_size = stream_mem_size;
*game::stream_buffer = reinterpret_cast<unsigned char*>(VirtualAlloc(NULL, *game::stream_size, MEM_COMMIT, PAGE_READWRITE));
}
void pmem_init_stub()
{
// call our own init
pmem_init();
const auto script_mem_size = script_mem_low_size + script_mem_high_size;
utils::hook::set<uint32_t>(SELECT_VALUE(0x420252_b, 0x5A5582_b), static_cast<uint32_t>(script_mem_size));
}
}
namespace
{
int out_of_memory_text_stub(char* dest, int size, const char* fmt, ...)
{
fmt = "%s (%d)\n\n"
"Disable shader caching, lower graphic settings, free up RAM, or update your GPU drivers.\n\n"
"If this still occurs, try using the '-memoryfix' parameter to generate the 'players2' folder.";
char buffer[2048];
{
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
}
return utils::hook::invoke<int>(SELECT_VALUE(0x429200_b, 0x5AF0F0_b), dest, size, "%s", buffer);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// patch PMem_Init, so we can use whatever memory size we want
utils::hook::call(SELECT_VALUE(0x38639C_b, 0x15C4D6_b), pmem_init_stub);
// Com_sprintf for "Out of memory. You are probably low on disk space."
utils::hook::call(SELECT_VALUE(0x457BC9_b, 0x1D8E09_b), out_of_memory_text_stub);
// "fix" for rare 'Out of memory error' error
// this will *at least* generate the configs for mp/sp, which is the #1 issue
if (utils::flags::has_flag("memoryfix"))
{
utils::hook::jump(SELECT_VALUE(0x5110D0_b, 0x6200C0_b), malloc);
utils::hook::jump(SELECT_VALUE(0x510FF0_b, 0x61FFE0_b), _aligned_malloc);
utils::hook::jump(SELECT_VALUE(0x511130_b, 0x620120_b), free);
utils::hook::jump(SELECT_VALUE(0x511220_b, 0x620210_b), realloc);
utils::hook::jump(SELECT_VALUE(0x511050_b, 0x620040_b), _aligned_realloc);
}
}
};
}
REGISTER_COMPONENT(memory::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace memory
{
constexpr auto custom_script_mem_size = 0x1000000ui64;
}

View File

@ -5,12 +5,14 @@
#include "command.hpp"
#include "console.hpp"
#include "dvars.hpp"
#include "filesystem.hpp"
#include "fonts.hpp"
#include "localized_strings.hpp"
#include "materials.hpp"
#include "mods.hpp"
#include "scheduler.hpp"
#include "game/demonware/services.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
@ -28,7 +30,6 @@ namespace mods
{
if (release_assets)
{
materials::clear();
fonts::clear();
}
@ -53,8 +54,11 @@ namespace mods
{
if (game::environment::is_mp())
{
// vid_restart works on multiplayer, but not on singleplayer
command::execute("vid_restart");
scheduler::once([]
{
mods::read_stats();
}, scheduler::main);
return;
}
@ -69,32 +73,37 @@ namespace mods
return utils::io::file_exists(path + "/mod.ff") || utils::io::file_exists(path + "/zone/mod.ff");
}
void set_filesystem_data(const std::string& path)
void set_filesystem_data(const std::string& path, bool change_fs_game)
{
if (mod_path.has_value())
{
filesystem::unregister_path(mod_path.value());
}
if (!game::environment::is_sp())
if (change_fs_game)
{
// modify fs_game on mp/dedi because its not set when we obviously vid_restart (sp does a full relaunch with command line arguments)
game::Dvar_SetFromStringByNameFromSource("fs_game", path.data(),
game::DVAR_SOURCE_INTERNAL);
game::Dvar_SetFromStringByNameFromSource("fs_game", path.data(), game::DVAR_SOURCE_INTERNAL);
}
if (path != "")
{
filesystem::register_path(path);
}
}
}
void set_mod(const std::string& path)
void set_mod(const std::string& path, bool change_fs_game)
{
set_filesystem_data(path);
mod_path = path;
}
set_filesystem_data(path, change_fs_game);
void clear_mod()
{
set_filesystem_data("");
mod_path.reset();
if (path != "")
{
mod_path = path;
}
else
{
mod_path.reset();
}
}
std::optional<std::string> get_mod()
@ -102,6 +111,12 @@ namespace mods
return mod_path;
}
void read_stats()
{
demonware::set_storage_path(mod_path.value_or(""));
utils::hook::invoke<void>(0x4E6B60_b, 0); // read stats
}
class component final : public component_interface
{
public:
@ -114,6 +129,12 @@ namespace mods
db_release_xassets_hook.create(SELECT_VALUE(0x1F4DB0_b, 0x399740_b), db_release_xassets_stub);
dvars::callback::on_new_value("fs_game", [](game::dvar_value* value)
{
console::warn("fs_game value changed to '%s'\n", value->string);
set_mod(value->string, false);
});
command::add("loadmod", [](const command::params& params)
{
if (params.size() < 2)
@ -143,7 +164,7 @@ namespace mods
mod_requires_restart(path))
{
console::info("Restarting...\n");
full_restart("+set fs_game \""s + path + "\"");
full_restart("-mod \""s + path + "\"");
}
else
{
@ -171,12 +192,12 @@ namespace mods
if (mod_requires_restart(mod_path.value()))
{
console::info("Restarting...\n");
clear_mod();
set_mod("");
full_restart("");
}
else
{
clear_mod();
set_mod("");
restart();
}
});

View File

@ -2,7 +2,7 @@
namespace mods
{
void set_mod(const std::string& path);
void clear_mod();
void set_mod(const std::string& path, bool change_fs_game = true);
std::optional<std::string> get_mod();
}
void read_stats();
}

View File

@ -0,0 +1,37 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "materials.hpp"
#include "game/game.hpp"
#include <utils/http.hpp>
namespace motd
{
class component final : public component_interface
{
public:
void post_load() override
{
std::thread([]
{
//auto data = utils::http::get_data("https://h1.gg/data/motd.png");
//if (data.has_value())
//{
// materials::add("motd_image", data.value().buffer);
//}
}).detach();
}
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
}
};
}
REGISTER_COMPONENT(motd::component)

View File

@ -112,7 +112,7 @@ namespace movement
const auto ps = pm->ps;
ps->sprintButtonUpRequired = 1;
ps->sprintState.sprintButtonUpRequired = 1;
float fmove{}, smove{}, wishspeed{};
game::vec3_t wishvel{}, wishdir{};

View File

@ -11,11 +11,14 @@
#include "fastfiles.hpp"
#include "mods.hpp"
#include "game/dvars.hpp"
#include "game/game.hpp"
#include "game/ui_scripting/execution.hpp"
#include "steam/steam.hpp"
#include "utils/hash.hpp"
#include <utils/properties.hpp>
#include <utils/string.hpp>
#include <utils/info_string.hpp>
@ -23,19 +26,14 @@
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <zlib.h>
namespace party
{
namespace
{
struct
{
game::netadr_s host{};
std::string challenge{};
bool hostDefined{false};
} connect_state;
std::string sv_motd;
int sv_maxclients;
connection_state server_connection_state{};
std::optional<discord_information> server_discord_info{};
struct usermap_file
{
@ -44,11 +42,20 @@ namespace party
bool optional;
};
// snake case these names before release
std::vector<usermap_file> usermap_files =
{
{".ff", "usermaphash", false},
{"_load.ff", "usermaploadhash", true},
{".arena", "usermaparenahash", true},
{".ff", "usermap_hash", false},
{"_load.ff", "usermap_load_hash", true},
{".arena", "usermap_arena_hash", true},
{".pak", "usermap_pak_hash", true},
};
std::vector<usermap_file> mod_files =
{
{".ff", "mod_hash", false},
{"_pre_gfx.ff", "mod_pre_gfx_hash", true},
{".pak", "mod_pak_hash", true},
};
struct
@ -57,6 +64,8 @@ namespace party
utils::info_string info_string{};
} saved_info_response;
const game::dvar_t* sv_say_name = nullptr;
void perform_game_initialization()
{
command::execute("onlinegame 1", true);
@ -149,11 +158,11 @@ namespace party
const char* get_didyouknow_stub(void* table, int row, int column)
{
if (party::sv_motd.empty())
if (server_connection_state.motd.empty())
{
return utils::hook::invoke<const char*>(0x5A0AC0_b, table, row, column);
}
return utils::string::va("%s", party::sv_motd.data());
return utils::string::va("%s", server_connection_state.motd.data());
}
void disconnect()
@ -188,26 +197,46 @@ namespace party
std::string get_file_hash(const std::string& file)
{
if (!utils::io::file_exists(file))
{
return {};
}
const auto iter = hash_cache.find(file);
if (iter != hash_cache.end())
{
return iter->second;
}
const auto data = utils::io::read_file(file);
const auto sha = utils::cryptography::sha1::compute(data, true);
hash_cache[file] = sha;
return sha;
const auto hash = utils::hash::get_file_hash(file);
if (!hash.empty())
{
hash_cache.insert(std::make_pair(file, hash));
}
return hash;
}
std::string get_usermap_file_path(const std::string& mapname, const std::string& extension)
{
return utils::string::va("usermaps\\%s\\%s%s", mapname.data(), mapname.data(), extension.data());
return std::format("usermaps\\{}\\{}{}", mapname, mapname, extension);
}
// generate hashes so they are cached
void generate_hashes(const std::string& mapname)
{
// usermap
for (const auto& file : usermap_files)
{
const auto path = get_usermap_file_path(mapname, file.extension);
get_file_hash(path);
}
// mod
const auto fs_game = get_dvar_string("fs_game");
if (!fs_game.empty())
{
for (const auto& file : mod_files)
{
const auto path = std::format("{}\\mod{}", fs_game, file.extension);
get_file_hash(path);
}
}
}
void check_download_map(const utils::info_string& info, std::vector<download::file_t>& files)
@ -223,21 +252,23 @@ namespace party
throw std::runtime_error(utils::string::va("Invalid server mapname value %s\n", mapname.data()));
}
const auto check_file = [&](const std::string& ext, const std::string& name, bool optional)
const auto check_file = [&](const usermap_file& file)
{
const std::string filename = utils::string::va("usermaps/%s/%s%s", mapname.data(), mapname.data(), ext.data());
const auto source_hash = info.get(name);
const std::string filename = utils::string::va("usermaps/%s/%s%s",
mapname.data(), mapname.data(), file.extension.data());
const auto source_hash = info.get(file.name);
if (source_hash.empty())
{
if (!optional)
if (!file.optional)
{
throw std::runtime_error(utils::string::va("Server %s is empty", name.data()));
throw std::runtime_error(utils::string::va("Server %s is empty", file.name.data()));
}
return;
}
const auto hash = get_file_hash(filename);
console::debug("hash != source_hash => %s != %s\n", source_hash.data(), hash.data());
if (hash != source_hash)
{
files.emplace_back(filename, source_hash);
@ -245,9 +276,9 @@ namespace party
}
};
for (const auto& [ext, name, opt] : usermap_files)
for (const auto& file : usermap_files)
{
check_file(ext, name, opt);
check_file(file);
}
}
@ -264,7 +295,7 @@ namespace party
if (server_fs_game.empty() && !client_fs_game.empty())
{
mods::clear_mod();
mods::set_mod("");
return true;
}
@ -273,35 +304,52 @@ namespace party
throw std::runtime_error(utils::string::va("Invalid server fs_game value %s\n", server_fs_game.data()));
}
const auto source_hash = info.get("modHash");
if (source_hash.empty())
auto needs_restart = false;
for (const auto& file : mod_files)
{
throw std::runtime_error("Connection failed: Server mod hash is empty.");
const auto source_hash = info.get(file.name);
if (source_hash.empty())
{
if (file.optional)
{
continue;
}
throw std::runtime_error(
utils::string::va("Connection failed: Server %s is empty.", file.name.data()));
}
const auto file_path = server_fs_game + "/mod" + file.extension;
auto has_to_download = !utils::io::file_exists(file_path);
if (!has_to_download)
{
const auto hash = get_file_hash(file_path);
console::debug("has_to_download = %s != %s\n", source_hash.data(), hash.data());
has_to_download = source_hash != hash;
}
if (has_to_download)
{
// unload mod before downloading it
if (client_fs_game == server_fs_game)
{
mods::set_mod("", true);
return true;
}
else
{
files.emplace_back(file_path, source_hash);
}
}
else if (client_fs_game != server_fs_game)
{
mods::set_mod(server_fs_game);
needs_restart = true;
}
}
const auto mod_path = server_fs_game + "/mod.ff";
auto has_to_download = !utils::io::file_exists(mod_path);
if (!has_to_download)
{
const auto data = utils::io::read_file(mod_path);
const auto hash = utils::cryptography::sha1::compute(data, true);
has_to_download = source_hash != hash;
}
if (has_to_download)
{
files.emplace_back(mod_path, source_hash);
return false;
}
else if (client_fs_game != server_fs_game)
{
mods::set_mod(server_fs_game);
return true;
}
return false;
return needs_restart;
}
void close_joining_popups()
@ -404,6 +452,7 @@ namespace party
needs_vid_restart = false;
scheduler::once([=]()
{
mods::read_stats();
connect(target);
}, scheduler::pipeline::main);
return true;
@ -428,21 +477,21 @@ namespace party
fastfiles::set_usermap(mapname);
for (const auto& [ext, key, opt] : usermap_files)
for (const auto& file : usermap_files)
{
char buffer[0x100] = {0};
const std::string source_hash = game::MSG_ReadStringLine(msg,
buffer, static_cast<unsigned int>(sizeof(buffer)));
const auto path = get_usermap_file_path(mapname, ext);
const auto path = get_usermap_file_path(mapname, file.extension);
const auto hash = get_file_hash(path);
if ((!source_hash.empty() && hash != source_hash) || (source_hash.empty() && !opt))
if ((!source_hash.empty() && hash != source_hash) || (source_hash.empty() && !file.optional))
{
command::execute("disconnect");
scheduler::once([]
{
connect(connect_state.host);
connect(server_connection_state.host);
}, scheduler::pipeline::main);
return;
}
@ -473,6 +522,12 @@ namespace party
hash_cache.clear();
current_sv_mapname = map;
if (game::environment::is_dedi())
{
generate_hashes(map);
}
utils::hook::invoke<void>(0x54BBB0_b, map, a2, a3, a4, a5);
}
@ -496,19 +551,14 @@ namespace party
line(current_sv_mapname);
line(sv_gametype->current.string);
const auto add_hash = [&](const std::string extension)
{
const auto filename = get_usermap_file_path(current_sv_mapname, extension);
const auto hash = get_file_hash(filename);
line(hash);
};
const auto is_usermap = fastfiles::usermap_exists(current_sv_mapname);
for (const auto& [ext, key, opt] : usermap_files)
for (const auto& file : usermap_files)
{
if (is_usermap)
{
add_hash(ext);
const auto filename = get_usermap_file_path(current_sv_mapname, file.extension);
const auto hash = get_file_hash(filename);
line(hash);
}
else
{
@ -557,7 +607,7 @@ namespace party
void clear_sv_motd()
{
party::sv_motd.clear();
server_connection_state.motd.clear();
}
int get_client_num_by_name(const std::string& name)
@ -579,9 +629,9 @@ namespace party
return -1;
}
void reset_connect_state()
void reset_server_connection_state()
{
connect_state = {};
server_connection_state = {};
}
int get_client_count()
@ -634,16 +684,11 @@ namespace party
command::execute("lui_open_popup popup_acceptinginvite", false);
connect_state.host = target;
connect_state.challenge = utils::cryptography::random::get_challenge();
connect_state.hostDefined = true;
server_connection_state.host = target;
server_connection_state.challenge = utils::cryptography::random::get_challenge();
server_connection_state.hostDefined = true;
network::send(target, "getInfo", connect_state.challenge);
}
game::netadr_s get_state_host()
{
return connect_state.host;
network::send(target, "getInfo", server_connection_state.challenge);
}
void start_map(const std::string& mapname, bool dev)
@ -705,9 +750,14 @@ namespace party
}
}
int server_client_count()
connection_state get_server_connection_state()
{
return party::sv_maxclients;
return server_connection_state;
}
std::optional<discord_information> get_server_discord_info()
{
return server_discord_info;
}
class component final : public component_interface
@ -720,7 +770,7 @@ namespace party
return;
}
// detour CL_Disconnect to clear motd
// clear motd & usermap
cl_disconnect_hook.create(0x12F080_b, cl_disconnect_stub);
if (game::environment::is_mp())
@ -792,7 +842,7 @@ namespace party
command::add("reconnect", [](const command::params& argument)
{
if (!connect_state.hostDefined)
if (!server_connection_state.hostDefined)
{
console::info("Cannot connect to server.\n");
return;
@ -805,7 +855,7 @@ namespace party
}
else
{
connect(connect_state.host);
connect(server_connection_state.host);
}
});
@ -908,8 +958,7 @@ namespace party
scheduler::once([]()
{
const auto hash = game::generateHashValue("sv_sayName");
game::Dvar_RegisterString(hash, "sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE);
sv_say_name = dvars::register_string("sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE, "Custom name for RCON console");
}, scheduler::pipeline::main);
command::add("tell", [](const command::params& params)
@ -921,7 +970,7 @@ namespace party
const auto client_num = atoi(params.get(1));
const auto message = params.join(2);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
const auto* const name = sv_say_name->current.string;
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
@ -951,7 +1000,7 @@ namespace party
}
const auto message = params.join(1);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
const auto* const name = sv_say_name->current.string;
game::SV_GameSendServerCommand(
-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
@ -972,6 +1021,17 @@ namespace party
console::info("%s\n", message.data());
});
command::add("hash", [](const command::params& params)
{
if (params.size() < 2)
{
return;
}
const auto hash = get_file_hash(params.get(1));
console::info("hash output: %s\n", hash.data());
});
network::on("getInfo", [](const game::netadr_s& target, const std::string& data)
{
const auto mapname = get_dvar_string("mapname");
@ -993,19 +1053,16 @@ namespace party
info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running") && !game::VirtualLobby_Loaded()));
info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
info.set("sv_wwwBaseUrl", get_dvar_string("sv_wwwBaseUrl"));
info.set("sv_discordImageUrl", get_dvar_string("sv_discordImageUrl"));
info.set("sv_discordImageText", get_dvar_string("sv_discordImageText"));
if (!fastfiles::is_stock_map(mapname))
{
const auto add_hash = [&](const std::string& extension, const std::string& name)
for (const auto& file : usermap_files)
{
const auto path = get_usermap_file_path(mapname, extension);
const auto path = get_usermap_file_path(mapname, file.extension);
const auto hash = get_file_hash(path);
info.set(name, hash);
};
for (const auto& [ext, name, opt] : usermap_files)
{
add_hash(ext, name);
info.set(file.name, hash);
}
}
@ -1014,8 +1071,12 @@ namespace party
if (!fs_game.empty())
{
const auto hash = get_file_hash(utils::string::va("%s/mod.ff", fs_game.data()));
info.set("modHash", hash);
for (const auto& file : mod_files)
{
const auto hash = get_file_hash(utils::string::va("%s/mod%s",
fs_game.data(), file.extension.data()));
info.set(file.name, hash);
}
}
network::send(target, "infoResponse", info.build(), '\n');
@ -1026,7 +1087,7 @@ namespace party
const utils::info_string info(data);
server_list::handle_info_response(target, info);
if (connect_state.host != target)
if (server_connection_state.host != target)
{
return;
}
@ -1035,7 +1096,14 @@ namespace party
saved_info_response.host = target;
saved_info_response.info_string = info;
if (info.get("challenge") != connect_state.challenge)
const auto protocol = info.get("protocol");
if (std::atoi(protocol.data()) != PROTOCOL)
{
menu_error("Connection failed: Invalid protocol.");
return;
}
if (info.get("challenge") != server_connection_state.challenge)
{
menu_error("Connection failed: Invalid challenge.");
return;
@ -1081,8 +1149,17 @@ namespace party
return;
}
party::sv_motd = info.get("sv_motd");
party::sv_maxclients = std::stoi(info.get("sv_maxclients"));
server_connection_state.motd = info.get("sv_motd");
server_connection_state.max_clients = std::stoi(info.get("sv_maxclients"));
server_connection_state.base_url = info.get("sv_wwwBaseUrl");
discord_information discord_info{};
discord_info.image = info.get("sv_discordImageUrl");
discord_info.image_text = info.get("sv_discordImageText");
if (!discord_info.image.empty() || !discord_info.image_text.empty())
{
server_discord_info.emplace(discord_info);
}
connect_to_party(target, mapname, gametype);
});
@ -1090,4 +1167,4 @@ namespace party
};
}
REGISTER_COMPONENT(party::component)
REGISTER_COMPONENT(party::component)

View File

@ -3,19 +3,34 @@
namespace party
{
std::string get_www_url();
struct connection_state
{
game::netadr_s host;
std::string challenge;
bool hostDefined;
std::string motd;
int max_clients;
std::string base_url;
};
struct discord_information
{
std::string image;
std::string image_text;
};
void user_download_response(bool response);
void menu_error(const std::string& error);
void reset_connect_state();
void reset_server_connection_state();
void connect(const game::netadr_s& target);
void start_map(const std::string& mapname, bool dev = false);
void clear_sv_motd();
game::netadr_s get_state_host();
int server_client_count();
connection_state get_server_connection_state();
std::optional<discord_information> get_server_discord_info();
int get_client_num_by_name(const std::string& name);

View File

@ -68,7 +68,7 @@ namespace patches
void cg_set_client_dvar_from_server_stub(void* clientNum, void* cgameGlob, const char* dvar_hash, const char* value)
{
int hash = atoi(dvar_hash);
const auto hash = std::atoi(dvar_hash);
auto* dvar = game::Dvar_FindMalleableVar(hash);
if (hash == game::generateHashValue("cg_fov") ||
@ -192,7 +192,7 @@ namespace patches
void sv_execute_client_message_stub(game::mp::client_t* client, game::msg_t* msg)
{
if (client->reliableAcknowledge < 0)
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
{
client->reliableAcknowledge = client->reliableSequence;
console::info("Negative reliableAcknowledge from %s - cl->reliableSequence is %i, reliableAcknowledge is %i\n",
@ -223,7 +223,7 @@ namespace patches
utils::hook::detour init_network_dvars_hook;
void init_network_dvars_stub(game::dvar_t* dvar)
{
static const auto hash = game::generateHashValue("r_tonemapHighlightRange");
constexpr auto hash = dvars::generate_hash("r_tonemapHighlightRange");
if (dvar->hash == hash)
{
init_network_dvars_hook.invoke<void>(dvar);
@ -244,26 +244,6 @@ namespace patches
}
}
int out_of_memory_text_stub(char* dest, int size, const char* fmt, ...)
{
fmt = "%s (%d)\n\n"
"Disable shader caching, lower graphic settings, free up RAM, or update your GPU drivers.\n\n"
"If this still occurs, try using the '-memoryfix' parameter to generate the 'players2' folder.";
char buffer[2048];
{
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
}
return utils::hook::invoke<int>(SELECT_VALUE(0x429200_b, 0x5AF0F0_b), dest, size, "%s", buffer);
}
void create_2d_texture_stub_1(const char* fmt, ...)
{
fmt = "Create2DTexture( %s, %i, %i, %i, %i ) failed\n\n"
@ -351,9 +331,18 @@ namespace patches
dvars::override::register_float("cg_fovScale", 1.f, 0.1f, 2.f, game::DvarFlags::DVAR_FLAG_SAVED);
dvars::override::register_float("cg_fovMin", 1.f, 1.0f, 90.f, game::DvarFlags::DVAR_FLAG_SAVED);
// Enable Marketing Comms
dvars::override::register_int("marketing_active", 1, 1, 1, game::DVAR_FLAG_WRITE);
// Makes com_maxfps saved dvar
dvars::override::register_int("com_maxfps", 0, 0, 1000, game::DVAR_FLAG_SAVED);
// Makes mis_cheat saved dvar
dvars::override::register_bool("mis_cheat", 0, game::DVAR_FLAG_SAVED);
// Fix speaker config bug
dvars::override::register_int("snd_detectedSpeakerConfig", 0, 0, 100, 0);
// Allow kbam input when gamepad is enabled
utils::hook::nop(SELECT_VALUE(0x1AC0CE_b, 0x135EFB_b), 2);
utils::hook::nop(SELECT_VALUE(0x1A9DDC_b, 0x13388F_b), 6);
@ -368,18 +357,6 @@ namespace patches
utils::hook::call(SELECT_VALUE(0x55E919_b, 0x681A69_b), create_2d_texture_stub_1); // Sys_Error for "Create2DTexture( %s, %i, %i, %i, %i ) failed"
utils::hook::call(SELECT_VALUE(0x55EACB_b, 0x681C1B_b), create_2d_texture_stub_2); // Com_Error for ^
utils::hook::call(SELECT_VALUE(0x5B35BA_b, 0x6CB1BC_b), swap_chain_stub); // Com_Error for "IDXGISwapChain::Present failed: %s"
utils::hook::call(SELECT_VALUE(0x457BC9_b, 0x1D8E09_b), out_of_memory_text_stub); // Com_sprintf for "Out of memory. You are probably low on disk space."
// "fix" for rare 'Out of memory error' error
// this will *at least* generate the configs for mp/sp, which is the #1 issue
if (utils::flags::has_flag("memoryfix"))
{
utils::hook::jump(SELECT_VALUE(0x5110D0_b, 0x6200C0_b), malloc);
utils::hook::jump(SELECT_VALUE(0x510FF0_b, 0x61FFE0_b), _aligned_malloc);
utils::hook::jump(SELECT_VALUE(0x511130_b, 0x620120_b), free);
utils::hook::jump(SELECT_VALUE(0x511220_b, 0x620210_b), realloc);
utils::hook::jump(SELECT_VALUE(0x511050_b, 0x620040_b), _aligned_realloc);
}
// Uncheat protect gamepad-related dvars
dvars::override::register_float("gpad_button_deadzone", 0.13f, 0, 1, game::DVAR_FLAG_SAVED);
@ -438,7 +415,7 @@ namespace patches
// prevent servers overriding our fov
utils::hook::nop(0x17DA96_b, 0x16);
utils::hook::nop(0xE00BE_b, 0x17);
utils::hook::set<uint8_t>(0x307F39_b, 0xEB);
utils::hook::nop(0x307F90_b, 0x5); // don't change cg_fov when toggling third person spectating
// make setclientdvar behave like older games
cg_set_client_dvar_from_server_hook.create(0x11AA90_b, cg_set_client_dvar_from_server_stub);
@ -474,8 +451,6 @@ namespace patches
dvars::override::register_bool("ui_drawCrosshair", true, game::DVAR_FLAG_WRITE);
utils::hook::jump(0x1E6010_b, ui_draw_crosshair);
dvars::override::register_int("com_maxfps", 0, 0, 1000, game::DVAR_FLAG_SAVED);
// Prevent clients from ending the game as non host by sending 'end_game' lui notification
cmd_lui_notify_server_hook.create(0x412D50_b, cmd_lui_notify_server_stub);
@ -497,4 +472,4 @@ namespace patches
};
}
REGISTER_COMPONENT(patches::component)
REGISTER_COMPONENT(patches::component)

View File

@ -21,10 +21,10 @@ namespace redirect
ZeroMemory(&process_info, sizeof(process_info));
startup_info.cb = sizeof(startup_info);
auto* arguments = const_cast<char*>(utils::string::va("%s%s%s", self.get_path().data(),
auto* arguments = const_cast<char*>(utils::string::va("%s%s%s", self.get_path().generic_string().data(),
(singleplayer ? " -singleplayer" : " -multiplayer"),
(mode.empty() ? "" : (" +"s + mode).data())));
CreateProcessA(self.get_path().data(), arguments, nullptr, nullptr, false, NULL, nullptr, nullptr,
CreateProcessA(self.get_path().generic_string().data(), arguments, nullptr, nullptr, false, NULL, nullptr, nullptr,
&startup_info, &process_info);
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)

View File

@ -132,6 +132,21 @@ namespace renderer
a.jmp(0x1C4136_b);
});
}
void r_preload_shaders_stub(utils::hook::assembler& a)
{
const auto is_zero = a.newLabel();
a.mov(rax, qword_ptr(SELECT_VALUE(0x123FFF30_b, 0x111DC230_b)));
a.test(rax, rax);
a.jz(is_zero);
a.mov(rcx, qword_ptr(rax, 0x540C68));
a.jmp(SELECT_VALUE(0x5CF1FF_b, 0x6E76FF_b));
a.bind(is_zero);
a.jmp(SELECT_VALUE(0x5CF20A_b, 0x6E7722_b));
}
}
class component final : public component_interface
@ -166,6 +181,10 @@ namespace renderer
r_use_custom_red_dot_brightness = dvars::register_bool("r_useCustomRedDotBrightness",
true, game::DVAR_FLAG_SAVED, "Use custom red-dot brightness values");
}
// patch r_preloadShaders crash at init
utils::hook::jump(SELECT_VALUE(0x5CF1F1_b, 0x6E76F1_b), utils::hook::assemble(r_preload_shaders_stub), true);
dvars::override::register_bool("r_preloadShaders", false, game::DVAR_FLAG_SAVED);
}
};
}

View File

@ -28,6 +28,7 @@ namespace scripting
utils::concurrency::container<shared_table_t> shared_table;
std::string current_file;
unsigned int current_file_id{};
namespace
{
@ -46,8 +47,7 @@ namespace scripting
utils::hook::detour db_find_xasset_header_hook;
std::string current_script_file;
unsigned int current_file_id{};
const char* current_script_file_name;
game::dvar_t* g_dump_scripts;
@ -63,7 +63,7 @@ namespace scripting
const auto* string = game::SL_ConvertToString(string_value);
if (string)
{
event e;
event e{};
e.name = string;
e.entity = notify_list_owner_id;
@ -97,20 +97,16 @@ namespace scripting
game::G_LogPrintf("InitGame\n");
lua::engine::start();
gsc::load_main_handles();
}
gsc::load_main_handles();
g_load_structs_hook.invoke<void>();
}
void scr_load_level_stub()
{
if (!game::VirtualLobby_Loaded())
{
gsc::load_init_handles();
}
gsc::load_init_handles();
scr_load_level_hook.invoke<void>();
}
@ -157,12 +153,13 @@ namespace scripting
void process_script_stub(const char* filename)
{
current_script_file = filename;
current_script_file_name = filename;
const auto file_id = atoi(filename);
if (file_id)
{
current_file_id = static_cast<std::uint16_t>(file_id);
current_file = scripting::get_token(current_file_id);
}
else
{
@ -176,14 +173,10 @@ namespace scripting
void add_function_sort(unsigned int id, const char* pos)
{
std::string filename = current_file;
if (current_file_id)
{
filename = scripting::get_token(current_file_id);
}
if (!script_function_table_sort.contains(filename))
{
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file.data(), false);
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file_name, false);
if (script)
{
const auto end = &script->bytecode[script->bytecodeLen];
@ -207,15 +200,7 @@ namespace scripting
{
add_function_sort(thread_name, code_pos);
if (current_file_id)
{
const auto name = get_token(current_file_id);
add_function(name, thread_name, code_pos);
}
else
{
add_function(current_file, thread_name, code_pos);
}
add_function(current_file, thread_name, code_pos);
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
}

View File

@ -13,6 +13,7 @@ namespace scripting
extern utils::concurrency::container<shared_table_t> shared_table;
extern std::string current_file;
extern unsigned int current_file_id;
void on_shutdown(const std::function<void(bool, bool)>& callback);
std::optional<std::string> get_canonical_string(const unsigned int id);

View File

@ -33,6 +33,7 @@ namespace server_list
std::string host_name;
std::string map_name;
std::string game_type;
std::string mod_name;
game::CodPlayMode play_mode;
char in_game;
game::netadr_s address;
@ -76,7 +77,7 @@ namespace server_list
server_list_page = 0;
}
party::reset_connect_state();
party::reset_server_connection_state();
if (get_master_server(master_state.address))
{
@ -131,39 +132,29 @@ namespace server_list
return "";
}
if (column == 0)
switch (column)
{
return servers[i].host_name.empty() ? "" : utils::string::va("%s", servers[i].host_name.data());
}
if (column == 1)
{
return servers[i].map_name.empty() ? "Unknown" : utils::string::va("%s", servers[i].map_name.data());
}
if (column == 2)
case 0:
return servers[i].host_name.empty() ? "" : servers[i].host_name.data();
case 1:
return servers[i].map_name.empty() ? "Unknown" : servers[i].map_name.data();
case 2:
{
const auto client_count = servers[i].clients - servers[i].bots;
return utils::string::va("%d/%d [%d]", client_count, servers[i].max_clients,
servers[i].clients);
}
if (column == 3)
{
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
}
if (column == 4)
{
return servers[i].game_type.empty() ? "" : utils::string::va("%i", servers[i].ping);
}
if (column == 5)
{
case 3:
return servers[i].game_type.empty() ? "" : servers[i].game_type.data();
case 4:
return servers[i].ping ? utils::string::va("%i", servers[i].ping) : "999";
case 5:
return servers[i].is_private ? "1" : "0";
case 6:
return servers[i].mod_name.empty() ? "" : servers[i].mod_name.data();
default:
return "";
}
return "";
}
void sort_serverlist()
@ -325,6 +316,13 @@ namespace server_list
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
{
// Don't show servers that aren't using the same protocol!
const auto protocol = std::atoi(info.get("protocol").data());
if (protocol != PROTOCOL)
{
return;
}
// Don't show servers that aren't dedicated!
const auto dedicated = std::atoi(info.get("dedicated").data());
if (!dedicated)
@ -372,6 +370,7 @@ namespace server_list
server.host_name = info.get("hostname");
server.map_name = game::UI_GetMapDisplayName(info.get("mapname").data());
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
server.mod_name = info.get("fs_game");
server.play_mode = playmode;
server.clients = atoi(info.get("clients").data());
server.max_clients = atoi(info.get("sv_maxclients").data());
@ -411,7 +410,7 @@ namespace server_list
scheduler::once([]()
{
// add dvars to change destination master server ip/port
master_server_ip = dvars::register_string("masterServerIP", "h1.fed0001.xyz", game::DVAR_FLAG_NONE,
master_server_ip = dvars::register_string("masterServerIP", "h1.fed.cat", game::DVAR_FLAG_NONE,
"IP of the destination master server to connect to");
master_server_port = dvars::register_string("masterServerPort", "20810", game::DVAR_FLAG_NONE,
"Port of the destination master server to connect to");

View File

@ -0,0 +1,84 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
namespace thirdperson
{
namespace
{
game::dvar_t* cg_thirdPerson = nullptr;
game::dvar_t* cg_thirdPersonRange = nullptr;
game::dvar_t* cg_thirdPersonAngle = nullptr;
namespace mp
{
__int64 sub_1D5950_stub([[maybe_unused]] int local_client_num, game::mp::cg_s* a2)
{
auto next_snap = a2->nextSnap;
if (next_snap->ps.pm_type < 7u)
{
int link_flags = next_snap->ps.linkFlags;
if ((link_flags & 2) == 0 && (next_snap->ps.otherFlags & 4) == 0)
{
auto client_globals = a2;
if (!client_globals->unk_979676 || !client_globals->unk_979696)
{
if (cg_thirdPerson && cg_thirdPerson->current.enabled)
{
return 1;
}
if (!(link_flags & (1 << 0xE)) || client_globals->unk_979696)
return (link_flags >> 27) & 1;
if (link_flags & (1 << 0x1D))
return 0;
if (!(link_flags & (1 << 0x1C)))
return a2->unk_601088;
}
}
}
return 1;
}
void sub_10C280_stub(int local_client_num, float angle, float range, int a4, int a5, int a6, int a7)
{
angle = cg_thirdPersonAngle->current.value;
range = cg_thirdPersonRange->current.value;
utils::hook::invoke<void>(0x10C280_b, local_client_num, angle, range, a4, a5, a6, a7);
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp())
{
return;
}
scheduler::once([]()
{
cg_thirdPerson = dvars::register_bool("cg_thirdPerson", 0, 4, "Use third person view");
cg_thirdPersonAngle = dvars::register_float("cg_thirdPersonAngle", 356.0f, -180.0f, 360.0f, 4,
"The angle of the camera from the player in third person view");
cg_thirdPersonRange = dvars::register_float("cg_thirdPersonRange", 120.0f, 0.0f, 1024, 4,
"The range of the camera from the player in third person view");
}, scheduler::main);
utils::hook::jump(0x1D5950_b, mp::sub_1D5950_stub);
utils::hook::call(0x10C26B_b, mp::sub_10C280_stub);
}
};
}
REGISTER_COMPONENT(thirdperson::component)

View File

@ -9,6 +9,7 @@
#include "localized_strings.hpp"
#include "console.hpp"
#include "discord.hpp"
#include "download.hpp"
#include "game_module.hpp"
#include "fps.hpp"
@ -33,6 +34,8 @@
#include "steam/steam.hpp"
#include <discord_rpc.h>
namespace ui_scripting
{
namespace
@ -318,7 +321,7 @@ namespace ui_scripting
game_type["virtuallobbypresentable"] = [](const game&)
{
::game::Dvar_SetFromStringByNameFromSource("virtualLobbyPresentable", "1", ::game::DvarSetSource::DVAR_SOURCE_INTERNAL);
::game::Dvar_SetFromStringByNameFromSource("virtualLobbyPresentable", "1", ::game::DVAR_SOURCE_INTERNAL);
};
game_type["getcurrentgamelanguage"] = [](const game&)
@ -332,6 +335,29 @@ namespace ui_scripting
material.data()));
};
game_type["getcommandbind"] = [](const game&, const std::string& cmd)
{
const auto binding = ::game::Key_GetBindingForCmd(cmd.data());
auto key = -1;
for (auto i = 0; i < 256; i++)
{
if (::game::playerKeys[0].keys[i].binding == binding)
{
key = i;
}
}
if (key == -1)
{
return ::game::UI_SafeTranslateString("KEY_UNBOUND");
}
else
{
const auto loc_string = ::game::Key_KeynumToString(key, 1, 0);
return ::game::UI_SafeTranslateString(loc_string);
}
};
auto server_list_table = table();
lua["serverlist"] = server_list_table;
@ -367,7 +393,29 @@ namespace ui_scripting
download_table["abort"] = download::stop_download;
download_table["userdownloadresponse"] = party::user_download_response;
download_table["getwwwurl"] = party::get_www_url;
download_table["getwwwurl"] = party::get_server_connection_state().base_url;
auto discord_table = table();
lua["discord"] = discord_table;
discord_table["respond"] = discord::respond;
discord_table["getavatarmaterial"] = [](const std::string& id)
-> script_value
{
const auto material = discord::get_avatar_material(id);
if (material == nullptr)
{
return {};
}
return lightuserdata(material);
};
discord_table["reply"] = table();
discord_table["reply"]["yes"] = DISCORD_REPLY_YES;
discord_table["reply"]["ignore"] = DISCORD_REPLY_IGNORE;
discord_table["reply"]["no"] = DISCORD_REPLY_NO;
}
void start()
@ -379,7 +427,26 @@ namespace ui_scripting
setup_functions();
lua["print"] = function(reinterpret_cast<game::hks::lua_function>(SELECT_VALUE(0x93490_b, 0x209EB0_b)));
lua["print"] = [](const variadic_args& va)
{
std::string buffer{};
const auto to_string = get_globals()["tostring"];
for (auto i = 0; i < va.size(); i++)
{
const auto& arg = va[i];
const auto str = to_string(arg)[0].as<std::string>();
buffer.append(str);
if (i < va.size() - 1)
{
buffer.append("\t");
}
}
console::info("%s\n", buffer.data());
};
lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua;
@ -553,6 +620,8 @@ namespace ui_scripting
return;
}
dvars::register_bool("r_preloadShadersFrontendAllow", true, game::DVAR_FLAG_SAVED, "Allow shader popup on startup");
utils::hook::call(SELECT_VALUE(0xE7419_b, 0x25E809_b), db_find_x_asset_header_stub);
utils::hook::call(SELECT_VALUE(0xE72CB_b, 0x25E6BB_b), db_find_x_asset_header_stub);

View File

@ -14,13 +14,12 @@
#include <utils/concurrency.hpp>
#include <utils/cryptography.hpp>
#include <utils/http.hpp>
#include <utils/io.hpp>
#include <utils/nt.hpp>
#include <utils/properties.hpp>
#include <utils/string.hpp>
#define MASTER "https://master.fed0001.xyz/h1-mod/"
#define MASTER "https://h1-mod.fed.cat/"
#define FILES_PATH "files.json"
#define FILES_PATH_DEV "files-dev.json"
@ -161,10 +160,17 @@ namespace updater
{
return utils::string::va("%i", uint32_t(time(nullptr)));
}
std::optional<utils::http::result> download_file(const std::string& name)
std::optional<utils::http::result> download_data_file(const std::string& name)
{
return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name + "?" + get_time_str());
const auto file = std::format("{}{}?{}", select(DATA_PATH, DATA_PATH_DEV), name, get_time_str());
return updater::get_server_file(file);
}
std::optional<utils::http::result> download_file_list()
{
const auto file = std::format("{}?{}", select(FILES_PATH, FILES_PATH_DEV), get_time_str());
return updater::get_server_file(file);
}
bool has_old_data_files()
@ -301,6 +307,34 @@ namespace updater
}
}
std::optional<utils::http::result> get_server_file(const std::string& endpoint)
{
static std::vector<std::string> server_urls =
{
{"https://h1-mod.fed.cat/"},
{"https://master.fed0001.xyz/h1-mod/"}, // remove this at some point
};
const auto try_url = [&](const std::string& base_url)
{
const auto url = base_url + endpoint;
console::debug("[HTTP] GET file \"%s\"\n", url.data());
const auto result = utils::http::get_data(url);
return result;
};
for (const auto& url : server_urls)
{
const auto result = try_url(url);
if (result.has_value())
{
return result;
}
}
return {};
}
void relaunch()
{
const auto mode = game::environment::is_mp() ? "-multiplayer" : "-singleplayer";
@ -406,7 +440,7 @@ namespace updater
scheduler::once([]()
{
const auto files_data = utils::http::get_data(MASTER + select(FILES_PATH, FILES_PATH_DEV) + "?" + get_time_str());
const auto files_data = download_file_list();
if (is_update_cancelled())
{
@ -546,7 +580,7 @@ namespace updater
console::debug("[Updater] downloading file %s\n", file.data());
const auto data = download_file(file);
const auto data = download_data_file(file);
if (is_update_cancelled())
{

View File

@ -2,8 +2,12 @@
#define CLIENT_DATA_FOLDER "cdata"
#include <utils/http.hpp>
namespace updater
{
std::optional<utils::http::result> get_server_file(const std::string& endpoint);
void relaunch();
void set_has_tried_update(bool tried);

View File

@ -6,8 +6,10 @@
#include "fastfiles.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
namespace weapon
{
@ -37,27 +39,38 @@ namespace weapon
// precache items
for (std::size_t i = 0; i < weapons.size(); i++)
{
console::debug("precaching weapon \"%s\"\n", weapons[i]->name);
//console::debug("precaching weapon \"%s\"\n", weapons[i]->name);
game::G_GetWeaponForName(weapons[i]->name);
}
}
utils::hook::detour xmodel_get_bone_index_hook;
__int64 xmodel_get_bone_index_stub(game::XModel* model, game::scr_string_t name, unsigned int offset, char* index)
int xmodel_get_bone_index_stub(game::XModel* model, game::scr_string_t name, unsigned int offset, char* index)
{
auto result = xmodel_get_bone_index_hook.invoke<__int64>(model, name, offset, index);
if (!result)
auto result = xmodel_get_bone_index_hook.invoke<int>(model, name, offset, index);
if (result)
{
if (name == game::SL_GetString("tag_weapon_right", 0) ||
name == game::SL_GetString("tag_knife_attach", 0))
return result;
}
const auto original_index = *index;
const auto original_result = result;
if (name == game::SL_FindString("tag_weapon_right") ||
name == game::SL_FindString("tag_knife_attach"))
{
const auto tag_weapon = game::SL_FindString("tag_weapon");
result = xmodel_get_bone_index_hook.invoke<int>(model, tag_weapon, offset, index);
if (result)
{
result = xmodel_get_bone_index_hook.invoke<__int64>(model, game::SL_GetString("tag_weapon", 0), offset, index);
if (result)
{
console::debug("using tag_weapon instead of %s (%s, %d)\n", game::SL_ConvertToString(name), model->name, offset);
}
console::debug("using tag_weapon instead of %s (%s, %d, %d)\n", game::SL_ConvertToString(name), model->name, offset, *index);
return result;
}
}
*index = original_index;
result = original_result;
return result;
}
@ -116,6 +129,131 @@ namespace weapon
{
set_weapon_field<bool>(weapon_name, field, value);
}
int compare_hash(const void* a, const void* b)
{
const auto hash_a = reinterpret_cast<game::DDLHash*>(
reinterpret_cast<size_t>(a))->hash;
const auto hash_b = reinterpret_cast<game::DDLHash*>(
reinterpret_cast<size_t>(b))->hash;
if (hash_a < hash_b)
{
return -1;
}
else if (hash_a > hash_b)
{
return 1;
}
return 0;
}
utils::memory::allocator ddl_allocator;
std::unordered_set<void*> modified_enums;
std::vector<const char*> get_stringtable_entries(const std::string& name)
{
std::vector<const char*> entries;
const auto string_table = game::DB_FindXAssetHeader(
game::ASSET_TYPE_STRINGTABLE, name.data(), false).stringTable;
if (string_table == nullptr)
{
return entries;
}
for (auto row = 0; row < string_table->rowCount; row++)
{
if (string_table->columnCount <= 0)
{
continue;
}
const auto index = (row * string_table->columnCount);
const auto weapon = string_table->values[index].string;
entries.push_back(ddl_allocator.duplicate_string(weapon));
}
return entries;
}
void add_entries_to_enum(game::DDLEnum* enum_, const std::vector<const char*> entries)
{
if (entries.size() <= 0)
{
return;
}
const auto new_size = enum_->memberCount + entries.size();
const auto members = ddl_allocator.allocate_array<const char*>(new_size);
const auto hash_list = ddl_allocator.allocate_array<game::DDLHash>(new_size);
std::memcpy(members, enum_->members, 8 * enum_->memberCount);
std::memcpy(hash_list, enum_->hashTable.list, 8 * enum_->hashTable.count);
for (auto i = 0; i < entries.size(); i++)
{
const auto hash = utils::hook::invoke<unsigned int>(0x794FB0_b, entries[i], 0);
const auto index = enum_->memberCount + i;
hash_list[index].index = index;
hash_list[index].hash = hash;
members[index] = entries[i];
}
std::qsort(hash_list, new_size, sizeof(game::DDLHash), compare_hash);
enum_->members = members;
enum_->hashTable.list = hash_list;
enum_->memberCount = static_cast<int>(new_size);
enum_->hashTable.count = static_cast<int>(new_size);
}
void load_ddl_asset_stub(game::DDLRoot** asset)
{
const auto root = *asset;
if (!root->ddlDef)
{
return utils::hook::invoke<void>(0x39BE20_b, root);
}
auto ddl_def = root->ddlDef;
while (ddl_def)
{
for (auto i = 0; i < ddl_def->enumCount; i++)
{
const auto enum_ = &ddl_def->enumList[i];
if (modified_enums.contains(enum_))
{
continue;
}
if ((enum_->name == "WeaponStats"s || enum_->name == "Weapon"s))
{
const auto weapons = get_stringtable_entries("mp/customweapons.csv");
add_entries_to_enum(enum_, weapons);
modified_enums.insert(enum_);
}
if (enum_->name == "AttachmentBase"s)
{
const auto attachments = get_stringtable_entries("mp/customattachments.csv");
add_entries_to_enum(enum_, attachments);
modified_enums.insert(enum_);
}
}
ddl_def = ddl_def->next;
}
utils::hook::invoke<void>(0x39BE20_b, asset);
}
}
void clear_modifed_enums()
{
modified_enums.clear();
}
class component final : public component_interface
@ -137,6 +275,11 @@ namespace weapon
// patch attachment configstring so it will create if not found
utils::hook::call(0x41C595_b, g_find_config_string_index_stub);
utils::hook::call(0x36B4D4_b, load_ddl_asset_stub);
dvars::register_bool("sv_disableCustomClasses",
false, game::DVAR_FLAG_REPLICATED, "Disable custom classes on server");
}
#ifdef DEBUG

View File

@ -0,0 +1,6 @@
#pragma once
namespace weapon
{
void clear_modifed_enums();
}

Some files were not shown because too many files have changed in this diff Show More