Merge branch 'develop'
This commit is contained in:
commit
a40cd645b0
32
.github/workflows/build.yml
vendored
32
.github/workflows/build.yml
vendored
@ -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
4
.gitmodules
vendored
@ -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
|
||||
|
14
README.md
14
README.md
@ -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
|
||||
|
||||
|
34
data/cdata/scripts/mp/classes.gsc
Normal file
34
data/cdata/scripts/mp/classes.gsc
Normal 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;
|
||||
}
|
383
data/cdata/scripts/mp_patches/custom_weapons.gsc
Normal file
383
data/cdata/scripts/mp_patches/custom_weapons.gsc
Normal 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;
|
||||
}
|
242
data/cdata/scripts/sp/battlechatter_patch.gsc
Normal file
242
data/cdata/scripts/sp/battlechatter_patch.gsc
Normal 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" );
|
||||
}
|
13
data/cdata/ui_scripts/classes/__init__.lua
Normal file
13
data/cdata/ui_scripts/classes/__init__.lua
Normal 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
|
26
data/cdata/ui_scripts/custom_weapons/__init__.lua
Normal file
26
data/cdata/ui_scripts/custom_weapons/__init__.lua
Normal 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
|
@ -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)
|
||||
|
@ -1,5 +1,6 @@
|
||||
require("language")
|
||||
require("background_effects")
|
||||
require("pausequit")
|
||||
|
||||
if game:issingleplayer() then
|
||||
require("sp_unlockall")
|
||||
|
@ -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)
|
||||
|
32
data/cdata/ui_scripts/patches/pausequit.lua
Normal file
32
data/cdata/ui_scripts/patches/pausequit.lua
Normal 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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
}, {
|
||||
|
@ -1,2 +1,3 @@
|
||||
localize,english
|
||||
ttf,fonts/bank_h1.ttf
|
||||
ttf,fonts/default.otf
|
|
@ -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..."
|
||||
}
|
BIN
data/zonetool/h1_mod_common/fonts/bank_h1.ttf
Normal file
BIN
data/zonetool/h1_mod_common/fonts/bank_h1.ttf
Normal file
Binary file not shown.
Binary file not shown.
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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."
|
||||
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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": "アンロックをキャンセル"
|
||||
|
@ -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": "잠금 해제 취소"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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": "取消解锁"
|
||||
|
@ -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"
|
||||
|
@ -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": "取消解鎖"
|
||||
|
@ -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
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit 6c6111acb7b5d687ac006969ac96e5b1f21374cd
|
||||
Subproject commit e64c97fc2cfc11992098bb38eda932de275e3f4d
|
2
deps/asmjit
vendored
2
deps/asmjit
vendored
@ -1 +1 @@
|
||||
Subproject commit 5b5b0b38775938df4d3779604ff1db60b9a9dcbf
|
||||
Subproject commit 7c10a14d347879f889c6d11a9398f1d453acc690
|
2
deps/curl
vendored
2
deps/curl
vendored
@ -1 +1 @@
|
||||
Subproject commit 4ab601d93a07cee665ec2458a51fccd0767c03f1
|
||||
Subproject commit 102de7aa8d5bfc6ed5fe85e89c7b943d0c186f03
|
2
deps/discord-rpc
vendored
2
deps/discord-rpc
vendored
@ -1 +1 @@
|
||||
Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33
|
||||
Subproject commit b3383798b353c31ea6770fee673740c27f6e3489
|
30
deps/extra/gsc-tool/interface.cpp
vendored
30
deps/extra/gsc-tool/interface.cpp
vendored
@ -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>();
|
||||
}
|
||||
}
|
9
deps/extra/gsc-tool/interface.hpp
vendored
9
deps/extra/gsc-tool/interface.hpp
vendored
@ -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
2
deps/gsc-tool
vendored
@ -1 +1 @@
|
||||
Subproject commit 7d374025b7675bada64c247ebe9378dd335a33da
|
||||
Subproject commit cbfcce1dd67534c2115331e41d4cb6893e96196c
|
2
deps/json
vendored
2
deps/json
vendored
@ -1 +1 @@
|
||||
Subproject commit 4c6cde72e533158e044252718c013a48bcff346c
|
||||
Subproject commit a259ecc51e1951e12f757ce17db958e9881e9c6c
|
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4
|
||||
Subproject commit 7e863d21429f94ed6a720e24499a12a3f852bb31
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
||||
Subproject commit 03de03dee753442d4b23166982514639c4ccbc39
|
||||
Subproject commit 8314bde5e5c8e5d9331460130a9d1066e324f091
|
2
deps/lua
vendored
2
deps/lua
vendored
@ -1 +1 @@
|
||||
Subproject commit be908a7d4d8130264ad67c5789169769f824c5d1
|
||||
Subproject commit 7923dbbf72da303ca1cca17efd24725668992f15
|
2
deps/minhook
vendored
2
deps/minhook
vendored
@ -1 +1 @@
|
||||
Subproject commit 49d03ad118cf7f6768c79a8f187e14b8f2a07f94
|
||||
Subproject commit f5485b8454544c2f034c78f8f127c1d03dea3636
|
76
deps/premake/gsc-tool.lua
vendored
76
deps/premake/gsc-tool.lua
vendored
@ -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
2
deps/protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit 7ce9c415455c098409222702b3b4572b47232882
|
||||
Subproject commit 5a3dac894157bf3618b2c906a8b9073b4cad62b6
|
2
deps/rapidjson
vendored
2
deps/rapidjson
vendored
@ -1 +1 @@
|
||||
Subproject commit a98e99992bd633a2736cc41f96ec85ef0c50e44d
|
||||
Subproject commit 6089180ecb704cb2b136777798fa1be303618975
|
2
deps/sol2
vendored
2
deps/sol2
vendored
@ -1 +1 @@
|
||||
Subproject commit f81643aa0c0c507c0cd8400b8cfedc74a34a19f6
|
||||
Subproject commit 9c882a28fdb6f4ad79a53a4191b43ce48a661175
|
2
deps/stb
vendored
2
deps/stb
vendored
@ -1 +1 @@
|
||||
Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55
|
||||
Subproject commit f4a71b13373436a2866c5d68f8f80ac6f0bc1ffe
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
||||
Subproject commit e554695638228b846d49657f31eeff0ca4680e8a
|
||||
Subproject commit 643e17b7498d12ab8d15565662880579692f769d
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
30
src/client/component/experimental.cpp
Normal file
30
src/client/component/experimental.cpp
Normal 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
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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_);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
|
@ -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{};
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
230
src/client/component/imagefiles.cpp
Normal file
230
src/client/component/imagefiles.cpp
Normal 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)
|
7
src/client/component/imagefiles.hpp
Normal file
7
src/client/component/imagefiles.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace imagefiles
|
||||
{
|
||||
void close_custom_handles();
|
||||
void close_handle(const std::string& fastfile);
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
34
src/client/component/map_rotation.hpp
Normal file
34
src/client/component/map_rotation.hpp
Normal 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_;
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
143
src/client/component/memory.cpp
Normal file
143
src/client/component/memory.cpp
Normal 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)
|
6
src/client/component/memory.hpp
Normal file
6
src/client/component/memory.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace memory
|
||||
{
|
||||
constexpr auto custom_script_mem_size = 0x1000000ui64;
|
||||
}
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
37
src/client/component/motd.cpp
Normal file
37
src/client/component/motd.cpp
Normal 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)
|
@ -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{};
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
84
src/client/component/thirdperson.cpp
Normal file
84
src/client/component/thirdperson.cpp
Normal 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)
|
@ -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);
|
||||
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
6
src/client/component/weapon.hpp
Normal file
6
src/client/component/weapon.hpp
Normal 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
Loading…
Reference in New Issue
Block a user