Merge pull request #62 from XLabsProject/develop

0.6.1 Release
This commit is contained in:
Dss0 2020-12-23 23:36:49 +01:00 committed by GitHub
commit 3b6fa9b6d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 6699 additions and 1355 deletions

36
.github/labeler.yml vendored Normal file
View File

@ -0,0 +1,36 @@
"part: game client":
- src/Components/Modules/Stats*
"part: game server":
- src/Components/Modules/Dedicated*
"part: zonebuilder":
- src/Components/Modules/ZoneBuilder*
"area: menus":
- src/Components/Modules/Menus*
"area: anticheat":
- src/Components/Modules/AntiCheat*
"area: serverlist":
- src/Components/Modules/ServerList*
"area: weapons":
- src/Components/Modules/Weapon*
"area: networking":
- src/Components/Modules/Auth*
- src/Components/Modules/Network*
- src/Components/Modules/Node*
- src/Components/Modules/PlayerName*
- src/Components/Modules/RCon*
- src/Components/Modules/Session*
"area: continuous integration":
- "*appveyor*"
- ".github/workflows/**"
- Jenkinsfile
"status: work in progress":
- "**"

11
.github/workflows/labeler.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: "Pull Request Labeler"
on:
- pull_request
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

1
.gitignore vendored
View File

@ -143,6 +143,7 @@ UpgradeLog*.htm
*.til
*.idb
*.i64
ida/*
### Custom user files
# User scripts

5
.gitmodules vendored
View File

@ -25,7 +25,10 @@
[submodule "deps/protobuf"]
path = deps/protobuf
url = https://github.com/google/protobuf.git
branch = 3.6.x
branch = 3.11.x
[submodule "deps/udis86"]
path = deps/udis86
url = https://github.com/vmt/udis86.git
[submodule "deps/dxsdk"]
path = deps/dxsdk
url = https://github.com/devKlausS/dxsdk.git

View File

@ -1,403 +1,450 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.6.0] - 2018-12-30
### Added
- Implement unbanclient command.
- Implement /serverlist api endpoint on dedicated servers
- Add dvar to control the server query rate (net_serverQueryLimit & net_serverFrames)
### Changed
- Update dependencies
### Fixed
- Fix mods not working in private matches.
- Fix multiple game structures (map tools)
- Fix multiple vulnerability's
- Fix lag spikes on lower end PCs
- Fix invalid name check
- Fix openLink command crash issue
- Fix lobby server map downloading
- Fix steam integration
### Known issues
- HTTPS is not supported for fast downloads at the moment.
## [0.5.4] - 2017-07-09
### Added
- Integrate IW4MVM by luckyy.
### Changed
- Displayed stats for Dragunov have been changed, has no effect on actual game play.
### Fixed
- Fix fast download failing when the target host is missing a trailing slash.
- Fix servers not being listed in the server browser.
- Fix some FPS drop issues caused by compression code.
### Known issues
- HTTPS is not supported for fast downloads at the moment.
## [0.5.3] - 2017-07-02
### Added
- Increase webserver security.
### Fixed
- Reduce lags introduced by nodes.
- Fix modlist download.
## [0.5.2] - 2017-07-02
### Fixed
- Fix dedicated server crash.
## [0.5.1] - 2017-07-02
### Added
- Add fast download option for custom mods/maps based on Call of Duty 4.
- Display a toast when an update is available.
- Use the hourglass cursor while loading assets (with the native cursor feature).
- Show bots in parenthesis after the number of players in the serverlist (request).
- Add GSC event `level waittill("say", string, player);` (request).
- Restrict unauthorized mod download for password protected servers.
- Add OMA support for 15 classes.
### Changed
- Show friend avatars when they play IW4x (request).
- Rewrite and optimize the entire node system.
- Remove syncnode command for node system.
- Remove steam start.
### Fixed
- Fix lags and frame drops caused by server sorting.
- Fix demos on custom maps.
- Can no longer join a lobby or server with an incorrect password.
- Fix crashes caused by a bug in file/data compression.
- Improve overall stability.
## [0.5.0] - 2017-06-04
### Added
- Add GSC functionality to download files via HTTP(S) (request).
- Implement preliminary custom map support.
### Changed
- Add new nicknames for bots.
- Bumped Fastfile version. If you built fastfiles with the zone builder (e.g. mod.ff) you have to rebuild them!
- Default `sv_network_fps` to `1000` on dedicated game servers.
- Increase maximum FOV to 120.
### Fixed
- Fix `iw4x_onelog` dvar.
- Fix server list only showing local servers by default instead of Internet servers.
- Fix some deadlock situations on game shutdown.
## [0.4.2] - 2017-03-16
### Changed
- Disable unnecessary dvar update in server browser.
- Update bot names.
### Fixed
- Fix process permissions.
- Fix classic AK-47 color bleedthrough.
- Re-aligned the MG4's Raffica iron sights.
## [0.4.1] - 2017-03-10
### Fixed
- Fix command line argument parsing.
## [0.4.0] - 2017-03-10
### Added
- Set played with status.
- Add support for 15 classes.
- Add `iw4x_onelog` dvar.
- Add show server/playercount in server browser.
### Changed
- Do not show friend status notifications with stream friendly UI.
- Reduce loaded modules for dedis.
- Use joystick emblem for friend status.
- Disable XP bar when max level.
- Change fs_game display postition.
- Replace Painkiller with Juiced from IW5.
### Fixed
- Fix AK weaponfiles.
- Fix brightness slider.
- Fix text length for column mod in server browser.
- Changed the L86 LSW to use the correct HUD icon.
- Re-aligned the M93 Raffica's iron sights.
- Re-aligned the Desert Eagle's iron sights.
## [0.3.3] - 2017-02-14
### Added
- Add mapname to friend status (request).
- Add option to toggle notify friend state.
- Add support for mod.ff.
### Changed
- Disabled big minidump message box.
- Limit dedicated servers to 15 instances per IP.
- Move build number location.
- Remove news ticker and friends button from theater.
### Fixed
- Fix audio bug in options menu.
- Fix crash caused by faulty file download requests to game hosts.
- Fix friend sorting.
- Fix game not starting issue under certain circumstances.
- Fix menu crash.
- Fix typo in security increase popmenu.
- Fix vid_restart crash with connect protocol.
- Fix weapon crash issue.
- Potentially fix no-ammo bug.
## [0.3.2] - 2017-02-12
This is the third public Beta version.
### Added
- Add working friend system.
- Add colored pings in the server list.
- Add credits.
- Add first launch menu.
- Add AK-47 (Classic) attachments.
- Add HUD icon for night vision goggles.
### Changed
- Join when pressing enter in server list (request).
- Redesign and refactor all fullscreen menus.
- Increase weapon and configstring limit.
- Allow creating full crash dumps if wanted.
- Set default name from steam.
### Fixed
- Fix missing models on village.
- Fix custom server motd (request).
- Fix various memory leaks.
- Fix mouse pitch (request).
- Fix compatibility with B3 (request).
- Fix RCon bug (request).
- Fix dedicated server crash on linux.
- Fix crash in mod download.
- Fix peacekeeper reload sound.
- Fix cl_maxpackets 125 in settings (request).
- Fix deserteaglegold_mp icon.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.3.1] - 2017-01-21
This is the second public Beta version.
### Added
- Add classic AK-47 to CAC.
- Add servers to favorites when ingame.
- Add delete favorites button in the serverlist.
### Changed
- Change maplist to a dynamic list.
### Fixed
- Fix list focus.
- Fix mod restart loop.
- Fix mod download status.
- Fix modelsurf crash.
- Fix floating AK-74u.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.3.0] - 2017-01-14
This is the first public Beta version.
### Added
- Add com_logFilter dvar.
- Add peacekeeper.
- Add support for maps from DLC 8 (Recycled Pack)
- Chemical Plant (mp_storm_spring)
- Crash: Tropical (mp_crash_tropical)
- Estate: Tropical (mp_estate_tropical)
- Favela: Tropical (mp_fav_tropical)
- Forgotten City (mp_bloc_sh)
### Changed
- Improve node synchronization handling.
- Improve security by modifying GUIDs to allow 64-bit certificate fingerprints.
- Optimize fastfiles, they are now a lot smaller.
- Replace intro.
### Fixed
- Fix concurrent image loading bug.
- Fix issues when spawning more than one bot.
- Fix no ammo bug.
- Fix server crash on startup.
- Fix splash screen hang.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.2.1] - 2016-12-14
This is the second public Alpha version.
### Added
- Add support for CoD:Online maps.
- Firing Range (mp_firingrange)
- Rust (mp_rust_long)
- Shipment (mp_shipment/mp_shipment_long)
- Add `sv_motd` Dvar for server owners to set custom motd (will be visible in the loadscreen).
- Add Zonebuilder support for sounds and fx.
- Add command setviewpos.
- Add high-definition loadscreens.
### Changed
- Rename Arctic Wet Work map to it's official name (Freighter).
- Complete redesign of the main menus.
- Allow `cl_maxpackets` to be set up to 125.
### Fixed
- Fix crash when using the Harrier killstreak.
- Disable code that downloads news/changelog when in zonebuilder mode.
- Fix freeze on game shutdown.
- Disable unlockstats while ingame to prevent a crash.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.2.0] - 2016-10-09
This is the first public Alpha version.
### Added
- Support for CoD:Online maps.
- Arctic Wet Work (mp_cargoship_sh)
- Bloc (mp_bloc)
- Bog (mp_bog_sh)
- Crossfire (mp_cross_fire)
- Killhouse (mp_killhouse)
- Nuketown (mp_nuked)
- Wet Work (mp_cargoship)
### Fixed
- Fix techniques in Zonebuilder.
- Fix possible memory leak.
- Fix timeout bug when connecting to server via iw4x link.
- Partially fix deadlock in decentralized networking code.
### Known issues
- Running IW4x on Linux currently requires gnutls to be installed additional to Wine as it needs to access the Tor service via HTTPS.
## [0.1.1] - 2016-09-19
This version is an internal Pre-Alpha version.
### Added
- Add IW5 material embedding system.
### Changed
- Enhance mod download with detailed progress display.
### Fixed
- Fix and optimize network logging commands.
- Fix console not displaying command inputs properly.
- Fix crash when running multiple instances of the IW4x client from the same directory.
- Fix crash when the `securityLevel` command is used on a game server.
- Fix possible data corruption in code for decentralized networking.
- Fix possible deadlock during client shutdown.
## [0.1.0] - 2016-09-03
This version is an internal Pre-Alpha version.
### Added
- Add `banclient` command which will permanently ban a client from a server. The ban will persist across restarts.
- Add capabilities to save played games and replay them ("Theater").
- Add code for generating and sending minidumps for debugging purposes. This feature is meant to be used only during the Open Beta and will be removed once the code goes to stable release.
- Add commands that allow forwarding console and games log via UDP to other computers ("network logging").
- Add D3D9Ex.
- Add filters for server list.
- Add handling for `iw4x://` URLs ("connect protocol"). For example, if IW4x is properly registered in Windows as URL handler for `iw4x://` URLs you can type `iw4x://ip:port`. If possible, this will connect to the server in an already running IW4x client.
- Add lean support through new key bindings.
- Add native cursor as replacement for the sometimes laggy in-game cursor. This change can be reverted in the settings menu.
- Add news ticker.
- Add remote console ("RCon").
- Add support for BigBrotherBot.
- Add support for hosting game mods in order to allow players to just join modded servers out of the box ("mod download").
- Add Warfare2 text coloring.
- Add zone builder. For more information see the respective documentation.
- Implement a completely decentralized peering network.
- Implement playlists which can be used for flexible map and gametype rotation.
- Introduce security levels. This ensures that you need to "pay" with CPU power to verify your identity once before being able to join a server which reduces the interval at which people who get banned can circumvent server bans through using new identities. The default security level is 23.
- Move IW4x resource files into their own folder to prevent clogging up the main game directories.
- Reintroduce parties, now also available for dedicated servers ("lobby servers").
### Changed
- Move logs to `userraw` folder.
- Replace main menu background music.
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.6.1] - 2020-12-23
### Added
- Add host information to /info endpoint (request)
- Add fileWrite GSC Function (#36)
- Add fileRead GSC Function (#36)
- Add fileExists GSC Function (#36)
- Add fileRemove GSC Function (#36)
- Add botMovement GSC Function (#46)
- Add botAction GSC Function (#46)
- Add botWeapon GSC Function (#46)
- Add botStop GSC Function (#46)
- Add isBot GSC Function (#46)
- Add setPing GSC Function (#46)
- Add GetSystemTime and GetSystemTimeMilliseconds GSC Functions (#46)
- Add PrintConsole GSC Function (#46)
- Add Exec GSC Function (#46)
- Add getIP GSC Method (#36)
- Add getPing GSC Method (#36)
- Add scr_intermissionTime GSC Function (#25)
- Add g_playerCollision Dvar (#36)
- Add g_playerEjection Dvar (#36)
- Add r_specularCustomMaps Dvar (#36)
- Unlock safeArea_horizontal and safeArea_vertical Dvars (#42)
- Unlock cg_fovscale Dvar (#47)
- Added g_antilag Dvar (#61)
### Changed
- Stats are now separate for each mod (#6). Player stats are copied to `fs_game` folder if no stats exist for this mod yet. Keep in mind this also means that level, XP and classes will not be synchronized with the main stats file after this point.
- Reduced duration of toasts (#48)
- Removed old updater functionality (#54)
- Use old bot names if bots.txt is not found (#46)
### Fixed
- Fixed a node system related crash (#45)
- Fixed an issue that made dedicated servers crash when info was requested during map rotation (#43)
- Fixed an issue where the game was trying to decrypt gsc files which caused it to crash when loading mods (#35)
- Fixed an issue causing the game to crash when Steam was running in the background (#56)
- Fixed slow download speed when using fast download
### Known issues
- HTTPS is not supported for fast downloads at the moment.
## [0.6.0] - 2018-12-30
### Added
- Implement unbanclient command.
- Implement /serverlist api endpoint on dedicated servers
- Add dvar to control the server query rate (net_serverQueryLimit & net_serverFrames)
### Changed
- Update dependencies
### Fixed
- Fix mods not working in private matches.
- Fix multiple game structures (map tools)
- Fix multiple vulnerability's
- Fix lag spikes on lower end PCs
- Fix invalid name check
- Fix openLink command crash issue
- Fix lobby server map downloading
- Fix steam integration
### Known issues
- HTTPS is not supported for fast downloads at the moment.
## [0.5.4] - 2017-07-09
### Added
- Integrate IW4MVM by luckyy.
### Changed
- Displayed stats for Dragunov have been changed, has no effect on actual game play.
### Fixed
- Fix fast download failing when the target host is missing a trailing slash.
- Fix servers not being listed in the server browser.
- Fix some FPS drop issues caused by compression code.
### Known issues
- HTTPS is not supported for fast downloads at the moment.
## [0.5.3] - 2017-07-02
### Added
- Increase webserver security.
### Fixed
- Reduce lags introduced by nodes.
- Fix modlist download.
## [0.5.2] - 2017-07-02
### Fixed
- Fix dedicated server crash.
## [0.5.1] - 2017-07-02
### Added
- Add fast download option for custom mods/maps based on Call of Duty 4.
- Display a toast when an update is available.
- Use the hourglass cursor while loading assets (with the native cursor feature).
- Show bots in parenthesis after the number of players in the serverlist (request).
- Add GSC event `level waittill("say", string, player);` (request).
- Restrict unauthorized mod download for password protected servers.
- Add OMA support for 15 classes.
### Changed
- Show friend avatars when they play IW4x (request).
- Rewrite and optimize the entire node system.
- Remove syncnode command for node system.
- Remove steam start.
### Fixed
- Fix lags and frame drops caused by server sorting.
- Fix demos on custom maps.
- Can no longer join a lobby or server with an incorrect password.
- Fix crashes caused by a bug in file/data compression.
- Improve overall stability.
## [0.5.0] - 2017-06-04
### Added
- Add GSC functionality to download files via HTTP(S) (request).
- Implement preliminary custom map support.
### Changed
- Add new nicknames for bots.
- Bumped Fastfile version. If you built fastfiles with the zone builder (e.g. mod.ff) you have to rebuild them!
- Default `sv_network_fps` to `1000` on dedicated game servers.
- Increase maximum FOV to 120.
### Fixed
- Fix `iw4x_onelog` dvar.
- Fix server list only showing local servers by default instead of Internet servers.
- Fix some deadlock situations on game shutdown.
## [0.4.2] - 2017-03-16
### Changed
- Disable unnecessary dvar update in server browser.
- Update bot names.
### Fixed
- Fix process permissions.
- Fix classic AK-47 color bleedthrough.
- Re-aligned the MG4's Raffica iron sights.
## [0.4.1] - 2017-03-10
### Fixed
- Fix command line argument parsing.
## [0.4.0] - 2017-03-10
### Added
- Set played with status.
- Add support for 15 classes.
- Add `iw4x_onelog` dvar.
- Add show server/playercount in server browser.
### Changed
- Do not show friend status notifications with stream friendly UI.
- Reduce loaded modules for dedis.
- Use joystick emblem for friend status.
- Disable XP bar when max level.
- Change fs_game display postition.
- Replace Painkiller with Juiced from IW5.
### Fixed
- Fix AK weaponfiles.
- Fix brightness slider.
- Fix text length for column mod in server browser.
- Changed the L86 LSW to use the correct HUD icon.
- Re-aligned the M93 Raffica's iron sights.
- Re-aligned the Desert Eagle's iron sights.
## [0.3.3] - 2017-02-14
### Added
- Add mapname to friend status (request).
- Add option to toggle notify friend state.
- Add support for mod.ff.
### Changed
- Disabled big minidump message box.
- Limit dedicated servers to 15 instances per IP.
- Move build number location.
- Remove news ticker and friends button from theater.
### Fixed
- Fix audio bug in options menu.
- Fix crash caused by faulty file download requests to game hosts.
- Fix friend sorting.
- Fix game not starting issue under certain circumstances.
- Fix menu crash.
- Fix typo in security increase popmenu.
- Fix vid_restart crash with connect protocol.
- Fix weapon crash issue.
- Potentially fix no-ammo bug.
## [0.3.2] - 2017-02-12
This is the third public Beta version.
### Added
- Add working friend system.
- Add colored pings in the server list.
- Add credits.
- Add first launch menu.
- Add AK-47 (Classic) attachments.
- Add HUD icon for night vision goggles.
### Changed
- Join when pressing enter in server list (request).
- Redesign and refactor all fullscreen menus.
- Increase weapon and configstring limit.
- Allow creating full crash dumps if wanted.
- Set default name from steam.
### Fixed
- Fix missing models on village.
- Fix custom server motd (request).
- Fix various memory leaks.
- Fix mouse pitch (request).
- Fix compatibility with B3 (request).
- Fix RCon bug (request).
- Fix dedicated server crash on linux.
- Fix crash in mod download.
- Fix peacekeeper reload sound.
- Fix cl_maxpackets 125 in settings (request).
- Fix deserteaglegold_mp icon.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.3.1] - 2017-01-21
This is the second public Beta version.
### Added
- Add classic AK-47 to CAC.
- Add servers to favorites when ingame.
- Add delete favorites button in the serverlist.
### Changed
- Change maplist to a dynamic list.
### Fixed
- Fix list focus.
- Fix mod restart loop.
- Fix mod download status.
- Fix modelsurf crash.
- Fix floating AK-74u.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.3.0] - 2017-01-14
This is the first public Beta version.
### Added
- Add com_logFilter dvar.
- Add peacekeeper.
- Add support for maps from DLC 8 (Recycled Pack)
- Chemical Plant (mp_storm_spring)
- Crash: Tropical (mp_crash_tropical)
- Estate: Tropical (mp_estate_tropical)
- Favela: Tropical (mp_fav_tropical)
- Forgotten City (mp_bloc_sh)
### Changed
- Improve node synchronization handling.
- Improve security by modifying GUIDs to allow 64-bit certificate fingerprints.
- Optimize fastfiles, they are now a lot smaller.
- Replace intro.
### Fixed
- Fix concurrent image loading bug.
- Fix issues when spawning more than one bot.
- Fix no ammo bug.
- Fix server crash on startup.
- Fix splash screen hang.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.2.1] - 2016-12-14
This is the second public Alpha version.
### Added
- Add support for CoD:Online maps.
- Firing Range (mp_firingrange)
- Rust (mp_rust_long)
- Shipment (mp_shipment/mp_shipment_long)
- Add `sv_motd` Dvar for server owners to set custom motd (will be visible in the loadscreen).
- Add Zonebuilder support for sounds and fx.
- Add command setviewpos.
- Add high-definition loadscreens.
### Changed
- Rename Arctic Wet Work map to it's official name (Freighter).
- Complete redesign of the main menus.
- Allow `cl_maxpackets` to be set up to 125.
### Fixed
- Fix crash when using the Harrier killstreak.
- Disable code that downloads news/changelog when in zonebuilder mode.
- Fix freeze on game shutdown.
- Disable unlockstats while ingame to prevent a crash.
### Known issues
- IW4x on Linux currently requires gnutls to be installed to access the Tor service via HTTPS.
## [0.2.0] - 2016-10-09
This is the first public Alpha version.
### Added
- Support for CoD:Online maps.
- Arctic Wet Work (mp_cargoship_sh)
- Bloc (mp_bloc)
- Bog (mp_bog_sh)
- Crossfire (mp_cross_fire)
- Killhouse (mp_killhouse)
- Nuketown (mp_nuked)
- Wet Work (mp_cargoship)
### Fixed
- Fix techniques in Zonebuilder.
- Fix possible memory leak.
- Fix timeout bug when connecting to server via iw4x link.
- Partially fix deadlock in decentralized networking code.
### Known issues
- Running IW4x on Linux currently requires gnutls to be installed additional to Wine as it needs to access the Tor service via HTTPS.
## [0.1.1] - 2016-09-19
This version is an internal Pre-Alpha version.
### Added
- Add IW5 material embedding system.
### Changed
- Enhance mod download with detailed progress display.
### Fixed
- Fix and optimize network logging commands.
- Fix console not displaying command inputs properly.
- Fix crash when running multiple instances of the IW4x client from the same directory.
- Fix crash when the `securityLevel` command is used on a game server.
- Fix possible data corruption in code for decentralized networking.
- Fix possible deadlock during client shutdown.
## [0.1.0] - 2016-09-03
This version is an internal Pre-Alpha version.
### Added
- Add `banclient` command which will permanently ban a client from a server. The ban will persist across restarts.
- Add capabilities to save played games and replay them ("Theater").
- Add code for generating and sending minidumps for debugging purposes. This feature is meant to be used only during the Open Beta and will be removed once the code goes to stable release.
- Add commands that allow forwarding console and games log via UDP to other computers ("network logging").
- Add D3D9Ex.
- Add filters for server list.
- Add handling for `iw4x://` URLs ("connect protocol"). For example, if IW4x is properly registered in Windows as URL handler for `iw4x://` URLs you can type `iw4x://ip:port`. If possible, this will connect to the server in an already running IW4x client.
- Add lean support through new key bindings.
- Add native cursor as replacement for the sometimes laggy in-game cursor. This change can be reverted in the settings menu.
- Add news ticker.
- Add remote console ("RCon").
- Add support for BigBrotherBot.
- Add support for hosting game mods in order to allow players to just join modded servers out of the box ("mod download").
- Add Warfare2 text coloring.
- Add zone builder. For more information see the respective documentation.
- Implement a completely decentralized peering network.
- Implement playlists which can be used for flexible map and gametype rotation.
- Introduce security levels. This ensures that you need to "pay" with CPU power to verify your identity once before being able to join a server which reduces the interval at which people who get banned can circumvent server bans through using new identities. The default security level is 23.
- Move IW4x resource files into their own folder to prevent clogging up the main game directories.
- Reintroduce parties, now also available for dedicated servers ("lobby servers").
### Changed
- Move logs to `userraw` folder.
- Replace main menu background music.

20
Jenkinsfile vendored
View File

@ -268,15 +268,17 @@ gitlabBuilds(builds: ["Checkout & Versioning", "Build", "Testing", "Archiving"])
}
executions["$testName on Linux"] = {
node("docker && linux && amd64") {
wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) {
def image = null
dir("src") {
unstash "jenkins-files"
image = docker.build("github.com/IW4x/iw4x-client-testing-wine32", "--rm --force-rm -f jenkins/wine32.Dockerfile jenkins")
deleteDir()
}
image.inside {
doUnitTests(test.StashName)
timeout(time: 10, unit: "MINUTES") {
wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) {
def image = null
dir("src") {
unstash "jenkins-files"
image = docker.build("github.com/IW4x/iw4x-client-testing-wine32", "--rm --force-rm -f jenkins/wine32.Dockerfile jenkins")
deleteDir()
}
image.inside {
doUnitTests(test.StashName)
}
}
}
}

View File

@ -2,7 +2,9 @@
![forks](https://img.shields.io/github/forks/IW4x/iw4x-client.svg)
![stars](https://img.shields.io/github/stars/IW4x/iw4x-client.svg)
![issues](https://img.shields.io/github/issues/IW4x/iw4x-client.svg)
[![discord](https://discordapp.com/api/guilds/219514629703860235/widget.png)](https://discord.gg/sKeVmR3)
[![build status](https://ci.appveyor.com/api/projects/status/rvljq0ooxen0oexm/branch/develop?svg=true)](https://ci.appveyor.com/project/iw4x/iw4x-client/branch/develop)
[![discord](https://img.shields.io/endpoint?url=https://momo5502.com/iw4x/members-badge.php)](https://discord.gg/sKeVmR3)
[![patreon](https://img.shields.io/badge/patreon-support-blue.svg?logo=patreon)](https://www.patreon.com/iw4x)
# IW4x: Client
@ -38,7 +40,6 @@
| `--disable-bitmessage` | Disable use of BitMessage completely. |
| `--disable-base128` | Disable base128 encoding for minidumps. |
| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). |
| `--enable-dxsdk` | Enable DirectX SDK (required for GfxMap exporting). |
## Disclaimer

34
appveyor.yml Normal file
View File

@ -0,0 +1,34 @@
# AppVeyor CI configuration
version: "#{build} ({branch})"
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
PREMAKE_ACTION: vs2019
configuration:
- Debug
- Release
platform: Win32
install:
- ps: |
Write-Host "Updating version information..." -ForegroundColor Cyan
Update-AppveyorBuild -Version $(& tools/premake5.exe version | select -Last 1)
- git submodule update --init --recursive
- ps: |
Write-Host "Generating project files with premake..." -ForegroundColor Cyan
& "./tools/premake5.exe" $env:PREMAKE_ACTION
Write-Host "Generated" -ForegroundColor Green
build:
project: build/iw4x.sln
parallel: true
verbosity: minimal
artifacts:
- path: build/bin/**/*.dll
- path: build/bin/**/*.pdb

1
deps/dxsdk vendored Submodule

@ -0,0 +1 @@
Subproject commit 996cf740444ce56178e6bc32e7fbe2c8b0f40f08

2
deps/json11 vendored

@ -1 +1 @@
Subproject commit 8ccf1f0c5ecab6151a65f216e7eeccd8588e5457
Subproject commit e2e3a11e99672b018e0e0657867e6a3439e180cf

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit a1f6312416ef6cd183ee62db58b640dc2d7ec1f4
Subproject commit 1937f412605e1b04ddb41ef9c2f2f0aab7e61548

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 13444a8af2d077eda8fd0be017cac2dad20465dc
Subproject commit 6ac0b0c1b69b9a88e1b3b3002c2e3a9062ae99b4

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit ff4649fe42bfe93835a0bac77fbe8ab84c8b36e4
Subproject commit cb602f178ccea7f0c790cf5510f7a29c017db954

2
deps/pdcurses vendored

@ -1 +1 @@
Subproject commit b12c4a5212e97cf83f0074618c1473253fad4131
Subproject commit 618e0aaa31b4728eb4df78ec4de6c2b873908eda

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 66dc42d891a4fc8e9190c524fd67961688a37bbe
Subproject commit 63cfdafacba6141717a2df97fc123dc0c14ba7c4

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 79baebe50e4d6b73ae1f8b603f0ef41300110aa3
Subproject commit d71dc66fa8a153fb6e7c626847095d9697a6cf42

View File

@ -1,7 +1,7 @@
# Requires a decent modern Docker version (v1.10.x at least ideally)
# Use semi-official Arch Linux image with fixed versioning
FROM base/archlinux:2018.11.01
FROM archlinux/base
# Environment variables
ENV WINEPREFIX /wine32
@ -10,7 +10,7 @@ ENV WINEDEBUG -all
# Install Wine (32-bit)
RUN \
echo -e "#!/bin/sh\nwine \$@\nretval=\$?\ntail --pid=\$(pidof wineserver 2>/dev/null||echo 0) -f /dev/null\nexit \$retval" > /usr/local/bin/wine-wrapper &&\
echo -e "#!/bin/sh\nwine \$@\nretval=\$?\nwineserver -w\nexit \$retval" > /usr/local/bin/wine-wrapper &&\
chmod +x /usr/local/bin/wine-wrapper &&\
\
(\
@ -19,11 +19,13 @@ RUN \
echo 'Include = /etc/pacman.d/mirrorlist'\
) >> /etc/pacman.conf &&\
pacman -Sy --noconfirm \
awk \
lib32-gnutls \
wine \
wget \
xorg-server-xvfb \
pacman-contrib \
pacman-contrib \
awk \
&&\
\
wine-wrapper wineboot.exe -i &&\
@ -39,7 +41,6 @@ RUN \
find /. -name "*~" -type f -delete &&\
rm -rf /tmp/* /var/tmp/* /usr/share/man/* /usr/share/info/* /usr/share/doc/* &&\
pacman -Scc --noconfirm &&\
paccache -rk0 &&\
rm -rf /var/lib/pacman/sync/*
USER 0

35
premake/dxsdk.lua Normal file
View File

@ -0,0 +1,35 @@
dxsdk = {
settings = nil
}
function dxsdk.setup(settings)
if not settings.source then error("Missing source.") end
dxsdk.settings = settings
if not dxsdk.settings.defines then dxsdk.settings.defines = {} end
end
function dxsdk.import()
if not dxsdk.settings then error("You need to call dxsdk.setup first") end
--filter "platforms:*32"
libdirs { path.join(dxsdk.settings.source, "Lib/x86") }
--filter "platforms:*64"
-- libdirs { path.join(dxsdk.settings.source, "Lib/x64") }
--filter {}
dxsdk.includes()
end
function dxsdk.includes()
if not dxsdk.settings then error("You need to call dxsdk.setup first") end
includedirs { path.join(dxsdk.settings.source, "Include") }
defines(dxsdk.settings.defines)
end
function dxsdk.project()
end

View File

@ -1,4 +1,5 @@
gitVersioningCommand = "git describe --tags --dirty --always"
gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD"
-- Quote the given string input as a C string
function cstrquote(value)
@ -73,11 +74,6 @@ newoption {
description = "Upload minidumps even for Debug builds."
}
newoption {
trigger = "enable-dxsdk",
description = "Enable DirectX SDK (required for GfxMap exporting)."
}
newaction {
trigger = "version",
description = "Returns the version string for the current commit of the source code.",
@ -86,8 +82,19 @@ newaction {
local proc = assert(io.popen(gitVersioningCommand, "r"))
local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
proc:close()
local version = gitDescribeOutput
print(gitDescribeOutput)
proc = assert(io.popen(gitCurrentBranchCommand, "r"))
local gitCurrentBranchOutput = assert(proc:read('*a')):gsub("%s+", "")
local gitCurrentBranchSuccess = proc:close()
if gitCurrentBranchSuccess then
-- We got a branch name, check if it is a feature branch
if gitCurrentBranchOutput ~= "develop" and gitCurrentBranchOutput ~= "master" then
version = version .. "-" .. gitCurrentBranchOutput
end
end
print(version)
os.exit(0)
end
}
@ -182,6 +189,7 @@ require "premake/protobuf"
require "premake/zlib"
require "premake/udis86"
require "premake/iw4mvm"
require "premake/dxsdk"
json11.setup
{
@ -236,8 +244,13 @@ iw4mvm.setup
},
source = path.join(depsBasePath, "iw4mvm"),
}
dxsdk.setup
{
source = path.join(depsBasePath, "dxsdk"),
}
workspace "iw4x"
startproject "iw4x"
location "./build"
objdir "%{wks.location}/obj"
targetdir "%{wks.location}/bin/%{cfg.buildcfg}"
@ -311,11 +324,6 @@ workspace "iw4x"
if _OPTIONS["force-exception-handler"] then
defines { "FORCE_EXCEPTION_HANDLER" }
end
if _OPTIONS["enable-dxsdk"] then
defines { "ENABLE_DXSDK" }
includedirs { "%DXSDK_DIR%Include" }
libdirs { "%DXSDK_DIR%Lib/x86" }
end
-- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives
@ -332,6 +340,7 @@ workspace "iw4x"
zlib.import()
udis86.import()
--iw4mvm.import()
dxsdk.import()
-- fix vpaths for protobuf sources
vpaths
@ -450,7 +459,7 @@ workspace "*"
buildoptions {
"/std:c++latest"
}
systemversion "10.0.17763.0"
systemversion "latest"
defines { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" }
rule "ProtobufCompiler"

View File

@ -60,6 +60,7 @@ namespace Components
Loader::Register(new Console());
Loader::Register(new Friends());
Loader::Register(new IPCPipe());
Loader::Register(new MapDump());
Loader::Register(new ModList());
Loader::Register(new Monitor());
Loader::Register(new Network());
@ -72,9 +73,7 @@ namespace Components
Loader::Register(new Renderer());
Loader::Register(new UIFeeder());
Loader::Register(new UIScript());
#ifndef DISABLE_ANTICHEAT
Loader::Register(new AntiCheat());
#endif
Loader::Register(new Changelog());
Loader::Register(new Dedicated());
Loader::Register(new Discovery());
@ -103,6 +102,8 @@ namespace Components
Loader::Register(new ConnectProtocol());
Loader::Register(new StartupMessages());
Loader::Register(new Client());
Loader::Pregame = false;
}

View File

@ -93,6 +93,7 @@ namespace Components
#include "Modules/Logger.hpp"
#include "Modules/Friends.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/MapDump.hpp"
#include "Modules/Session.hpp"
#include "Modules/ClanTags.hpp"
#include "Modules/Download.hpp"
@ -129,3 +130,5 @@ namespace Components
#include "Modules/ConnectProtocol.hpp"
#include "Modules/StartupMessages.hpp"
#include "Modules/Stats.hpp"
#include "Modules/Client.hpp"

View File

@ -824,6 +824,64 @@ namespace Components
__VMProtectEnd;
}
void AntiCheat::SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC) {
FILETIME ftA, ftB, ftC;
ULARGE_INTEGER uiA, uiB, uiC;
SystemTimeToFileTime(stA, &ftA);
SystemTimeToFileTime(stB, &ftB);
uiA.HighPart = ftA.dwHighDateTime;
uiA.LowPart = ftA.dwLowDateTime;
uiB.HighPart = ftB.dwHighDateTime;
uiB.LowPart = ftB.dwLowDateTime;
uiC.QuadPart = uiA.QuadPart - uiB.QuadPart;
ftC.dwHighDateTime = uiC.HighPart;
ftC.dwLowDateTime = uiC.LowPart;
FileTimeToSystemTime(&ftC, stC);
}
void AntiCheat::CheckStartupTime()
{
__VMProtectBeginUltra("");
FILETIME creation, exit, kernel, user;
SYSTEMTIME current, creationSt, diffSt;
GetSystemTime(&current);
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
FileTimeToSystemTime(&creation, &creationSt);
AntiCheat::SystemTimeDiff(&current, &creationSt, &diffSt);
#ifdef DEBUG
char buf[512];
snprintf(buf, 512, "creation: %d:%d:%d:%d\n", creationSt.wHour, creationSt.wMinute, creationSt.wSecond, creationSt.wMilliseconds);
OutputDebugStringA(buf);
snprintf(buf, 512, "current: %d:%d:%d:%d\n", current.wHour, current.wMinute, current.wSecond, current.wMilliseconds);
OutputDebugStringA(buf);
snprintf(buf, 512, "diff: %d:%d:%d:%d\n", diffSt.wHour, diffSt.wMinute, diffSt.wSecond, diffSt.wMilliseconds);
OutputDebugStringA(buf);
#endif
// crash client if they are using process suspension to inject dlls during startup (aka before we got to here)
// maybe tweak this value depending on what the above logging reveals during testing,
// but 5 seconds seems about right for now
int time = diffSt.wMilliseconds + (diffSt.wSecond * 1000) + (diffSt.wMinute * 1000 * 60);
if (time > 5000) {
Components::AntiCheat::CrashClient();
}
// use below for logging when using StartSuspended.exe
// FILE* f = fopen("times.txt", "a");
// fwrite(buf, 1, strlen(buf), f);
// fclose(f);
__VMProtectEnd;
}
AntiCheat::AntiCheat()
{
__VMProtectBeginUltra("");
@ -831,13 +889,12 @@ namespace Components
time(nullptr);
AntiCheat::Flags = NO_FLAG;
#ifdef DEBUG
#ifdef DISABLE_ANTICHEAT
Command::Add("penis", [](Command::Params*)
{
AntiCheat::CrashClient();
});
#else
Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick();
Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick();
Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick();
@ -866,6 +923,7 @@ namespace Components
// Set the integrity flag
AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION;
#endif
__VMProtectEnd;

View File

@ -54,6 +54,9 @@ namespace Components
static void UninstallLibHook();
static void InstallLibHook();
static void CheckStartupTime();
static void SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC);
private:
enum IntergrityFlag
{
@ -114,3 +117,4 @@ namespace Components
static Utils::Hook VirtualProtectHook[2];
};
}

View File

@ -112,5 +112,9 @@ namespace Components
Utils::Hook::Set<BYTE>(0x4A95F8, 32);
Utils::Hook::Set<int>(0x42F22B, offsetof(Game::newMapArena_t, mapName) - offsetof(Game::newMapArena_t, other));
// patch max arena count
constexpr auto arenaCount = sizeof(ArenaLength::NewArenas) / sizeof(Game::newMapArena_t);
Utils::Hook::Set<std::uint32_t>(0x630AA3, arenaCount);
}
}

View File

@ -3,6 +3,7 @@
namespace Components
{
thread_local int AssetHandler::BypassState = 0;
bool AssetHandler::ShouldSearchTempAssets = false;
std::map<Game::XAssetType, AssetHandler::IAsset*> AssetHandler::AssetInterfaces;
std::map<Game::XAssetType, Utils::Slot<AssetHandler::Callback>> AssetHandler::TypeCallbacks;
Utils::Signal<AssetHandler::RestrictCallback> AssetHandler::RestrictSignal;
@ -69,6 +70,21 @@ namespace Components
return header;
}
Game::XAssetHeader AssetHandler::FindTemporaryAsset(Game::XAssetType type, const char* filename)
{
Game::XAssetHeader header = { nullptr };
if (type >= Game::XAssetType::ASSET_TYPE_COUNT) return header;
auto tempPool = &AssetHandler::TemporaryAssets[type];
auto entry = tempPool->find(filename);
if (entry != tempPool->end())
{
header = { entry->second };
}
return header;
}
int AssetHandler::HasThreadBypass()
{
return AssetHandler::BypassState > 0;
@ -116,7 +132,7 @@ namespace Components
test al, al
jnz finishOriginal
jnz checkTempAssets
mov ecx, [esp + 18h] // Asset type
mov ebx, [esp + 1Ch] // Filename
@ -139,9 +155,28 @@ namespace Components
test eax, eax
jnz finishFound
checkTempAssets:
mov al, AssetHandler::ShouldSearchTempAssets // check to see if enabled
test eax, eax
jz finishOriginal
finishOriginal:
// Asset not found using custom handlers, redirect to DB_FindXAssetHeader
mov ecx, [esp + 18h] // Asset type
mov ebx, [esp + 1Ch] // Filename
push ebx
push ecx
call AssetHandler::FindTemporaryAsset
add esp, 8h
test eax, eax
jnz finishFound
finishOriginal:
// Asset not found using custom handlers or in temp assets or bypasses were enabled
// redirect to DB_FindXAssetHeader
mov ebx, ds:6D7190h // InterlockedDecrement
mov eax, 40793Bh
jmp eax
@ -181,7 +216,7 @@ namespace Components
}
// Fix shader const stuff
if (type == Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET && Zones::Version() >= 359)
if (type == Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET && Zones::Version() >= 359 && Zones::Version() < 448)
{
for (int i = 0; i < 48; ++i)
{
@ -454,6 +489,11 @@ namespace Components
Utils::Hook::Set<Game::XAssetEntry*>(0x5BAEA2, entryPool + 1);
}
void AssetHandler::ExposeTemporaryAssets(bool expose)
{
AssetHandler::ShouldSearchTempAssets = expose;
}
AssetHandler::AssetHandler()
{
this->reallocateEntryPool();
@ -493,6 +533,210 @@ namespace Components
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool*)
{
#ifdef DEBUG
// #define DUMP_TECHSETS
#ifdef DUMP_TECHSETS
if (type == Game::XAssetType::ASSET_TYPE_VERTEXDECL && !name.empty() && name[0] != ',')
{
std::filesystem::create_directories("techsets/vertexdecl");
auto vertexdecl = asset.vertexDecl;
std::vector<json11::Json> routingData;
for (int i = 0; i < vertexdecl->streamCount; i++)
{
routingData.push_back(json11::Json::object
{
{ "source", (int)vertexdecl->routing.data[i].source },
{ "dest", (int)vertexdecl->routing.data[i].dest },
});
}
std::vector<json11::Json> declData;
for (int i = 0; i < 16; i++)
{
if (vertexdecl->routing.decl[i])
{
routingData.push_back(int(vertexdecl->routing.decl[i]));
}
else
{
routingData.push_back(nullptr);
}
}
json11::Json vertexData = json11::Json::object
{
{ "name", vertexdecl->name },
{ "streamCount", vertexdecl->streamCount },
{ "hasOptionalSource", vertexdecl->hasOptionalSource },
{ "routing", routingData },
{ "decl", declData },
};
auto stringData = vertexData.dump();
auto fp = fopen(Utils::String::VA("techsets/vertexdecl/%s.%s.json", vertexdecl->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb");
fwrite(&stringData[0], stringData.size(), 1, fp);
fclose(fp);
}
if (type == Game::ASSET_TYPE_TECHNIQUE_SET && !name.empty() && name[0] != ',')
{
std::filesystem::create_directory("techsets");
std::filesystem::create_directories("techsets/techniques");
auto techset = asset.techniqueSet;
std::vector<json11::Json> techniques;
for (int technique = 0; technique < 48; technique++)
{
auto curTech = techset->techniques[technique];
if (curTech)
{
std::vector<json11::Json> passDataArray;
for (int pass = 0; pass < curTech->passCount; pass++)
{
auto curPass = &curTech->passArray[pass];
std::vector<json11::Json> argDataArray;
for (int arg = 0; arg < curPass->perObjArgCount + curPass->perPrimArgCount + curPass->stableArgCount; arg++)
{
auto curArg = &curPass->args[arg];
if (curArg->type == 1 || curArg->type == 7)
{
std::vector<float> literalConsts;
if (curArg->u.literalConst != 0)
{
literalConsts.push_back(curArg->u.literalConst[0]);
literalConsts.push_back(curArg->u.literalConst[1]);
literalConsts.push_back(curArg->u.literalConst[2]);
literalConsts.push_back(curArg->u.literalConst[3]);
}
json11::Json argData = json11::Json::object
{
{ "type", curArg->type },
{ "value", literalConsts },
};
argDataArray.push_back(argData);
}
else if (curArg->type == 3 || curArg->type == 5)
{
json11::Json argData = json11::Json::object
{
{ "type", curArg->type },
{ "firstRow", curArg->u.codeConst.firstRow },
{ "rowCount", curArg->u.codeConst.rowCount },
{ "index", curArg->u.codeConst.index },
};
argDataArray.push_back(argData);
}
else
{
json11::Json argData = json11::Json::object
{
{ "type", curArg->type },
{ "value", static_cast<int>(curArg->u.codeSampler) },
};
argDataArray.push_back(argData);
}
}
json11::Json passData = json11::Json::object
{
{ "perObjArgCount", curPass->perObjArgCount },
{ "perPrimArgCount", curPass->perPrimArgCount },
{ "stableArgCount", curPass->stableArgCount },
{ "args", argDataArray },
{ "pixelShader", curPass->pixelShader ? curPass->pixelShader->name : "" },
{ "vertexShader", curPass->vertexShader ? curPass->vertexShader->name : "" },
{ "vertexDecl", curPass->vertexDecl ? curPass->vertexDecl->name : "" },
};
passDataArray.push_back(passData);
}
json11::Json techData = json11::Json::object
{
{ "name", curTech->name },
{ "index", technique },
{ "flags", curTech->flags },
{ "numPasses", curTech->passCount },
{ "pass", passDataArray },
};
auto stringData = techData.dump();
auto fp = fopen(Utils::String::VA("techsets/techniques/%s.%s.json", curTech->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb");
fwrite(&stringData[0], stringData.size(), 1, fp);
fclose(fp);
json11::Json techsetTechnique = json11::Json::object
{
{ "name", curTech->name },
{ "index", technique },
};
techniques.push_back(techsetTechnique);
}
else
{
techniques.push_back(nullptr);
}
}
json11::Json techsetData = json11::Json::object
{
{ "name", techset->name },
{ "techniques", techniques },
};
auto stringData = techsetData.dump();
auto fp = fopen(Utils::String::VA("techsets/%s.%s.json", techset->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb");
fwrite(&stringData[0], stringData.size(), 1, fp);
fclose(fp);
}
#endif
if (type == Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET && Zones::Version() >= 460)
{
auto techset = asset.techniqueSet;
if (techset)
{
for (int t = 0; t < 48; t++)
{
if (techset->techniques[t])
{
for (int p = 0; p < techset->techniques[t]->passCount; p++)
{
for (int a = 0; a < techset->techniques[t]->passArray[p].perObjArgCount +
techset->techniques[t]->passArray[p].perPrimArgCount +
techset->techniques[t]->passArray[p].stableArgCount; a++)
{
auto arg = &techset->techniques[t]->passArray[p].args[a];
if (arg->type == 3 || arg->type == 5)
{
if (arg->u.codeConst.index > 140)
{
OutputDebugStringA(Utils::String::VA("codeConst %i is out of range for %s::%s[%i]\n", arg->u.codeConst.index,
techset->name, techset->techniques[t]->name, p));
if (!ZoneBuilder::IsEnabled())
{
__debugbreak();
}
}
}
}
}
}
}
}
}
#endif
if (Dvar::Var("r_noVoid").get<bool>() && type == Game::XAssetType::ASSET_TYPE_XMODEL && name == "void")
{
asset.model->numLods = 0;
@ -500,16 +744,16 @@ namespace Components
});
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP, 1);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, ZoneBuilder::IsEnabled() ? 14336 : 7168);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, ZoneBuilder::IsEnabled() ? 14336 * 2 : 7168);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, 14000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIMPARTS, 8192);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIMPARTS, 8192 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PHYSPRESET, 128);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PIXELSHADER, ZoneBuilder::IsEnabled() ? 0x4000 : 10000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, ZoneBuilder::IsEnabled() ? 0x2000 : 3072);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MATERIAL, 8192);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MATERIAL, 8192 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXDECL, ZoneBuilder::IsEnabled() ? 0x400 : 196);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_WEAPON, WEAPON_LIMIT);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_STRINGTABLE, 800);
@ -519,12 +763,14 @@ namespace Components
if (ZoneBuilder::IsEnabled())
{
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MAP_ENTS, 10);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, 8192);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, 8192 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, 0x2000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FONT, 32);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_RAWFILE, 2048);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LEADERBOARD, 500);
AssetHandler::RegisterInterface(new Assets::IFont_s());
AssetHandler::RegisterInterface(new Assets::IWeapon());
AssetHandler::RegisterInterface(new Assets::IXModel());
AssetHandler::RegisterInterface(new Assets::IFxWorld());
AssetHandler::RegisterInterface(new Assets::IMapEnts());
@ -535,8 +781,9 @@ namespace Components
AssetHandler::RegisterInterface(new Assets::ISndCurve());
AssetHandler::RegisterInterface(new Assets::IMaterial());
AssetHandler::RegisterInterface(new Assets::IMenuList());
AssetHandler::RegisterInterface(new Assets::IclipMap_t());
AssetHandler::RegisterInterface(new Assets::ImenuDef_t());
AssetHandler::RegisterInterface(new Assets::IclipMap_t());
AssetHandler::RegisterInterface(new Assets::ITracerDef());
AssetHandler::RegisterInterface(new Assets::IPhysPreset());
AssetHandler::RegisterInterface(new Assets::IXAnimParts());
AssetHandler::RegisterInterface(new Assets::IFxEffectDef());

View File

@ -39,8 +39,13 @@ namespace Components
static void ResetBypassState();
static void ExposeTemporaryAssets(bool expose);
static void OffsetToAlias(Utils::Stream::Offset* offset);
private:
static thread_local int BypassState;
static bool ShouldSearchTempAssets;
static std::map<std::string, Game::XAssetHeader> TemporaryAssets[Game::XAssetType::ASSET_TYPE_COUNT];
@ -55,12 +60,11 @@ namespace Components
static void RegisterInterface(IAsset* iAsset);
static Game::XAssetHeader FindAsset(Game::XAssetType type, const char* filename);
static Game::XAssetHeader FindTemporaryAsset(Game::XAssetType type, const char* filename);
static bool IsAssetEligible(Game::XAssetType type, Game::XAssetHeader* asset);
static void FindAssetStub();
static void AddAssetStub();
static void OffsetToAlias(Utils::Stream::Offset* offset);
static void StoreEmptyAsset(Game::XAssetType type, const char* name);
static void StoreEmptyAssetStub();
@ -76,6 +80,7 @@ namespace Components
}
#include "AssetInterfaces/IFont_s.hpp"
#include "AssetInterfaces/IWeapon.hpp"
#include "AssetInterfaces/IXModel.hpp"
#include "AssetInterfaces/IFxWorld.hpp"
#include "AssetInterfaces/IMapEnts.hpp"
@ -86,8 +91,9 @@ namespace Components
#include "AssetInterfaces/IMaterial.hpp"
#include "AssetInterfaces/ISndCurve.hpp"
#include "AssetInterfaces/IMenuList.hpp"
#include "AssetInterfaces/ImenuDef_t.hpp"
#include "AssetInterfaces/IclipMap_t.hpp"
#include "AssetInterfaces/ImenuDef_t.hpp"
#include "AssetInterfaces/ITracerDef.hpp"
#include "AssetInterfaces/IPhysPreset.hpp"
#include "AssetInterfaces/IXAnimParts.hpp"
#include "AssetInterfaces/IFxEffectDef.hpp"
@ -105,3 +111,4 @@ namespace Components
#include "AssetInterfaces/IMaterialVertexShader.hpp"
#include "AssetInterfaces/IStructuredDataDefSet.hpp"
#include "AssetInterfaces/IMaterialVertexDeclaration.hpp"

View File

@ -1,7 +1,56 @@
#include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets
{
void IMaterialPixelShader::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
}
void IMaterialPixelShader::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{
header->pixelShader = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).pixelShader;
}
void IMaterialPixelShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File psFile(Utils::String::VA("ps/%s.iw4xPS", name.data()));
if (!psFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), psFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xPIXL", 8))
{
Components::Logger::Error(0, "Reading pixel shader '%s' failed, header is invalid!", name.data());
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error("Reading pixel shader '%s' failed, expected version is %d, but it was %d!", name.data(), atoi(IW4X_TECHSET_VERSION), atoi(version.data()));
}
Game::MaterialPixelShader* asset = reader.readObject<Game::MaterialPixelShader>();
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->prog.loadDef.program)
{
asset->prog.loadDef.program = reader.readArray<unsigned int>(asset->prog.loadDef.programSize);
}
header->pixelShader = asset;
}
void IMaterialPixelShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::MaterialPixelShader, 16);

View File

@ -8,5 +8,9 @@ namespace Assets
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PIXELSHADER; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
};
}

View File

@ -1,9 +1,144 @@
#include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets
{
void IMaterialTechniqueSet::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
}
void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{
header->techniqueSet = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).techniqueSet;
}
void IMaterialTechniqueSet::loadBinaryTechnique(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::MaterialPass, 20);
Components::FileSystem::File techFile(Utils::String::VA("techniques/%s.iw4xTech", name.data()));
if (!techFile.exists()) {
*tech = nullptr;
Components::Logger::Print("Warning: Missing technique '%s'\n", name.data());
return;
}
Utils::Stream::Reader reader(builder->getAllocator(), techFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xTECH", 8))
{
Components::Logger::Error(0, "Reading technique '%s' failed, header is invalid!", name.data());
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error("Reading technique '%s' failed, expected version is %d, but it was %d!", name.data(), atoi(IW4X_TECHSET_VERSION), atoi(version.data()));
}
unsigned short flags = reader.read<unsigned short>();
unsigned short passCount = reader.read<unsigned short>();
Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray<unsigned char>(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passCount - 1)));
asset->name = builder->getAllocator()->duplicateString(name);
asset->flags = flags;
asset->passCount = passCount;
Game::MaterialPass* passes = reader.readArray<Game::MaterialPass>(passCount);
std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * passCount);
for (unsigned short i = 0; i < asset->passCount; i++)
{
Game::MaterialPass* pass = &asset->passArray[i];
if (pass->vertexDecl)
{
const char* declName = reader.readCString();
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
}
if (pass->vertexShader)
{
const char* vsName = reader.readCString();
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
}
if (pass->pixelShader)
{
const char* psName = reader.readCString();
pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader;
}
pass->args = reader.readArray<Game::MaterialShaderArgument>(pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount);
for (int j = 0; j < pass->perPrimArgCount + pass->perObjArgCount + pass->stableArgCount; j++)
{
if (pass->args[j].type == 1 || pass->args[j].type == 7)
{
pass->args[j].u.literalConst = reader.readArray<float>(4);
}
if (pass->args[j].type == 3 || pass->args[j].type == 5)
{
pass->args[j].u.codeConst.index = *reader.readObject<unsigned short>();
pass->args[j].u.codeConst.firstRow = *reader.readObject<unsigned char>();
pass->args[j].u.codeConst.rowCount = *reader.readObject<unsigned char>();
}
}
}
*tech = asset;
}
void IMaterialTechniqueSet::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File tsFile(Utils::String::VA("techsets/%s.iw4xTS", name.data()));
if (!tsFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), tsFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xTSET", 8))
{
Components::Logger::Error(0, "Reading techset '%s' failed, header is invalid!", name.data());
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error("Reading techset '%s' failed, expected version is %d, but it was %d!", name.data(), atoi(IW4X_TECHSET_VERSION), atoi(version.data()));
}
Game::MaterialTechniqueSet* asset = reader.readObject<Game::MaterialTechniqueSet>();
if (asset->name)
{
asset->name = reader.readCString();
}
for (int i = 0; i < 48; i++)
{
if (asset->techniques[i])
{
const char* techName = reader.readCString();
this->loadBinaryTechnique(&asset->techniques[i], techName, builder);
}
}
header->techniqueSet = asset;
}
void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
{
Game::MaterialTechniqueSet* asset = header.techniqueSet;
for (int i = 0; i < ARRAYSIZE(Game::MaterialTechniqueSet::techniques); ++i)

View File

@ -9,5 +9,11 @@ namespace Assets
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinaryTechnique(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder);
};
}

View File

@ -1,7 +1,50 @@
#include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets
{
void IMaterialVertexDeclaration::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
}
void IMaterialVertexDeclaration::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{
header->vertexDecl = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).vertexDecl;
}
void IMaterialVertexDeclaration::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File declFile(Utils::String::VA("decl/%s.iw4xDECL", name.data()));
if (!declFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), declFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xDECL", 8))
{
Components::Logger::Error(0, "Reading vertex declaration '%s' failed, header is invalid!", name.data());
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error("Reading vertex declaration '%s' failed, expected version is %d, but it was %d!", name.data(), atoi(IW4X_TECHSET_VERSION), atoi(version.data()));
}
Game::MaterialVertexDeclaration* asset = reader.readObject<Game::MaterialVertexDeclaration>();
if (asset->name)
{
asset->name = reader.readCString();
}
header->vertexDecl = asset;
}
void IMaterialVertexDeclaration::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::MaterialVertexDeclaration, 100);

View File

@ -8,5 +8,9 @@ namespace Assets
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_VERTEXDECL; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
};
}

View File

@ -1,7 +1,55 @@
#include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets
{
void IMaterialVertexShader::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
}
void IMaterialVertexShader::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{
header->vertexShader = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).vertexShader;
}
void IMaterialVertexShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Components::FileSystem::File vsFile(Utils::String::VA("vs/%s.iw4xVS", name.data()));
if (!vsFile.exists()) return;
Utils::Stream::Reader reader(builder->getAllocator(), vsFile.getBuffer());
char* magic = reader.readArray<char>(8);
if (std::memcmp(magic, "IW4xVERT", 8))
{
Components::Logger::Error(0, "Reading vertex shader '%s' failed, header is invalid!", name.data());
}
std::string version;
version.push_back(reader.read<char>());
if (version != IW4X_TECHSET_VERSION)
{
Components::Logger::Error("Reading vertex shader '%s' failed, expected version is %d, but it was %d!", name.data(), atoi(IW4X_TECHSET_VERSION), atoi(version.data()));
}
Game::MaterialVertexShader* asset = reader.readObject<Game::MaterialVertexShader>();
if (asset->name)
{
asset->name = reader.readCString();
}
if (asset->prog.loadDef.program)
{
asset->prog.loadDef.program = reader.readArray<unsigned int>(asset->prog.loadDef.programSize);
}
header->vertexShader = asset;
}
void IMaterialVertexShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::MaterialVertexShader, 16);

View File

@ -8,5 +8,9 @@ namespace Assets
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_VERTEXSHADER; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
void loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
};
}

View File

@ -2,6 +2,36 @@
namespace Assets
{
void IMenuList::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{
Utils::Memory::Allocator* allocator = builder->getAllocator();
// actually gets the whole list
auto menus = Components::Menus::LoadMenu(name);
if (menus.empty()) return;
// Allocate new menu list
Game::MenuList* newList = allocator->allocate<Game::MenuList>();
if (!newList) return;
newList->menus = allocator->allocateArray<Game::menuDef_t*>(menus.size());
if (!newList->menus)
{
allocator->free(newList);
return;
}
newList->name = allocator->duplicateString(name);
newList->menuCount = menus.size();
// Copy new menus
for (unsigned int i = 0; i < menus.size(); ++i)
{
newList->menus[i] = menus[i].second;
}
header->menuList = newList;
}
void IMenuList::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::MenuList *asset = header.menuList;

View File

@ -9,6 +9,6 @@ namespace Assets
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
// virtual void load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
};
}

View File

@ -0,0 +1,44 @@
#include "STDInclude.hpp"
namespace Assets
{
void ITracerDef::load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, Components::ZoneBuilder::Zone* /*builder*/)
{
return; // don't load from filesystem right now
}
void ITracerDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::TracerDef* asset = header.tracerDef;
if (asset->material)
{
builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->material);
}
}
void ITracerDef::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::TracerDef, 0x70);
Utils::Stream* buffer = builder->getBuffer();
Game::TracerDef* asset = header.tracerDef;
Game::TracerDef* dest = buffer->dest<Game::TracerDef>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
if (asset->name)
{
buffer->saveString(builder->getAssetName(this->getType(), asset->name));
Utils::Stream::ClearPointer(&dest->name);
}
if (asset->material)
{
dest->material = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->material).material;
}
buffer->popBlock();
}
}

View File

@ -0,0 +1,14 @@
#pragma once
namespace Assets
{
class ITracerDef : public Components::AssetHandler::IAsset
{
public:
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_TRACER; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
};
}

View File

@ -0,0 +1,702 @@
#include "STDInclude.hpp"
namespace Assets
{
void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{
// Try loading raw weapon
if (Components::FileSystem::File(Utils::String::VA("weapons/mp/%s", name.data())).exists())
{
// let the function see temporary assets when calling DB_FindXAssetHeader during the loading function
// otherwise it fails to link things properly
Components::AssetHandler::ExposeTemporaryAssets(true);
header->data = Game::BG_LoadWeaponDef_LoadObj(name.data());
Components::AssetHandler::ExposeTemporaryAssets(false);
}
}
void IWeapon::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::WeaponCompleteDef* asset = header.weapon;
// convert all script strings
if (asset->hideTags)
{
for (char i = 0; i < 32; ++i)
{
if (asset->hideTags[i] == NULL) break; // no more strings
builder->addScriptString(asset->hideTags[i]);
}
}
if (asset->weapDef->notetrackSoundMapKeys)
{
for (char i = 0; i < 16; ++i)
{
if (asset->weapDef->notetrackSoundMapKeys[i] == NULL) break; // no more strings
builder->addScriptString(asset->weapDef->notetrackSoundMapKeys[i]);
}
}
if (asset->weapDef->notetrackSoundMapValues)
{
for (char i = 0; i < 16; ++i)
{
if (asset->weapDef->notetrackSoundMapValues[i] == NULL) break; // no more strings
builder->addScriptString(asset->weapDef->notetrackSoundMapValues[i]);
}
}
if (asset->weapDef->notetrackRumbleMapKeys)
{
for (char i = 0; i < 16; ++i)
{
if (asset->weapDef->notetrackRumbleMapKeys[i] == NULL) break; // no more strings
builder->addScriptString(asset->weapDef->notetrackRumbleMapKeys[i]);
}
}
if (asset->weapDef->notetrackRumbleMapValues)
{
for (char i = 0; i < 16; ++i)
{
if (asset->weapDef->notetrackRumbleMapValues[i] == NULL) break; // no more strings
builder->addScriptString(asset->weapDef->notetrackRumbleMapValues[i]);
}
}
// now load all sub-assets properly
if (asset->killIcon) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->killIcon);
if (asset->dpadIcon) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->dpadIcon);
if (asset->weapDef->reticleCenter) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->reticleCenter);
if (asset->weapDef->reticleSide) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->reticleSide);
if (asset->weapDef->hudIcon) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->hudIcon);
if (asset->weapDef->pickupIcon) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->pickupIcon);
if (asset->weapDef->ammoCounterIcon) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->ammoCounterIcon);
if (asset->weapDef->overlayMaterial) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->overlayMaterial);
if (asset->weapDef->overlayMaterialLowRes) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->overlayMaterialLowRes);
if (asset->weapDef->overlayMaterialEMP) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->overlayMaterialEMP);
if (asset->weapDef->overlayMaterialEMPLowRes) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->weapDef->overlayMaterialEMPLowRes);
if (asset->weapDef->gunXModel)
{
for (int i = 0; i < 16; i++)
{
if (asset->weapDef->gunXModel[i]) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->gunXModel[i]);
}
}
if (asset->weapDef->handXModel) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->handXModel);
if (asset->weapDef->worldModel)
{
for (int i = 0; i < 16; i++)
{
if (asset->weapDef->worldModel[i]) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->worldModel[i]);
}
}
if (asset->weapDef->worldClipModel) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->worldClipModel);
if (asset->weapDef->rocketModel) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->rocketModel);
if (asset->weapDef->knifeModel) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->knifeModel);
if (asset->weapDef->worldKnifeModel) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->worldKnifeModel);
if (asset->weapDef->projectileModel) builder->loadAsset(Game::XAssetType::ASSET_TYPE_XMODEL, asset->weapDef->projectileModel);
if (asset->weapDef->physCollmap) builder->loadAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, asset->weapDef->physCollmap);
if (asset->weapDef->tracerType) builder->loadAsset(Game::XAssetType::ASSET_TYPE_TRACER, asset->weapDef->tracerType);
if (asset->weapDef->viewFlashEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->viewFlashEffect);
if (asset->weapDef->worldFlashEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->worldFlashEffect);
if (asset->weapDef->viewShellEjectEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->viewShellEjectEffect);
if (asset->weapDef->worldShellEjectEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->worldShellEjectEffect);
if (asset->weapDef->viewLastShotEjectEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->viewLastShotEjectEffect);
if (asset->weapDef->worldLastShotEjectEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->worldLastShotEjectEffect);
if (asset->weapDef->projExplosionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projExplosionEffect);
if (asset->weapDef->projDudEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projDudEffect);
if (asset->weapDef->projTrailEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projTrailEffect);
if (asset->weapDef->projBeaconEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projBeaconEffect);
if (asset->weapDef->projIgnitionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projIgnitionEffect);
if (asset->weapDef->turretOverheatEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->turretOverheatEffect);
}
void IWeapon::writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer)
{
AssertSize(Game::WeaponDef, 0x684);
Game::WeaponDef* dest = buffer->dest<Game::WeaponDef>();
buffer->save(def);
if (def->szOverlayName)
{
buffer->saveString(def->szOverlayName);
Utils::Stream::ClearPointer(&dest->szOverlayName);
}
if (def->gunXModel)
{
buffer->align(Utils::Stream::ALIGN_4);
Game::XModel** pointerTable = buffer->dest<Game::XModel*>();
buffer->saveMax(16 * sizeof(Game::XModel*));
for (int i = 0; i < 16; i++)
{
if (!def->gunXModel[i])
{
pointerTable[i] = NULL;
continue;
}
pointerTable[i] = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->gunXModel[i]).model;
}
Utils::Stream::ClearPointer(&dest->gunXModel);
}
if (def->handXModel)
{
dest->handXModel = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->handXModel).model;
}
if (def->szXAnimsRightHanded)
{
buffer->align(Utils::Stream::ALIGN_4);
int* poinerTable = buffer->dest<int>();
buffer->saveMax(37 * sizeof(char*)); // array of 37 string pointers
for (int i = 0; i < 37; i++)
{
if (!def->szXAnimsRightHanded[i]) {
poinerTable[i] = 0; // clear poiner if there isn't a string here
continue;
}
// save string if it is present
buffer->saveString(def->szXAnimsRightHanded[i]);
}
Utils::Stream::ClearPointer(&dest->szXAnimsRightHanded);
}
if (def->szXAnimsLeftHanded)
{
buffer->align(Utils::Stream::ALIGN_4);
int* poinerTable = buffer->dest<int>();
buffer->saveMax(37 * sizeof(char*)); // array of 37 string pointers
for (int i = 0; i < 37; i++)
{
if (!def->szXAnimsLeftHanded[i]) {
poinerTable[i] = 0; // clear poiner if there isn't a string here
continue;
}
// save string if it is present
buffer->saveString(def->szXAnimsLeftHanded[i]);
}
Utils::Stream::ClearPointer(&dest->szXAnimsLeftHanded);
}
if (def->szModeName)
{
buffer->saveString(def->szModeName);
Utils::Stream::ClearPointer(&dest->szModeName);
}
if (def->notetrackSoundMapKeys)
{
buffer->align(Utils::Stream::ALIGN_2);
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackSoundMapKeys, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackSoundMapKeys);
}
if (def->notetrackSoundMapValues)
{
buffer->align(Utils::Stream::ALIGN_2);
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackSoundMapValues, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackSoundMapValues);
}
if (def->notetrackRumbleMapKeys)
{
buffer->align(Utils::Stream::ALIGN_2);
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackRumbleMapKeys, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackRumbleMapKeys);
}
if (def->notetrackRumbleMapValues)
{
buffer->align(Utils::Stream::ALIGN_2);
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(def->notetrackRumbleMapValues, 16);
for (int i = 0; i < 16; i++) {
builder->mapScriptString(&scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->notetrackRumbleMapValues);
}
if (def->viewFlashEffect)
{
dest->viewFlashEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->viewFlashEffect).fx;
}
if (def->worldFlashEffect)
{
dest->worldFlashEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->worldFlashEffect).fx;
}
// This is compressed because I don't want to write the same piece of code 47 times
// TODO: verify that this is saving the aliases correctly because the old code looks wrong and this looks right but the old code worked so go figure
Game::snd_alias_list_t ** allSounds = &def->pickupSound;
Game::snd_alias_list_t ** allSoundsDest = &dest->pickupSound;
for (int i = 0; i < 47; i++) {
if (!allSounds[i]) continue;
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveMax(sizeof(Game::snd_alias_list_t*));
buffer->saveString(allSounds[i]->aliasName);
Utils::Stream::ClearPointer(&allSoundsDest[i]);
}
if (def->bounceSound)
{
buffer->align(Utils::Stream::ALIGN_4);
int* ptrs = buffer->dest<int>();
buffer->saveMax(37 * sizeof(Game::snd_alias_list_t*));
for (int i = 0; i < 37; i++)
{
if (!def->bounceSound[i])
{
ptrs[i] = 0;
continue;
}
buffer->saveMax(sizeof(Game::snd_alias_list_t*));
buffer->saveString(def->bounceSound[i]->aliasName);
}
Utils::Stream::ClearPointer(&dest->bounceSound);
}
if (def->viewShellEjectEffect)
{
dest->viewShellEjectEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->viewShellEjectEffect).fx;
}
if (def->worldShellEjectEffect)
{
dest->worldShellEjectEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->worldShellEjectEffect).fx;
}
if (def->viewLastShotEjectEffect)
{
dest->viewLastShotEjectEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->viewLastShotEjectEffect).fx;
}
if (def->worldLastShotEjectEffect)
{
dest->worldLastShotEjectEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->worldLastShotEjectEffect).fx;
}
if (def->reticleCenter)
{
dest->reticleCenter = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->reticleCenter).material;
}
if (def->reticleSide)
{
dest->reticleSide = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->reticleSide).material;
}
if (def->worldModel)
{
buffer->align(Utils::Stream::ALIGN_4);
Game::XModel** pointerTable = buffer->dest<Game::XModel*>();
buffer->saveMax(16 * sizeof(Game::XModel*));
for (int i = 0; i < 16; i++)
{
if (!def->worldModel[i])
{
pointerTable[i] = NULL;
continue;
}
pointerTable[i] = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->worldModel[i]).model;
}
Utils::Stream::ClearPointer(&dest->worldModel);
}
if (def->worldClipModel)
{
dest->worldClipModel = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->worldClipModel).model;
}
if (def->rocketModel)
{
dest->rocketModel = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->rocketModel).model;
}
if (def->knifeModel)
{
dest->knifeModel = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->knifeModel).model;
}
if (def->worldKnifeModel)
{
dest->worldKnifeModel = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->worldKnifeModel).model;
}
if (def->hudIcon)
{
dest->hudIcon = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->hudIcon).material;
}
if (def->pickupIcon)
{
dest->pickupIcon = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->pickupIcon).material;
}
if (def->ammoCounterIcon)
{
dest->ammoCounterIcon = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->ammoCounterIcon).material;
}
if (def->szAmmoName)
{
buffer->saveString(def->szAmmoName);
Utils::Stream::ClearPointer(&dest->szAmmoName);
}
if (def->szClipName)
{
buffer->saveString(def->szClipName);
Utils::Stream::ClearPointer(&dest->szClipName);
}
if (def->szSharedAmmoCapName)
{
buffer->saveString(def->szSharedAmmoCapName);
Utils::Stream::ClearPointer(&dest->szSharedAmmoCapName);
}
if (def->overlayMaterial)
{
dest->overlayMaterial = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->overlayMaterial).material;
}
if (def->overlayMaterialLowRes)
{
dest->overlayMaterialLowRes = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->overlayMaterialLowRes).material;
}
if (def->overlayMaterialEMP)
{
dest->overlayMaterialEMP = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->overlayMaterialEMP).material;
}
if (def->overlayMaterialEMPLowRes)
{
dest->overlayMaterialEMPLowRes = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, def->overlayMaterialEMPLowRes).material;
}
if (def->physCollmap)
{
dest->physCollmap = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, def->overlayMaterialEMPLowRes).physCollmap;
}
if (def->projectileModel)
{
dest->projectileModel = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_XMODEL, def->projectileModel).model;
}
if (def->projExplosionEffect)
{
dest->projExplosionEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->projExplosionEffect).fx;
}
if (def->projDudEffect)
{
dest->projDudEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->projDudEffect).fx;
}
if (def->projExplosionSound)
{
buffer->saveMax(4);
buffer->saveString(def->projExplosionSound->aliasName);
Utils::Stream::ClearPointer(&dest->projExplosionSound);
}
if (def->projDudSound)
{
buffer->saveMax(4);
buffer->saveString(def->projDudSound->aliasName);
Utils::Stream::ClearPointer(&dest->projDudSound);
}
if (def->parallelBounce)
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(def->parallelBounce, 31);
Utils::Stream::ClearPointer(&dest->parallelBounce);
}
if (def->perpendicularBounce)
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(def->perpendicularBounce, 31);
Utils::Stream::ClearPointer(&dest->perpendicularBounce);
}
if (def->projTrailEffect)
{
dest->projTrailEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->projTrailEffect).fx;
}
if (def->projBeaconEffect)
{
dest->projBeaconEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->projBeaconEffect).fx;
}
if (def->projIgnitionEffect)
{
dest->projIgnitionEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->projIgnitionEffect).fx;
}
if (def->projIgnitionSound)
{
buffer->saveMax(4);
buffer->saveString(def->projIgnitionSound->aliasName);
Utils::Stream::ClearPointer(&dest->projIgnitionSound);
}
if (def->accuracyGraphName[0])
{
buffer->saveString(def->accuracyGraphName[0]);
Utils::Stream::ClearPointer(&dest->accuracyGraphName[0]);
}
if (def->originalAccuracyGraphKnots[0])
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(def->originalAccuracyGraphKnots[0], def->originalAccuracyGraphKnotCount[0]);
Utils::Stream::ClearPointer(&dest->originalAccuracyGraphKnots[0]);
}
if (def->accuracyGraphName[1])
{
buffer->saveString(def->accuracyGraphName[1]);
Utils::Stream::ClearPointer(&dest->accuracyGraphName[1]);
}
if (def->originalAccuracyGraphKnots[1])
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(def->originalAccuracyGraphKnots[1], def->originalAccuracyGraphKnotCount[1]);
Utils::Stream::ClearPointer(&dest->originalAccuracyGraphKnots[1]);
}
if (def->szUseHintString)
{
buffer->saveString(def->szUseHintString);
Utils::Stream::ClearPointer(&dest->szUseHintString);
}
if (def->dropHintString)
{
buffer->saveString(def->dropHintString);
Utils::Stream::ClearPointer(&dest->dropHintString);
}
if (def->szScript)
{
buffer->saveString(def->szScript);
Utils::Stream::ClearPointer(&dest->szScript);
}
if (def->locationDamageMultipliers)
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(def->locationDamageMultipliers, 20);
Utils::Stream::ClearPointer(&dest->locationDamageMultipliers);
}
if (def->fireRumble)
{
buffer->saveString(def->fireRumble);
Utils::Stream::ClearPointer(&dest->fireRumble);
}
if (def->meleeImpactRumble)
{
buffer->saveString(def->meleeImpactRumble);
Utils::Stream::ClearPointer(&dest->meleeImpactRumble);
}
if (def->tracerType)
{
dest->tracerType = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_TRACER, def->tracerType).tracerDef;
}
if (def->turretOverheatSound)
{
buffer->saveMax(4);
buffer->saveString(def->turretOverheatSound->aliasName);
Utils::Stream::ClearPointer(&dest->turretOverheatSound);
}
if (def->turretOverheatEffect)
{
dest->turretOverheatEffect = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_FX, def->turretOverheatEffect).fx;
}
if (def->turretBarrelSpinRumble)
{
buffer->saveString(def->turretBarrelSpinRumble);
Utils::Stream::ClearPointer(&dest->turretBarrelSpinRumble);
}
if (def->turretBarrelSpinMaxSnd)
{
buffer->saveMax(4);
buffer->saveString(def->turretBarrelSpinMaxSnd->aliasName);
Utils::Stream::ClearPointer(&dest->turretBarrelSpinMaxSnd);
}
for (int i = 0; i < 4; i++) {
if (!def->turretBarrelSpinUpSnd[i]) continue;
buffer->saveMax(4);
buffer->saveString(def->turretBarrelSpinUpSnd[i]->aliasName);
Utils::Stream::ClearPointer(&dest->turretBarrelSpinUpSnd[i]);
}
for (int i = 0; i < 4; i++) {
if (!def->turretBarrelSpinDownSnd[i]) continue;
buffer->saveMax(4);
buffer->saveString(def->turretBarrelSpinDownSnd[i]->aliasName);
Utils::Stream::ClearPointer(&dest->turretBarrelSpinDownSnd[i]);
}
if (def->missileConeSoundAlias)
{
buffer->saveMax(4);
buffer->saveString(def->missileConeSoundAlias->aliasName);
Utils::Stream::ClearPointer(&dest->missileConeSoundAlias);
}
if (def->missileConeSoundAliasAtBase)
{
buffer->saveMax(4);
buffer->saveString(def->missileConeSoundAliasAtBase->aliasName);
Utils::Stream::ClearPointer(&dest->missileConeSoundAliasAtBase);
}
}
void IWeapon::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::WeaponCompleteDef, 0x74);
Utils::Stream* buffer = builder->getBuffer();
Game::WeaponCompleteDef* asset = header.weapon;
Game::WeaponCompleteDef* dest = buffer->dest<Game::WeaponCompleteDef>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
if (asset->szInternalName)
{
buffer->saveString(builder->getAssetName(this->getType(), asset->szInternalName));
Utils::Stream::ClearPointer(&dest->szInternalName);
}
if (asset->weapDef)
{
buffer->align(Utils::Stream::ALIGN_4);
IWeapon::writeWeaponDef(asset->weapDef, builder, buffer);
Utils::Stream::ClearPointer(&dest->weapDef);
}
if (asset->szDisplayName)
{
buffer->saveString(asset->szDisplayName);
Utils::Stream::ClearPointer(&dest->szDisplayName);
}
if (asset->hideTags)
{
buffer->align(Utils::Stream::ALIGN_2);
unsigned short* scriptStringTable = buffer->dest<unsigned short>();
buffer->saveArray(asset->hideTags, 32);
for (int i = 0; i < 32; i++) {
builder->mapScriptString(&scriptStringTable[i]);
}
Utils::Stream::ClearPointer(&dest->hideTags);
}
if (asset->szXAnims)
{
buffer->align(Utils::Stream::ALIGN_4);
int* poinerTable = buffer->dest<int>();
buffer->saveMax(37 * sizeof(char*)); // array of 37 string pointers
for (int i = 0; i < 37; i++)
{
if (!asset->szXAnims[i]) {
poinerTable[i] = 0; // clear poiner if there isn't a string here
continue;
}
// save string if it is present
buffer->saveString(asset->szXAnims[i]);
}
Utils::Stream::ClearPointer(&dest->szXAnims);
}
if (asset->szAltWeaponName)
{
buffer->saveString(asset->szAltWeaponName);
Utils::Stream::ClearPointer(&dest->szAltWeaponName);
}
if (asset->killIcon)
{
dest->killIcon = builder->saveSubAsset(Game::ASSET_TYPE_MATERIAL, asset->killIcon).material;
}
if (asset->dpadIcon)
{
dest->dpadIcon = builder->saveSubAsset(Game::ASSET_TYPE_MATERIAL, asset->dpadIcon).material;
}
if (asset->accuracyGraphKnots[0])
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(asset->accuracyGraphKnots[0], asset->accuracyGraphKnotCount[0]);
Utils::Stream::ClearPointer(&dest->accuracyGraphKnots[0]);
}
if (asset->accuracyGraphKnots[1])
{
buffer->align(Utils::Stream::ALIGN_4);
buffer->saveArray(asset->accuracyGraphKnots[1], asset->accuracyGraphKnotCount[1]);
Utils::Stream::ClearPointer(&dest->accuracyGraphKnots[1]);
}
buffer->popBlock();
}
}

View File

@ -0,0 +1,17 @@
#pragma once
namespace Assets
{
class IWeapon : public Components::AssetHandler::IAsset
{
public:
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_WEAPON; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
private:
void writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer);
};
}

View File

@ -17,7 +17,7 @@ namespace Assets
}
}
void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader)
void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder)
{
if (surf->vertInfo.vertsBlend)
{
@ -48,13 +48,23 @@ namespace Assets
}
// Access index block
if (surf->triIndices)
{
surf->triIndices = reader->readArray<unsigned short>(surf->triCount * 3);
}
if (surf->triIndices)
{
void* oldPtr = surf->triIndices;
surf->triIndices = reader->readArray<unsigned short>(surf->triCount * 3);
if (builder->getAllocator()->isPointerMapped(oldPtr))
{
surf->triIndices = builder->getAllocator()->getPointer<unsigned short>(oldPtr);
}
else
{
builder->getAllocator()->mapPointer(oldPtr, surf->triIndices);
}
}
}
void IXModel::loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader)
void IXModel::loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder)
{
if (asset->name)
{
@ -67,7 +77,7 @@ namespace Assets
for (int i = 0; i < asset->numsurfs; ++i)
{
this->loadXSurface(&asset->surfs[i], reader);
this->loadXSurface(&asset->surfs[i], reader, builder);
}
}
}
@ -165,7 +175,7 @@ namespace Assets
if (asset->lodInfo[i].modelSurfs)
{
asset->lodInfo[i].modelSurfs = reader.readObject<Game::XModelSurfs>();
this->loadXModelSurfs(asset->lodInfo[i].modelSurfs, &reader);
this->loadXModelSurfs(asset->lodInfo[i].modelSurfs, &reader, builder);
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { asset->lodInfo[i].modelSurfs });
asset->lodInfo[i].surfs = asset->lodInfo[i].modelSurfs->surfs;
@ -293,7 +303,7 @@ namespace Assets
}
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { asset->physCollmap });
//asset->physCollmap = nullptr;
// asset->physCollmap = nullptr;
}
}

View File

@ -12,8 +12,9 @@ namespace Assets
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
private:
void loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader);
void loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader);
std::map<void*, void*> triIndicies;
void loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder);
void loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder);
void loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader);
};
}

View File

@ -81,12 +81,16 @@ namespace Assets
// Access index block
buffer->pushBlock(Game::XFILE_BLOCK_INDEX);
if (surf->triIndices)
{
buffer->align(Utils::Stream::ALIGN_16);
buffer->saveArray(surf->triIndices, surf->triCount * 3);
Utils::Stream::ClearPointer(&destSurf->triIndices);
}
if (builder->hasPointer(surf->triIndices))
{
destSurf->triIndices = builder->getPointer(surf->triIndices);
}
else
{
buffer->align(Utils::Stream::ALIGN_16);
buffer->saveArray(surf->triIndices, surf->triCount * 3);
Utils::Stream::ClearPointer(&destSurf->triIndices);
}
buffer->popBlock();
}

View File

@ -2,6 +2,21 @@
namespace Assets
{
std::unordered_map<std::string, Game::menuDef_t*> ImenuDef_t::LoadedMenus;
void ImenuDef_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
{
// load from disk
auto menus = Components::Menus::LoadMenu(Utils::String::VA("ui_mp/%s.menu", name.data()));
if (menus.size() == 0) return;
if (menus.size() > 1) Components::Logger::Print("Menu '%s' on disk has more than one menudef in it. Only saving the first one\n", name.data());
header->menu = menus[0].second;
}
void ImenuDef_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
Game::menuDef_t *asset = header.menu;
@ -37,6 +52,10 @@ namespace Assets
AssertSize(Game::ExpressionSupportingData, 24);
Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("ExpressionSupportingData");
#endif
buffer->align(Utils::Stream::ALIGN_4);
Game::ExpressionSupportingData *dest = buffer->dest<Game::ExpressionSupportingData>();
@ -107,13 +126,21 @@ namespace Assets
}
}
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
void ImenuDef_t::save_Statement_s(Game::Statement_s* asset, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::Statement_s, 24);
AssertSize(Game::expressionEntry, 12);
Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("Statement_s");
#endif
// Write header data
Game::Statement_s *dest = buffer->dest<Game::Statement_s>();
buffer->save(asset);
@ -121,6 +148,9 @@ namespace Assets
// Write statement entries
if (asset->entries)
{
#ifdef WRITE_LOGS
buffer->enterStruct("statement entries");
#endif
buffer->align(Utils::Stream::ALIGN_4);
// Write entries
@ -130,6 +160,9 @@ namespace Assets
// Loop through entries
for (int i = 0; i < asset->numEntries; ++i)
{
#ifdef WRITE_LOGS
buffer->enterStruct("entry");
#endif
if (asset->entries[i].type)
{
switch (asset->entries[i].data.operand.dataType)
@ -159,13 +192,23 @@ namespace Assets
break;
}
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
if (asset->supportingData)
{
this->save_ExpressionSupportingData(asset->supportingData, builder);
Utils::Stream::ClearPointer(&dest->supportingData);
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
void ImenuDef_t::save_MenuEventHandlerSet(Game::MenuEventHandlerSet* asset, Components::ZoneBuilder::Zone* builder)
@ -173,6 +216,10 @@ namespace Assets
AssertSize(Game::MenuEventHandlerSet, 8);
Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("MenuEventHandlerSet");
#endif
// Write header data
Game::MenuEventHandlerSet *destset = buffer->dest<Game::MenuEventHandlerSet>();
buffer->save(asset);
@ -191,6 +238,9 @@ namespace Assets
if (asset->eventHandlers[i])
{
buffer->align(Utils::Stream::ALIGN_4);
#ifdef WRITE_LOGS
buffer->enterStruct("MenuEventHandler");
#endif
// Write menu event handler
Game::MenuEventHandler *dest = buffer->dest<Game::MenuEventHandler>();
@ -278,11 +328,17 @@ namespace Assets
}
break;
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
}
Utils::Stream::ClearPointer(&destset->eventHandlers);
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
void ImenuDef_t::save_ItemKeyHandler(Game::ItemKeyHandler* asset, Components::ZoneBuilder::Zone* builder)
@ -290,6 +346,10 @@ namespace Assets
AssertSize(Game::ItemKeyHandler, 12);
Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("ItemKeyHandler");
#endif
while (asset)
{
// Write header
@ -313,6 +373,9 @@ namespace Assets
// Next key handler
asset = asset->next;
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
#define EVENTHANDLERSET(__indice) \
@ -340,6 +403,10 @@ namespace Assets
Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("itemDefData_t");
#endif
// feeder
if (type == 6)
{
@ -421,6 +488,10 @@ namespace Assets
}
Utils::Stream::ClearPointer(&dest->typeData.data);
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
void ImenuDef_t::save_itemDef_s(Game::itemDef_s *asset, Components::ZoneBuilder::Zone* builder)
@ -430,6 +501,15 @@ namespace Assets
Utils::Stream* buffer = builder->getBuffer();
Game::itemDef_s* dest = buffer->dest<Game::itemDef_s>();
#ifdef WRITE_LOGS
if (asset->window.name)
buffer->enterStruct(Utils::String::VA("itemDef_s: name = '%s'", asset->window.name));
else if (asset->window.background)
buffer->enterStruct(Utils::String::VA("itemDef_s: bg = '%s'", asset->window.background->info.name));
else
buffer->enterStruct("itemDef_s");
#endif
buffer->save(asset);
// window data
@ -458,6 +538,7 @@ namespace Assets
buffer->saveString(asset->dvar);
Utils::Stream::ClearPointer(&dest->dvar);
}
if (asset->dvarTest)
{
buffer->saveString(asset->dvarTest);
@ -478,6 +559,7 @@ namespace Assets
buffer->saveString(asset->enableDvar);
Utils::Stream::ClearPointer(&dest->enableDvar);
}
if (asset->localVar)
{
buffer->saveString(asset->localVar);
@ -500,6 +582,9 @@ namespace Assets
if (asset->floatExpressions)
{
buffer->align(Utils::Stream::ALIGN_4);
#ifdef WRITE_LOGS
buffer->enterStruct("floatExpressions");
#endif
Game::ItemFloatExpression* destExp = buffer->dest<Game::ItemFloatExpression>();
buffer->saveArray(asset->floatExpressions, asset->floatExpressionCount);
@ -508,10 +593,14 @@ namespace Assets
{
buffer->align(Utils::Stream::ALIGN_4);
this->save_Statement_s(asset->floatExpressions[i].expression, builder);
Utils::Stream::ClearPointer(&destExp->expression);
Utils::Stream::ClearPointer(&destExp[i].expression);
}
Utils::Stream::ClearPointer(&dest->floatExpressions);
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
// Statements
@ -519,16 +608,23 @@ namespace Assets
STATEMENT(disabledExp);
STATEMENT(textExp);
STATEMENT(materialExp);
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
}
void ImenuDef_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{
AssertSize(Game::menuDef_t, 400);
#ifdef WRITE_LOGS
buffer->enterStruct("ImenuDef_t");
#endif
Utils::Stream* buffer = builder->getBuffer();
Game::menuDef_t* asset = header.menu;
Game::menuDef_t* dest = buffer->dest<Game::menuDef_t>();
buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -603,6 +699,9 @@ namespace Assets
}
}
}
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
buffer->popBlock();
}

View File

@ -9,7 +9,9 @@ namespace Assets
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
// virtual void load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) override;
virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
static std::unordered_map<std::string, Game::menuDef_t*> LoadedMenus;
private:
template <typename T> void save_windowDef_t(Game::windowDef_t* asset, T* dest, Components::ZoneBuilder::Zone* builder)

View File

@ -8,6 +8,12 @@ namespace Components
Utils::Cryptography::Token Auth::ComputeToken;
Utils::Cryptography::ECC::Key Auth::GuidKey;
std::vector<std::uint64_t> Auth::BannedUids = {
0xf4d2c30b712ac6e3,
0xf7e33c4081337fa3,
0x6f5597f103cc50e9
};
void Auth::Frame()
{
if (Auth::TokenContainer.generating)
@ -53,7 +59,7 @@ namespace Components
Auth::TokenContainer.cancel = false;
}
}
void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len)
{
// Ensure our certificate is loaded
@ -64,6 +70,13 @@ namespace Components
return;
}
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != Auth::BannedUids.end())
{
Auth::GenerateKey();
Logger::SoftError("Your online profile is invalid. A new key has been generated.");
return;
}
std::string connectString(format, len);
Game::SV_Cmd_TokenizeString(connectString.data());
@ -86,7 +99,7 @@ namespace Components
return;
}
if (Steam::Enabled() && !Dvar::Var("cl_anonymous").get<bool>() && Steam::Proxy::SteamUser_)
if (Steam::Enabled() && !Friends::IsInvisible() && !Dvar::Var("cl_anonymous").get<bool>() && Steam::Proxy::SteamUser_)
{
infostr.set("realsteamId", Utils::String::VA("%llX", Steam::Proxy::SteamUser_->GetSteamID().bits));
}
@ -187,6 +200,12 @@ namespace Components
return;
}
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), xuid) != Auth::BannedUids.end())
{
Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7.");
return;
}
if (xuid != Auth::GetKeyHash(connectData.publickey()))
{
Network::Send(address, "error\nXUID doesn't match the certificate!");
@ -268,6 +287,14 @@ namespace Components
}
}
void Auth::GenerateKey()
{
Auth::GuidToken.clear();
Auth::ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey();
}
void Auth::LoadKey(bool force)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
@ -287,10 +314,7 @@ namespace Components
if (!Auth::GuidKey.isValid())
{
Auth::GuidToken.clear();
Auth::ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey();
Auth::GenerateKey();
}
}

View File

@ -13,6 +13,8 @@ namespace Components
static void StoreKey();
static void LoadKey(bool force = false);
static void GenerateKey();
static unsigned __int64 GetKeyHash();
static unsigned __int64 GetKeyHash(const std::string& key);
@ -41,7 +43,8 @@ namespace Components
static Utils::Cryptography::Token GuidToken;
static Utils::Cryptography::Token ComputeToken;
static Utils::Cryptography::ECC::Key GuidKey;
static std::vector<std::uint64_t> BannedUids;
static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len);
static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr);
static void DirectConnectStub();

View File

@ -1,12 +1,94 @@
#include "STDInclude.hpp"
#define KEY_MASK_FIRE 1
#define KEY_MASK_SPRINT 2
#define KEY_MASK_MELEE 4
#define KEY_MASK_RELOAD 16
#define KEY_MASK_LEANLEFT 64
#define KEY_MASK_LEANRIGHT 128
#define KEY_MASK_PRONE 256
#define KEY_MASK_CROUCH 512
#define KEY_MASK_JUMP 1024
#define KEY_MASK_ADS_MODE 2048
#define KEY_MASK_TEMP_ACTION 4096
#define KEY_MASK_HOLDBREATH 8192
#define KEY_MASK_FRAG 16384
#define KEY_MASK_SMOKE 32768
#define KEY_MASK_NIGHTVISION 262144
#define KEY_MASK_ADS 524288
#define KEY_MASK_USE 0x28
#define MAX_G_BOTAI_ENTRIES 18
namespace Components
{
std::vector<std::string> Bots::BotNames;
typedef struct BotMovementInfo_t
{
/* Actions */
int buttons;
/* Movement */
int8 forward;
int8 right;
/* Weapon */
unsigned short weapon;
} BotMovementInfo_t;
static BotMovementInfo_t g_botai[MAX_G_BOTAI_ENTRIES];
struct BotAction_t
{
const char* action;
int key;
};
static const BotAction_t BotActions[] =
{
{ "gostand", KEY_MASK_JUMP },
{ "gocrouch", KEY_MASK_CROUCH },
{ "goprone", KEY_MASK_PRONE },
{ "fire", KEY_MASK_FIRE },
{ "melee", KEY_MASK_MELEE },
{ "frag", KEY_MASK_FRAG },
{ "smoke", KEY_MASK_SMOKE },
{ "reload", KEY_MASK_RELOAD },
{ "sprint", KEY_MASK_SPRINT },
{ "leanleft", KEY_MASK_LEANLEFT },
{ "leanright", KEY_MASK_LEANRIGHT },
{ "ads", KEY_MASK_ADS_MODE },
{ "holdbreath", KEY_MASK_HOLDBREATH },
{ "use", KEY_MASK_USE },
{ "0", 8 },
{ "1", 32 },
{ "2", 65536 },
{ "3", 131072 },
{ "4", 1048576 },
{ "5", 2097152 },
{ "6", 4194304 },
{ "7", 8388608 },
{ "8", 16777216 },
{ "9", 33554432 },
};
unsigned int Bots::GetClientNum(Game::client_s* cl)
{
unsigned int num;
num = ((byte*)cl - (byte*)Game::svs_clients) / sizeof(Game::client_s);
return num;
}
bool Bots::IsValidClientNum(unsigned int cNum)
{
return (cNum >= 0) && (cNum < (unsigned int)*Game::svs_numclients);
}
void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port)
{
static int botId = 0;
const char* botName;
if (Bots::BotNames.empty())
{
@ -27,15 +109,19 @@ namespace Components
}
}
}
if (Bots::BotNames.empty())
{
Bots::BotNames.push_back("bot");
}
}
botId %= Bots::BotNames.size();
strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, Bots::BotNames[botId++].data(), protocol, checksum, statVer, statStuff, port), 0x400);
if (!Bots::BotNames.empty())
{
botId %= Bots::BotNames.size();
botName = Bots::BotNames[botId++].data();
}
else
{
botName = Utils::String::VA("bot%d", ++botId);
}
strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, botName, protocol, checksum, statVer, statStuff, port), 0x400);
}
void Bots::Spawn(unsigned int count)
@ -70,6 +156,241 @@ namespace Components
}
}
void Bots::AddMethods()
{
Script::AddFunction("SetPing", [](Game::scr_entref_t id) // gsc: self SetPing(<int>)
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_INTEGER)
{
Game::Scr_Error("^1SetPing: Needs one integer parameter!\n");
return;
}
auto ping = Game::Scr_GetInt(0);
if (ping < 0 || ping > 999)
{
Game::Scr_Error("^1SetPing: Ping needs to between 0 and 999!\n");
return;
}
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1SetPing: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1SetPing: Need to call on a connected player!\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1SetPing: Can only call on a bot!\n");
return;
}
client->ping = (short)ping;
});
Script::AddFunction("isBot", [](Game::scr_entref_t id) // Usage: <bot> isBot();
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1isBot: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1isBot: Needs to be connected.\n");
return;
}
Game::Scr_AddInt(client->isBot);
});
Script::AddFunction("botStop", [](Game::scr_entref_t id) // Usage: <bot> botStop();
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botStop: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botStop: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botStop: Can only call on a bot!\n");
return;
}
g_botai[clientNum] = { 0 };
g_botai[clientNum].weapon = 1;
});
Script::AddFunction("botWeapon", [](Game::scr_entref_t id) // Usage: <bot> botWeapon(<str>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1botWeapon: Needs one string parameter!\n");
return;
}
auto weapon = Game::Scr_GetString(0);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botWeapon: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botWeapon: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botWeapon: Can only call on a bot!\n");
return;
}
if (weapon == ""s)
{
g_botai[clientNum].weapon = 1;
return;
}
int weapId = Game::G_GetWeaponIndexForName(weapon);
g_botai[clientNum].weapon = (unsigned short)weapId;
});
Script::AddFunction("botAction", [](Game::scr_entref_t id) // Usage: <bot> botAction(<str action>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1botAction: Needs one string parameter!\n");
return;
}
auto action = Game::Scr_GetString(0);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botAction: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botAction: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botAction: Can only call on a bot!\n");
return;
}
if (action[0] != '+' && action[0] != '-')
{
Game::Scr_Error("^1botAction: Sign for action must be '+' or '-'.\n");
return;
}
for (size_t i = 0; i < sizeof(BotActions) / sizeof(BotAction_t); ++i)
{
if (strcmp(&action[1], BotActions[i].action))
continue;
if (action[0] == '+')
g_botai[clientNum].buttons |= BotActions[i].key;
else
g_botai[clientNum].buttons &= ~(BotActions[i].key);
return;
}
Game::Scr_Error("^1botAction: Unknown action.\n");
});
Script::AddFunction("botMovement", [](Game::scr_entref_t id) // Usage: <bot> botMovement(<int>, <int>);
{
if (Game::Scr_GetNumParam() != 2 || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER)
{
Game::Scr_Error("^1botMovement: Needs two integer parameters!\n");
return;
}
auto forwardInt = Game::Scr_GetInt(0);
auto rightInt = Game::Scr_GetInt(1);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botMovement: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botMovement: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botMovement: Can only call on a bot!\n");
return;
}
if (forwardInt > 127)
forwardInt = 127;
if (forwardInt < -127)
forwardInt = -127;
if (rightInt > 127)
rightInt = 127;
if (rightInt < -127)
rightInt = -127;
g_botai[clientNum].forward = (int8)forwardInt;
g_botai[clientNum].right = (int8)rightInt;
});
}
Bots::Bots()
{
// Replace connect string
@ -78,6 +399,50 @@ namespace Components
// Intercept sprintf for the connect string
Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick();
// Stop default behavour of bots spinning and shooting
Utils::Hook(0x627021, 0x4BB9B0, HOOK_CALL).install()->quick();
Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick();
// zero the bot command array
for (int i = 0; i < MAX_G_BOTAI_ENTRIES; i++)
{
g_botai[i] = { 0 };
g_botai[i].weapon = 1; // prevent the bots from defaulting to the 'none' weapon
}
// have the bots perform the command every server frame
Scheduler::OnFrame([]()
{
if (!Game::SV_Loaded())
return;
int time = *Game::svs_time;
int numClients = *Game::svs_numclients;
for (int i = 0; i < numClients; ++i)
{
Game::client_t* client = &Game::svs_clients[i];
if (client->state < 3)
continue;
if (!client->isBot)
continue;
Game::usercmd_s ucmd = { 0 };
ucmd.serverTime = time;
ucmd.buttons = g_botai[i].buttons;
ucmd.forwardmove = g_botai[i].forward;
ucmd.rightmove = g_botai[i].right;
ucmd.weapon = g_botai[i].weapon;
client->deltaMessage = client->outgoingSequence - 1;
Game::SV_ClientThink(client, &ucmd);
}
});
Command::Add("spawnBot", [](Command::Params* params)
{
unsigned int count = 1;
@ -103,6 +468,8 @@ namespace Components
Bots::Spawn(count);
});
Bots::AddMethods();
}
Bots::~Bots()

View File

@ -7,6 +7,8 @@ namespace Components
public:
Bots();
~Bots();
static unsigned int GetClientNum(Game::client_s*);
static bool IsValidClientNum(unsigned int);
private:
static std::vector<std::string> BotNames;
@ -14,5 +16,7 @@ namespace Components
static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port);
static void Spawn(unsigned int count);
static void AddMethods();
};
}

View File

@ -220,6 +220,12 @@ namespace Components
// Table lookup stuff
Utils::Hook(0x62DCC1, CardTitles::TableLookupByRowHookStub).install()->quick();
Utils::Hook::Nop(0x62DCC6, 1);
// This is placed here in case the anticheat has been disabled!
// This checks specifically for launching the process suspended to inject a dll
#if !defined(DISABLE_ANTICHEAT)
AntiCheat::CheckStartupTime();
#endif
}
CardTitles::~CardTitles()

View File

@ -12,7 +12,7 @@ namespace Components
std::lock_guard<std::mutex> _(Changelog::Mutex);
Changelog::Lines.clear();
std::string data = Utils::Cache::GetFile("/iw4/changelog.txt");
std::string data = Utils::Cache::GetFile("/develop/CHANGELOG.md");
if (data.empty())
{

View File

@ -13,7 +13,7 @@ namespace Components
~ClanTags();
private:
static std::string ClanTags::Tags[18];
static std::string Tags[18];
static void DrawPlayerNameOnScoreboard();

View File

@ -0,0 +1,176 @@
#include "STDInclude.hpp"
namespace Components
{
void Client::AddFunctions()
{
//File functions
Script::AddFunction("fileWrite", [](Game::scr_entref_t) // gsc: fileWrite(<filepath>, <string>, <mode>)
{
std::string path = Game::Scr_GetString(0);
auto text = Game::Scr_GetString(1);
auto mode = Game::Scr_GetString(2);
if (path.empty())
{
Game::Com_Printf(0, "^1fileWrite: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s)
{
Game::Com_Printf(0, "^3fileWrite: mode not defined or was wrong, defaulting to 'write'\n");
mode = const_cast<char*>("write");
}
if (mode == "write"s)
{
FileSystem::FileWriter(path).write(text);
}
else if (mode == "append"s)
{
FileSystem::FileWriter(path, true).write(text);
}
});
Script::AddFunction("fileRead", [](Game::scr_entref_t) // gsc: fileRead(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileRead: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileRead: directory traversal is not allowed!\n");
return;
}
}
if (!FileSystem::FileReader(path).exists())
{
Game::Com_Printf(0, "^1fileRead: file not found!\n");
return;
}
Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data());
});
Script::AddFunction("fileExists", [](Game::scr_entref_t) // gsc: fileExists(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileExists: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileExists: directory traversal is not allowed!\n");
return;
}
}
Game::Scr_AddInt(FileSystem::FileReader(path).exists());
});
Script::AddFunction("fileRemove", [](Game::scr_entref_t) // gsc: fileRemove(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileRemove: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileRemove: directory traversal is not allowed!\n");
return;
}
}
auto p = std::filesystem::path(path);
std::string folder = p.parent_path().string();
std::string file = p.filename().string();
Game::Scr_AddInt(FileSystem::DeleteFile(folder, file));
});
}
void Client::AddMethods()
{
// Client methods
Script::AddFunction("getIp", [](Game::scr_entref_t id) // gsc: self getIp()
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
if (client->state >= 3)
{
std::string ip = Game::NET_AdrToString(client->addr);
if (ip.find_first_of(":") != std::string::npos)
ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // erase port
Game::Scr_AddString(ip.data());
}
});
Script::AddFunction("getPing", [](Game::scr_entref_t id) // gsc: self getPing()
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
if (client->state >= 3)
{
int ping = (int)client->ping;
Game::Scr_AddInt(ping);
}
});
}
void Client::AddCommands()
{
Command::Add("NULL", [](Command::Params*)
{
return NULL;
});
}
Client::Client()
{
Client::AddFunctions();
Client::AddMethods();
Client::AddCommands();
}
Client::~Client()
{
}
}

View File

@ -0,0 +1,17 @@
#pragma once
namespace Components
{
class Client : public Component
{
public:
Client();
~Client();
private:
static void AddFunctions();
static void AddMethods();
static void AddCommands();
};
}

View File

@ -25,7 +25,7 @@ namespace Components
char* Command::ClientParams::get(size_t index)
{
if (index >= this->length()) return "";
if (index >= this->length()) return const_cast<char*>("");
return Game::cmd_argv[this->commandId][index];
}
@ -36,7 +36,7 @@ namespace Components
char* Command::ServerParams::get(size_t index)
{
if (index >= this->length()) return "";
if (index >= this->length()) return const_cast<char*>("");
return Game::cmd_argv_sv[this->commandId][index];
}
@ -160,6 +160,10 @@ namespace Components
{
AssertSize(Game::cmd_function_t, 24);
static int toastDurationShort = 1000;
static int toastDurationMedium = 2500;
static int toastDurationLong = 5000;
// Disable native noclip command
Utils::Hook::Nop(0x474846, 5);
@ -169,21 +173,21 @@ namespace Components
if (!Game::CL_IsCgameInitialized() || clientNum >= 18 || clientNum < 0 || !Game::g_entities[clientNum].client)
{
Logger::Print("You are not hosting a match!\n");
Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", 3000);
Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", toastDurationMedium);
return;
}
if (!Dvar::Var("sv_cheats").get<bool>())
{
Logger::Print("Cheats disabled!\n");
Toast::Show("cardicon_stop", "Error", "Cheats disabled!", 3000);
Toast::Show("cardicon_stop", "Error", "Cheats disabled!", toastDurationMedium);
return;
}
Game::g_entities[clientNum].client->flags ^= Game::PLAYER_FLAG_NOCLIP;
Logger::Print("Noclip toggled\n");
Toast::Show("cardicon_abduction", "Success", "Noclip toggled", 3000);
Toast::Show("cardicon_abduction", "Success", "Noclip toggled", toastDurationShort);
});
Command::Add("ufo", [](Command::Params*)
@ -192,21 +196,21 @@ namespace Components
if (!Game::CL_IsCgameInitialized() || clientNum >= 18 || clientNum < 0 || !Game::g_entities[clientNum].client)
{
Logger::Print("You are not hosting a match!\n");
Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", 3000);
Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", toastDurationMedium);
return;
}
if (!Dvar::Var("sv_cheats").get<bool>())
{
Logger::Print("Cheats disabled!\n");
Toast::Show("cardicon_stop", "Error", "Cheats disabled!", 3000);
Toast::Show("cardicon_stop", "Error", "Cheats disabled!", toastDurationMedium);
return;
}
Game::g_entities[clientNum].client->flags ^= Game::PLAYER_FLAG_UFO;
Logger::Print("UFO toggled\n");
Toast::Show("cardicon_abduction", "Success", "UFO toggled", 3000);
Toast::Show("cardicon_abduction", "Success", "UFO toggled", toastDurationShort);
});
Command::Add("setviewpos", [](Command::Params* params)
@ -215,21 +219,21 @@ namespace Components
if (!Game::CL_IsCgameInitialized() || clientNum >= 18 || clientNum < 0 || !Game::g_entities[clientNum].client)
{
Logger::Print("You are not hosting a match!\n");
Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", 3000);
Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", toastDurationMedium);
return;
}
if (!Dvar::Var("sv_cheats").get<bool>())
{
Logger::Print("Cheats disabled!\n");
Toast::Show("cardicon_stop", "Error", "Cheats disabled!", 3000);
Toast::Show("cardicon_stop", "Error", "Cheats disabled!", toastDurationMedium);
return;
}
if (params->length() != 4 && params->length() != 6)
{
Logger::Print("Invalid coordinate specified!\n");
Toast::Show("cardicon_stop", "Error", "Invalid coordinate specified!", 3000);
Toast::Show("cardicon_stop", "Error", "Invalid coordinate specified!", toastDurationMedium);
return;
}
@ -250,7 +254,7 @@ namespace Components
// Logging that will spam the console and screen if people use cinematics
//Logger::Print("Successfully teleported player!\n");
//Toast::Show("cardicon_abduction", "Success", "You have been teleported!", 3000);
//Toast::Show("cardicon_abduction", "Success", "You have been teleported!", toastDurationShort);
});
Command::Add("openLink", [](Command::Params* params)

View File

@ -537,7 +537,7 @@ namespace Components
Console::Console()
{
// Console '%s: %s> ' string
Utils::Hook::Set<char*>(0x5A44B4, "IW4x: " VERSION "> ");
Utils::Hook::Set<const char*>(0x5A44B4, "IW4x: " VERSION "> ");
// Patch console color
static float consoleColor[] = { 0.70f, 1.00f, 0.00f, 1.00f };

View File

@ -748,7 +748,7 @@ namespace Components
{
if (Dedicated::IsEnabled()) return;
Dvar::Register<bool>("r_useD3D9Ex", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use extended d3d9 interface!");
Dvar::Register<bool>("r_useD3D9Ex", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Use extended d3d9 interface!");
// Hook Interface creation
Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub);

View File

@ -120,65 +120,7 @@ namespace Components
if (ev == MG_EV_RECV)
{
size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
fDownload->receivedBytes += bytes;
fDownload->download->downBytes += bytes;
fDownload->download->timeStampBytes += bytes;
double progress = 0;
if (fDownload->download->totalBytes)
{
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
}
static unsigned int dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size();
dlProgress = static_cast<unsigned int>(progress);
static bool framePushed = false;
if (!framePushed)
{
framePushed = true;
Scheduler::Once([]()
{
framePushed = false;
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
});
}
int delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
if (delta > 300)
{
bool doFormat = fDownload->download->lastTimeStamp != 0;
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
size_t dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
int timeLeft = 0;
if (fDownload->download->timeStampBytes)
{
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
timeLeft = static_cast<int>(timeLeftD);
}
if (doFormat)
{
static size_t dlTsBytes;
static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft;
dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes;
Scheduler::Once([]()
{
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
});
}
fDownload->download->timeStampBytes = 0;
}
Download::DownloadProgress(fDownload, bytes);
}
if (ev == MG_EV_HTTP_REPLY)
@ -259,7 +201,7 @@ namespace Components
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
}
Logger::Print("Downloading from url %s", url.data());
Logger::Print("Downloading from url %s\n", url.data());
Download::FileDownload fDownload;
fDownload.file = file;
@ -270,17 +212,42 @@ namespace Components
Utils::String::Replace(url, " ", "%20");
// Just a speedtest ;)
//download->totalBytes = 1048576000;
//url = "http://speed.hetzner.de/1GB.bin";
download->valid = true;
ZeroMemory(&download->mgr, sizeof download->mgr);
/*ZeroMemory(&download->mgr, sizeof download->mgr);
mg_mgr_init(&download->mgr, &fDownload);
mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr);
while (fDownload.downloading && !fDownload.download->terminateThread)
{
mg_mgr_poll(&download->mgr, 0);
mg_mgr_poll(&download->mgr, 100);
}
mg_mgr_free(&download->mgr);
mg_mgr_free(&download->mgr);*/
fDownload.downloading = true;
Utils::WebIO webIO;
webIO.setProgressCallback([&fDownload, &webIO](size_t bytes, size_t)
{
if(!fDownload.downloading || fDownload.download->terminateThread)
{
webIO.cancelDownload();
return;
}
Download::DownloadProgress(&fDownload, bytes - fDownload.receivedBytes);
});
bool result = false;
fDownload.buffer = webIO.get(url, &result);
if (!result) fDownload.buffer.clear();
fDownload.downloading = false;
download->valid = false;
if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash)
@ -430,6 +397,69 @@ namespace Components
return nullptr;
}
void Download::DownloadProgress(FileDownload* fDownload, size_t bytes)
{
fDownload->receivedBytes += bytes;
fDownload->download->downBytes += bytes;
fDownload->download->timeStampBytes += bytes;
static volatile bool framePushed = false;
if (!framePushed)
{
double progress = 0;
if (fDownload->download->totalBytes)
{
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
}
static unsigned int dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size();
dlProgress = static_cast<unsigned int>(progress);
framePushed = true;
Scheduler::Once([]()
{
framePushed = false;
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
});
}
int delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
if (delta > 300)
{
bool doFormat = fDownload->download->lastTimeStamp != 0;
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
int timeLeft = 0;
if (fDownload->download->timeStampBytes)
{
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
timeLeft = static_cast<int>(timeLeftD);
}
if (doFormat)
{
static size_t dlTsBytes;
static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft;
dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes;
Scheduler::Once([]()
{
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
});
}
fDownload->download->timeStampBytes = 0;
}
}
bool Download::VerifyPassword(mg_connection *nc, http_message* message)
{
std::string g_password = Dvar::Var("g_password").get<std::string>();
@ -701,9 +731,11 @@ namespace Components
//if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
Utils::InfoString status = ServerInfo::GetInfo();
Utils::InfoString host = ServerInfo::GetHostInfo();
std::map<std::string, json11::Json> info;
info["status"] = status.to_json();
info["host"] = host.to_json();
std::vector<json11::Json> players;
@ -932,36 +964,35 @@ namespace Components
Download::ScriptDownloads.clear();
});
if (Dedicated::IsEnabled() || Flags::HasFlag("scriptablehttp"))
Script::AddFunction("httpGet", [](Game::scr_entref_t)
{
Script::AddFunction("httpGet", [](Game::scr_entref_t)
if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return;
if (Game::Scr_GetNumParam() < 1) return;
std::string url = Game::Scr_GetString(0);
unsigned int object = Game::AllocObject();
Game::Scr_AddObject(object);
Download::ScriptDownloads.push_back(std::make_shared<ScriptDownload>(url, object));
Game::RemoveRefToObject(object);
});
Script::AddFunction("httpCancel", [](Game::scr_entref_t)
{
if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return;
if (Game::Scr_GetNumParam() < 1) return;
unsigned int object = Game::Scr_GetObject(0);
for (auto& download : Download::ScriptDownloads)
{
if (Game::Scr_GetNumParam() < 1) return;
std::string url = Game::Scr_GetString(0);
unsigned int object = Game::AllocObject();
Game::Scr_AddObject(object);
Download::ScriptDownloads.push_back(std::make_shared<ScriptDownload>(url, object));
Game::RemoveRefToObject(object);
});
Script::AddFunction("httpCancel", [](Game::scr_entref_t)
{
if (Game::Scr_GetNumParam() < 1) return;
unsigned int object = Game::Scr_GetObject(0);
for (auto& download : Download::ScriptDownloads)
if (object == download->getObject())
{
if (object == download->getObject())
{
download->cancel();
break;
}
download->cancel();
break;
}
});
}
}
});
}
Download::~Download()

View File

@ -26,7 +26,7 @@ namespace Components
bool terminateThread;
bool isMap;
bool isPrivate;
mg_mgr mgr;
//mg_mgr mgr;
Network::Address target;
std::string hashedPassword;
std::string mod;
@ -64,7 +64,7 @@ namespace Components
if (this->valid)
{
this->valid = false;
mg_mgr_free(&(this->mgr));
//mg_mgr_free(&(this->mgr));
}
}
};
@ -212,6 +212,8 @@ namespace Components
static bool Terminate;
static bool ServerRunning;
static void DownloadProgress(FileDownload* fDownload, size_t bytes);
static bool VerifyPassword(mg_connection *nc, http_message* message);
static void EventHandler(mg_connection *nc, int ev, void *ev_data);

View File

@ -27,7 +27,7 @@ namespace Components
return const_cast<char*>(this->dvar->current.string);
}
return "";
return const_cast<char*>("");
}
template <> const char* Dvar::Var::get()
{
@ -138,6 +138,10 @@ namespace Components
{
return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description);
}
template<> static Dvar::Var Dvar::Register(const char* name, float value, float min, float max, Dvar::Flag flag, const char* description)
{
return Game::Dvar_RegisterFloat(name, value, min, max, flag.val, description);
}
void Dvar::OnInit(Utils::Slot<Scheduler::Callback> callback)
{
@ -223,6 +227,9 @@ namespace Components
// un-cheat cg_fov and add archive flags
Utils::Hook::Xor<BYTE>(0x4F8E35, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED);
// un-cheat cg_fovscale and add archive flags
Utils::Hook::Xor<BYTE>(0x4F8E68, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED);
// un-cheat cg_debugInfoCornerOffset and add archive flags
Utils::Hook::Xor<BYTE>(0x4F8FC2, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED);
@ -251,6 +258,12 @@ namespace Components
// Hook dvar 'name' registration
Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).install()->quick();
// un-cheat safeArea_* and add archive flags
Utils::Hook::Xor<INT>(0x42E3F5, Game::dvar_flag::DVAR_FLAG_READONLY | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_adjusted_horizontal
Utils::Hook::Xor<INT>(0x42E423, Game::dvar_flag::DVAR_FLAG_READONLY | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_adjusted_vertical
Utils::Hook::Xor<BYTE>(0x42E398, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_horizontal
Utils::Hook::Xor<BYTE>(0x42E3C4, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_vertical
// Don't allow setting cheat protected dvars via menus
Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
Utils::Hook(0x63CA96, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();

View File

@ -76,7 +76,7 @@ namespace Components
errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
}
Exception::SuspendProcess();
//Exception::SuspendProcess();
bool doFullDump = Flags::HasFlag("bigdumps") || Flags::HasFlag("reallybigdumps");
/*if (!doFullDump)
@ -131,15 +131,15 @@ namespace Components
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
#ifndef DISABLE_ANTICHEAT
AntiCheat::InstallLibHook();
#endif
if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
//if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
{
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
#ifndef DISABLE_ANTICHEAT
AntiCheat::InstallLibHook();
#endif
return EXCEPTION_CONTINUE_SEARCH;
}
@ -231,26 +231,6 @@ namespace Components
auto oldHandler = Exception::Hook();
Logger::Print("Old exception handler was 0x%010X.\n", oldHandler);
});
// Check if folder exists && crash-helper exists
if (Utils::IO::DirectoryExists("minidumps\\") && Utils::IO::FileExists("crash-helper.exe"))
{
if (!Utils::IO::DirectoryIsEmpty("minidumps\\"))
{
STARTUPINFOA sInfo;
PROCESS_INFORMATION pInfo;
ZeroMemory(&sInfo, sizeof(sInfo));
ZeroMemory(&pInfo, sizeof(pInfo));
sInfo.cb = sizeof(sInfo);
CreateProcessA("crash-helper.exe", const_cast<char*>(Utils::String::VA("crash-helper.exe %s", VERSION)), nullptr, nullptr, false, NULL, nullptr, nullptr, &sInfo, &pInfo);
if (pInfo.hThread && pInfo.hThread != INVALID_HANDLE_VALUE) CloseHandle(pInfo.hThread);
if (pInfo.hProcess && pInfo.hProcess != INVALID_HANDLE_VALUE) CloseHandle(pInfo.hProcess);
}
}
}
Exception::~Exception()

View File

@ -18,6 +18,7 @@ namespace Components
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
static __declspec(noreturn) void ErrorLongJmp(jmp_buf _Buf, int _Value);
static __declspec(noreturn) void LongJmp(jmp_buf _Buf, int _Value);
static void DebugMinidumpCommand(Command::Params*);
static int MiniDumpType;
static Utils::Hook SetFilterHook;

View File

@ -320,7 +320,7 @@ namespace Components
Zones::SetVersion(*version);
// Allow loading of codo versions
if (*version >= VERSION_ALPHA2 && *version <= 360)
if ((*version >= VERSION_ALPHA2 && *version <= 360) || (*version >= 423 && *version <= 461))
{
*version = XFILE_VERSION;
}
@ -369,7 +369,7 @@ namespace Components
unsigned long outLen = sizeof(FastFiles::CurrentKey);
rsa_import(FastFiles::ZoneKey, sizeof(FastFiles::ZoneKey), &key);
rsa_decrypt_key_ex(encKey, 256, FastFiles::CurrentKey.data, &outLen, nullptr, NULL, hash, (Zones::Version() >= 359 ? 1 : 2), &stat, &key);
rsa_decrypt_key_ex(encKey, 256, FastFiles::CurrentKey.data, &outLen, nullptr, NULL, hash, Zones::Version() >= 359 ? 1 : 2, &stat, &key);
rsa_free(&key);
ctr_start(aes, FastFiles::CurrentKey.iv, FastFiles::CurrentKey.key, sizeof(FastFiles::CurrentKey.key), 0, 0, &FastFiles::CurrentCTR);
@ -484,7 +484,7 @@ namespace Components
}
#endif
void FastFiles::Load_XSurfaceArray(int atStreamStart, int /*count*/)
void FastFiles::Load_XSurfaceArray(int atStreamStart, [[maybe_unused]] int count)
{
// read the actual count from the varXModelSurfs ptr
auto surface = *reinterpret_cast<Game::XModelSurfs**>(0x0112A95C);

View File

@ -172,11 +172,11 @@ namespace Components
return fileList;
}
void FileSystem::DeleteFile(const std::string& folder, const std::string& file)
bool FileSystem::DeleteFile(const std::string& folder, const std::string& file)
{
char path[MAX_PATH] = { 0 };
Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").get<const char*>(), reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&path));
Game::FS_Remove(path);
return Game::FS_Remove(path);
}
int FileSystem::ReadFile(const char* path, char** buffer)
@ -263,10 +263,18 @@ namespace Components
void FileSystem::FsRestartSync(int a1, int a2)
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
Maps::GetUserMap()->freeIwd();
Utils::Hook::Call<void(int, int)>(0x461A50)(a1, a2); // FS_Restart
Maps::GetUserMap()->reloadIwd();
}
void FileSystem::FsShutdownSync(int a1)
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
Maps::GetUserMap()->freeIwd();
Utils::Hook::Call<void(int)>(0x4A46C0)(a1); // FS_Shutdown
}
void FileSystem::DelayLoadImagesSync()
{
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
@ -322,6 +330,10 @@ namespace Components
Utils::Hook(0x4C8609, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart
Utils::Hook(0x5AC68E, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
// Synchronize filesystem stops
Utils::Hook(0x461A55, FileSystem::FsShutdownSync, HOOK_CALL).install()->quick(); // FS_Restart
Utils::Hook(0x4D40DB, FileSystem::FsShutdownSync, HOOK_CALL).install()->quick(); // Com_Quitf
// Synchronize db image loading
Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick();
Utils::Hook(0x4D32BC, FileSystem::LoadTextureSync, HOOK_CALL).install()->quick();

View File

@ -90,7 +90,7 @@ namespace Components
static std::vector<std::string> GetFileList(const std::string& path, const std::string& extension);
static std::vector<std::string> GetSysFileList(const std::string& path, const std::string& extension, bool folders = false);
static void DeleteFile(const std::string& folder, const std::string& file);
static bool DeleteFile(const std::string& folder, const std::string& file);
private:
static std::mutex Mutex;
@ -109,6 +109,7 @@ namespace Components
static void FsStartupSync(const char* a1);
static void FsRestartSync(int a1, int a2);
static void FsShutdownSync(int a1);
static void DelayLoadImagesSync();
static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image);

View File

@ -124,7 +124,7 @@ namespace Components
void Friends::UpdateState(bool force)
{
if (Dvar::Var("cl_anonymous").get<bool>() || !Steam::Enabled()) return;
if (Dvar::Var("cl_anonymous").get<bool>() || Friends::IsInvisible() || !Steam::Enabled()) return;
if (force)
{
@ -186,7 +186,7 @@ namespace Components
{
std::lock_guard<std::recursive_mutex> _(Friends::Mutex);
const unsigned int modId = *reinterpret_cast<unsigned int*>("IW4x") | 0x80000000;
const unsigned int modId = *reinterpret_cast<unsigned int*>(const_cast<char*>("IW4x")) | 0x80000000;
// Split up the list
for (auto entry : Friends::FriendsList)
@ -228,7 +228,7 @@ namespace Components
void Friends::SetPresence(const std::string& key, const std::string& value)
{
if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamUtils && !Dvar::Var("cl_anonymous").get<bool>() && Steam::Enabled())
if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamUtils && !Dvar::Var("cl_anonymous").get<bool>() && !Friends::IsInvisible() && Steam::Enabled())
{
Friends::SetRawPresence(key.data(), value.data());
}
@ -494,6 +494,11 @@ namespace Components
return appId;
}
bool Friends::IsInvisible()
{
return Friends::InitialState == 7;
}
void Friends::UpdateTimeStamp()
{
Friends::SetPresence("iw4x_playing", Utils::String::VA("%d", Steam::SteamUtils()->GetServerRealTime()));
@ -696,10 +701,10 @@ namespace Components
{
if (Steam::Proxy::SteamFriends)
{
Friends::InitialState = Steam::Proxy::SteamFriends->GetPersonaState();
Friends::InitialState = Steam::Proxy::SteamFriends->GetFriendPersonaState(Steam::Proxy::SteamUser_->GetSteamID());
}
if (Dvar::Var("cl_anonymous").get<bool>() || !Steam::Enabled())
if (Dvar::Var("cl_anonymous").get<bool>() || Friends::IsInvisible() || !Steam::Enabled())
{
if (Steam::Proxy::ClientFriends)
{

View File

@ -23,6 +23,8 @@ namespace Components
static int GetGame(SteamID user);
static bool IsInvisible();
private:
#pragma pack(push, 4)
struct FriendRichPresenceUpdate

View File

@ -11,8 +11,8 @@ namespace Components
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled() || Loader::IsPerformingUnitTests()) return;
DWORD oldProtect;
std::uint8_t* module = reinterpret_cast<std::uint8_t*>(GetModuleHandle(nullptr));
VirtualProtect(module + 0x1000, 0x2D6000, PAGE_EXECUTE_READWRITE, &oldProtect);
std::uint8_t* _module = reinterpret_cast<std::uint8_t*>(GetModuleHandle(nullptr));
VirtualProtect(_module + 0x1000, 0x2D6000, PAGE_EXECUTE_READWRITE, &oldProtect);
#ifdef COMPILE_IW4MVM
client_main::Init();
@ -26,7 +26,7 @@ namespace Components
}
});
VirtualProtect(module + 0x1000, 0x2D6000, PAGE_EXECUTE_READ, &oldProtect);
VirtualProtect(_module + 0x1000, 0x2D6000, PAGE_EXECUTE_READ, &oldProtect);
}
IW4MVM::~IW4MVM()

View File

@ -185,15 +185,23 @@ namespace Components
"Dasfonia",
"Deity",
"Dizzy",
"Dss0",
"H3X1C",
"HardNougat",
"Homura",
"INeedGames",
"Killera",
"Lithium",
"OneFourOne",
"quaK",
"RaidMax",
"Revo",
"RezTech",
"Shadow the Hedgehog",
"Slykuiper",
"st0rm",
"VVLNT",
"X3RX35"
};
static const char* specials[] =
@ -202,7 +210,7 @@ namespace Components
"aerosoul94",
"ReactIW4",
"IW4Play",
"Rocket V2",
"V2",
"luckyy"
};

View File

@ -0,0 +1,451 @@
#include "STDInclude.hpp"
namespace Components
{
class MapDumper
{
public:
MapDumper(Game::GfxWorld* world) : world_(world)
{
}
void dump()
{
if (!this->world_) return;
Logger::Print("Exporting '%s'...\n", this->world_->baseName);
this->parseVertices();
this->parseFaces();
this->parseStaticModels();
this->write();
}
private:
struct Vertex
{
Game::vec3_t coordinate;
Game::vec2_t texture;
Game::vec3_t normal;
};
struct Face
{
int a{};
int b{};
int c{};
};
struct FaceList
{
std::vector<Face> indices{};
};
class File
{
public:
File() {}
File(const std::string& file)
{
Utils::IO::WriteFile(file, {});
this->stream_ = std::ofstream(file, std::ofstream::out);
}
void append(const std::string& str)
{
this->stream_.write(str.data(), str.size());
}
private:
std::ofstream stream_{};
};
Game::GfxWorld* world_{};
std::vector<Vertex> vertices_{};
std::unordered_map<Game::Material*, FaceList> faces_{};
std::vector<Game::Material*> facesOrder_{};
File object_{};
File material_{};
void transformAxes(Game::vec3_t& vec) const
{
std::swap(vec[0], vec[1]);
std::swap(vec[1], vec[2]);
}
void parseVertices()
{
Logger::Print("Parsing vertices...\n");
for (unsigned int i = 0; i < this->world_->draw.vertexCount; ++i)
{
const auto* vertex = &this->world_->draw.vd.vertices[i];
Vertex v{};
v.coordinate[0] = vertex->xyz[0];
v.coordinate[1] = vertex->xyz[1];
v.coordinate[2] = vertex->xyz[2];
this->transformAxes(v.coordinate);
v.texture[0] = vertex->texCoord[0];
v.texture[1] = -vertex->texCoord[1];
Game::Vec3UnpackUnitVec(vertex->normal, &v.normal);
this->transformAxes(v.normal);
this->vertices_.push_back(v);
}
}
void parseFaces()
{
Logger::Print("Parsing faces...\n");
for (unsigned int i = 0; i < this->world_->dpvs.staticSurfaceCount; ++i)
{
const auto* surface = &this->world_->dpvs.surfaces[i];
const unsigned int vertOffset = surface->tris.firstVertex + 1;
const unsigned int indexOffset = surface->tris.baseIndex;
auto& f = this->getFaceList(surface->material);
for (unsigned short j = 0; j < surface->tris.triCount; ++j)
{
Face face{};
face.a = this->world_->draw.indices[indexOffset + j * 3 + 0] + vertOffset;
face.b = this->world_->draw.indices[indexOffset + j * 3 + 1] + vertOffset;
face.c = this->world_->draw.indices[indexOffset + j * 3 + 2] + vertOffset;
f.indices.push_back(face);
}
}
}
FaceList& getFaceList(Game::Material* material)
{
auto& faceList = this->faces_[material];
if (this->facesOrder_.size() < this->faces_.size())
{
this->facesOrder_.push_back(material);
}
return faceList;
}
void performWorldTransformation(const Game::GfxPackedPlacement& placement, Vertex& v) const
{
Game::MatrixVecMultiply(placement.axis, v.normal, v.normal);
Game::Vec3Normalize(v.normal);
Game::MatrixVecMultiply(placement.axis, v.coordinate, v.coordinate);
v.coordinate[0] = v.coordinate[0] * placement.scale + placement.origin[0];
v.coordinate[1] = v.coordinate[1] * placement.scale + placement.origin[1];
v.coordinate[2] = v.coordinate[2] * placement.scale + placement.origin[2];
}
std::vector<Vertex> parseSurfaceVertices(const Game::XSurface* surface, const Game::GfxPackedPlacement& placement)
{
std::vector<Vertex> vertices;
for (unsigned short j = 0; j < surface->vertCount; j++)
{
const auto *vertex = &surface->verts0[j];
Vertex v{};
v.coordinate[0] = vertex->xyz[0];
v.coordinate[1] = vertex->xyz[1];
v.coordinate[2] = vertex->xyz[2];
// Why...
Game::Vec2UnpackTexCoords(vertex->texCoord, &v.texture);
std::swap(v.texture[0], v.texture[1]);
v.texture[1] *= -1;
Game::Vec3UnpackUnitVec(vertex->normal, &v.normal);
this->performWorldTransformation(placement, v);
this->transformAxes(v.coordinate);
this->transformAxes(v.normal);
vertices.push_back(v);
}
return vertices;
}
std::vector<Face> parseSurfaceFaces(const Game::XSurface* surface) const
{
std::vector<Face> faces;
for (unsigned short j = 0; j < surface->triCount; ++j)
{
Face face{};
face.a = surface->triIndices[j * 3 + 0];
face.b = surface->triIndices[j * 3 + 1];
face.c = surface->triIndices[j * 3 + 2];
faces.push_back(face);
}
return faces;
}
void removeVertex(const int index, std::vector<Face>& faces, std::vector<Vertex>& vertices) const
{
vertices.erase(vertices.begin() + index);
for (auto &face : faces)
{
if (face.a > index) --face.a;
if (face.b > index) --face.b;
if (face.c > index) --face.c;
}
}
void filterSurfaceVertices(std::vector<Face>& faces, std::vector<Vertex>& vertices) const
{
for (auto i = 0; i < int(vertices.size()); ++i)
{
auto referenced = false;
for (const auto &face : faces)
{
if (face.a == i || face.b == i || face.c == i)
{
referenced = true;
break;
}
}
if (!referenced)
{
this->removeVertex(i--, faces, vertices);
}
}
}
void parseStaticModel(Game::GfxStaticModelDrawInst* model)
{
for (unsigned char i = 0; i < model->model->numsurfs; ++i)
{
this->getFaceList(model->model->materialHandles[i]);
}
const auto* lod = &model->model->lodInfo[model->model->numLods - 1];
const auto baseIndex = this->vertices_.size() + 1;
const auto surfIndex = lod->surfIndex;
assert(lod->modelSurfs->numsurfs <= model->model->numsurfs);
for (unsigned short i = 0; i < lod->modelSurfs->numsurfs; ++i)
{
// TODO: Something is still wrong about the models. Probably baseTriIndex and baseVertIndex might help
const auto* surface = &lod->modelSurfs->surfs[i];
auto faces = this->parseSurfaceFaces(surface);
auto vertices = this->parseSurfaceVertices(surface, model->placement);
this->filterSurfaceVertices(faces, vertices);
auto& f = this->getFaceList(model->model->materialHandles[i + surfIndex]);
for (const auto& vertex : vertices)
{
this->vertices_.push_back(vertex);
}
for (auto face : faces)
{
face.a += baseIndex;
face.b += baseIndex;
face.c += baseIndex;
f.indices.push_back(std::move(face));
}
}
}
void parseStaticModels()
{
Logger::Print("Parsing static models...\n");
for (unsigned i = 0u; i < this->world_->dpvs.smodelCount; ++i)
{
this->parseStaticModel(this->world_->dpvs.smodelDrawInsts + i);
}
}
void write()
{
this->object_ = File(Utils::String::VA("raw/mapdump/%s/%s.obj", this->world_->baseName, this->world_->baseName));
this->material_ = File(Utils::String::VA("raw/mapdump/%s/%s.mtl", this->world_->baseName, this->world_->baseName));
this->object_.append("# Generated by IW4x\n");
this->object_.append("# Credit to SE2Dev for his D3DBSP Tool\n");
this->object_.append(Utils::String::VA("o %s\n", this->world_->baseName));
this->object_.append(Utils::String::VA("mtllib %s.mtl\n\n", this->world_->baseName));
this->material_.append("# IW4x MTL File\n");
this->material_.append("# Credit to SE2Dev for his D3DBSP Tool\n");
this->writeVertices();
this->writeFaces();
Logger::Print("Writing files...\n");
this->object_ = {};
this->material_ = {};
}
void writeVertices()
{
Logger::Print("Writing vertices...\n");
this->object_.append("# Vertices\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("v %.6f %.6f %.6f\n", vertex.coordinate[0], vertex.coordinate[1], vertex.coordinate[2]));
}
Logger::Print("Writing texture coordinates...\n");
this->object_.append("\n# Texture coordinates\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("vt %.6f %.6f\n", vertex.texture[0], vertex.texture[1]));
}
Logger::Print("Writing normals...\n");
this->object_.append("\n# Normals\n");
for (const auto& vertex : this->vertices_)
{
this->object_.append(Utils::String::VA("vn %.6f %.6f %.6f\n", vertex.normal[0], vertex.normal[1], vertex.normal[2]));
}
this->object_.append("\n");
}
void writeMaterial(Game::Material* material)
{
std::string name = material->info.name;
const auto pos = name.find_last_of('/');
if (pos != std::string::npos)
{
name = name.substr(pos + 1);
}
this->object_.append(Utils::String::VA("usemtl %s\n", name.data()));
this->object_.append("s off\n");
Game::GfxImage *image = nullptr;
for (char l = 0; l < material->textureCount; ++l)
{
if (material->textureTable[l].nameStart == 'c' && material->textureTable[l].nameEnd == 'p')
{
image = material->textureTable[l].u.image; // Hopefully our colorMap
}
}
if (!image)
{
Logger::Print("Failed to get color map for material: %s\n", material->info.name);
return;
}
// TODO: This is still wrong.
if (image->mapType == 5 && false)
{
for (auto i = 0; i < 6; ++i)
{
IDirect3DSurface9* surface = nullptr;
image->texture.cubemap->GetCubeMapSurface(D3DCUBEMAP_FACES(i), 0, &surface);
if (surface)
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s_%i.png", this->world_->baseName, image->name, i);
D3DXSaveSurfaceToFileA(_name.data(), D3DXIFF_PNG, surface, nullptr, nullptr);
surface->Release();
}
}
}
else
{
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", this->world_->baseName, image->name);
D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr);
}
this->material_.append(Utils::String::VA("\nnewmtl %s\n", name.data()));
this->material_.append("Ka 1.0000 1.0000 1.0000\n");
this->material_.append("Kd 1.0000 1.0000 1.0000\n");
this->material_.append("illum 1\n");
this->material_.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name));
this->material_.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name));
}
void writeFaces()
{
Logger::Print("Writing faces...\n");
Utils::IO::CreateDir(Utils::String::VA("raw/mapdump/%s/textures", this->world_->baseName));
this->material_.append(Utils::String::VA("# Material count: %d\n", this->faces_.size()));
this->object_.append("# Faces\n");
for (const auto& material : this->facesOrder_)
{
this->writeMaterial(material);
const auto& faces = this->getFaceList(material);
for (const auto& index : faces.indices)
{
const int a = index.a;
const int b = index.b;
const int c = index.c;
this->object_.append(Utils::String::VA("f %d/%d/%d %d/%d/%d %d/%d/%d\n", a, a, a, b, b, b, c, c, c));
}
this->object_.append("\n");
}
}
};
MapDump::MapDump()
{
Command::Add("dumpmap", [](Command::Params*)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
Logger::Print("DirectX needs to be enabled, please start a client to use this command!\n");
return;
}
Game::GfxWorld* world = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_GFXWORLD, [](Game::XAssetHeader header, void* world)
{
*reinterpret_cast<Game::GfxWorld**>(world) = header.gfxWorld;
}, &world, false);
if (world)
{
MapDumper dumper(world);
dumper.dump();
Logger::Print("Map '%s' exported!\n", world->baseName);
}
else
{
Logger::Print("No map loaded, unable to dump anything!\n");
}
});
}
}

View File

@ -0,0 +1,10 @@
#pragma once
namespace Components
{
class MapDump : public Component
{
public:
MapDump();
};
}

View File

@ -128,7 +128,7 @@ namespace Components
Maps::SPMap = false;
Maps::CurrentMainZone = zoneInfo->name;
Maps::CurrentDependencies.clear();
for (auto i = Maps::DependencyList.begin(); i != Maps::DependencyList.end(); ++i)
{
@ -149,7 +149,7 @@ namespace Components
std::vector<Game::XZoneInfo> data;
Utils::Merge(&data, zoneInfo, zoneCount);
Game::XZoneInfo team;
team.allocFlags = zoneInfo->allocFlags;
team.freeFlags = zoneInfo->freeFlags;
@ -252,12 +252,8 @@ namespace Components
asset.mapEnts->entityString = const_cast<char*>(mapEntities.data());
asset.mapEnts->numEntityChars = mapEntities.size() + 1;
}
// Apply new mapEnts
// This doesn't work, entities are spawned before the patch file is loaded
//Maps::OverrideMapEnts(asset.mapEnts);
}
// This is broken
if ((type == Game::XAssetType::ASSET_TYPE_MENU || type == Game::XAssetType::ASSET_TYPE_MENULIST) && Zones::Version() >= 359)
{
@ -271,14 +267,13 @@ namespace Components
Logger::Print("Waiting for database...\n");
while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms);
if (!Utils::String::StartsWith(Maps::CurrentMainZone, "mp_") || Maps::SPMap)
if (!Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP].gameWorldMp || !Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP].gameWorldMp->name ||
!Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP].gameWorldMp->g_glassData || Maps::SPMap)
{
return Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP].gameWorldSp[0].g_glassData;
}
else
{
return Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP].gameWorldMp[0].g_glassData;
return Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP].gameWorldSp->g_glassData;
}
return Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP].gameWorldMp->g_glassData;
}
__declspec(naked) void Maps::GetWorldDataStub()
@ -529,140 +524,6 @@ namespace Components
}
}
#if defined(DEBUG) && defined(ENABLE_DXSDK)
// Credit to SE2Dev, as we shouldn't share the code, keep that in debug mode!
void Maps::ExportMap(Game::GfxWorld* world)
{
Utils::Memory::Allocator allocator;
if (!world) return;
Logger::Print("Exporting '%s'...\n", world->baseName);
std::string mtl;
mtl.append("# IW4x MTL File\n");
mtl.append("# Credit to SE2Dev for his D3DBSP Tool\n");
std::string map;
map.append("# Generated by IW4x\n");
map.append("# Credit to SE2Dev for his D3DBSP Tool\n");
map.append(Utils::String::VA("o %s\n", world->baseName));
map.append(Utils::String::VA("mtllib %s.mtl\n\n", world->baseName));
Logger::Print("Writing vertices...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
float x = world->draw.vd.vertices[i].xyz[1];
float y = world->draw.vd.vertices[i].xyz[2];
float z = world->draw.vd.vertices[i].xyz[0];
map.append(Utils::String::VA("v %.6f %.6f %.6f\n", x, y, z));
}
map.append("\n");
Logger::Print("Writing texture coordinates...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
map.append(Utils::String::VA("vt %.6f %.6f\n", world->draw.vd.vertices[i].texCoord[0], -world->draw.vd.vertices[i].texCoord[1]));
}
Logger::Print("Writing normals...\n");
for (unsigned int i = 0; i < world->draw.vertexCount; ++i)
{
Game::vec3_t normal;
Game::Vec3UnpackUnitVec(world->draw.vd.vertices[i].normal, &normal);
map.append(Utils::String::VA("vn %.6f %.6f %.6f\n", normal[0], normal[1], normal[2]));
}
map.append("\n");
Logger::Print("Searching materials...\n");
int materialCount = 0;
Game::Material** materials = allocator.allocateArray<Game::Material*>(world->dpvs.staticSurfaceCount);
for (unsigned int i = 0; i < world->dpvs.staticSurfaceCount; ++i)
{
bool isNewMat = true;
for (int j = 0; j < materialCount; ++j)
{
if (world->dpvs.surfaces[i].material == materials[j])
{
isNewMat = false;
break;
}
}
if (isNewMat)
{
materials[materialCount++] = world->dpvs.surfaces[i].material;
}
}
Utils::IO::CreateDir(Utils::String::VA("raw/mapdump/%s/textures", world->baseName));
mtl.append(Utils::String::VA("# Material Count: %d\n", materialCount));
Logger::Print("Exporting materials and faces...\n");
for (int m = 0; m < materialCount; ++m)
{
std::string name = materials[m]->info.name;
auto pos = name.find_last_of("/");
if (pos != std::string::npos)
{
name = name.substr(pos + 1);
}
map.append(Utils::String::VA("\nusemtl %s\n", name.data()));
map.append("s off\n");
Game::GfxImage* image = materials[m]->textureTable[0].u.image;
for (char l = 0; l < materials[m]->textureCount; ++l)
{
if (materials[m]->textureTable[l].nameStart == 'c')
{
if (materials[m]->textureTable[l].nameEnd == 'p')
{
image = materials[m]->textureTable[l].u.image; // Hopefully our colorMap
}
}
}
std::string _name = Utils::String::VA("raw/mapdump/%s/textures/%s.png", world->baseName, image->name);
D3DXSaveTextureToFile(std::wstring(_name.begin(), _name.end()).data(), D3DXIFF_PNG, image->texture.map, NULL);
mtl.append(Utils::String::VA("\nnewmtl %s\n", name.data()));
mtl.append("Ka 1.0000 1.0000 1.0000\n");
mtl.append("Kd 1.0000 1.0000 1.0000\n");
mtl.append("illum 1\n");
mtl.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name));
mtl.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name));
for (unsigned int i = 0; i < world->dpvs.staticSurfaceCount; ++i)
{
if (world->dpvs.surfaces[i].material != materials[m])
continue;
int vertOffset = world->dpvs.surfaces[i].tris.firstVertex + 1;//+1 cus obj starts at 1
int indexOffset = world->dpvs.surfaces[i].tris.baseIndex;
for (unsigned short j = 0; j < world->dpvs.surfaces[i].tris.triCount; ++j)
{
int a = world->draw.indices[indexOffset + j * 3 + 0] + vertOffset;
int b = world->draw.indices[indexOffset + j * 3 + 1] + vertOffset;
int c = world->draw.indices[indexOffset + j * 3 + 2] + vertOffset;
map.append(Utils::String::VA("f %d/%d/%d %d/%d/%d %d/%d/%d\n", a, a, a, b, b, b, c, c, c));
}
}
}
Logger::Print("Writing final files...\n");
Utils::IO::WriteFile(Utils::String::VA("raw/mapdump/%s/%s.mtl", world->baseName, world->baseName), mtl);
Utils::IO::WriteFile(Utils::String::VA("raw/mapdump/%s/%s.obj", world->baseName, world->baseName), map);
}
#endif
void Maps::AddDlc(Maps::DLC dlc)
{
for (auto& pack : Maps::DlcPacks)
@ -821,12 +682,16 @@ namespace Components
Game::dvar_t* Maps::GetSpecularDvar()
{
Game::dvar_t*& r_specular = *reinterpret_cast<Game::dvar_t**>(0x69F0D94);
static Game::dvar_t* r_specularCustomMaps = Game::Dvar_RegisterBool("r_specularCustomMaps", false, Game::DVAR_FLAG_SAVED, "Allows shaders to use phong specular lighting on custom maps");
if (Maps::IsCustomMap())
{
static Game::dvar_t noSpecular;
ZeroMemory(&noSpecular, sizeof noSpecular);
return &noSpecular;
if (!r_specularCustomMaps->current.enabled)
{
static Game::dvar_t noSpecular;
ZeroMemory(&noSpecular, sizeof noSpecular);
return &noSpecular;
}
}
return r_specular;
@ -864,6 +729,17 @@ namespace Components
}
}
void Maps::G_SpawnTurretHook(Game::gentity_s* ent, int unk, int unk2)
{
if (Maps::CurrentMainZone == "mp_backlot_sh"s || Maps::CurrentMainZone == "mp_con_spring"s ||
Maps::CurrentMainZone == "mp_mogadishu_sh"s || Maps::CurrentMainZone == "mp_nightshift_sh"s)
{
return;
}
Utils::Hook::Call<void(Game::gentity_s*, int, int)>(0x408910)(ent, unk, unk2);
}
Maps::Maps()
{
Dvar::OnInit([]()
@ -874,8 +750,8 @@ namespace Components
Maps::AddDlc({ 1, "Stimulus Pack", {"mp_complex", "mp_compact", "mp_storm", "mp_overgrown", "mp_crash"} });
Maps::AddDlc({ 2, "Resurgence Pack", {"mp_abandon", "mp_vacant", "mp_trailerpark", "mp_strike", "mp_fuel2"} });
Maps::AddDlc({ 3, "Nuketown", {"mp_nuked"} });
Maps::AddDlc({ 4, "Classics Pack", {"mp_cross_fire", "mp_cargoship", "mp_bloc"} });
Maps::AddDlc({ 5, "Classics Pack", {"mp_killhouse", "mp_bog_sh"} });
Maps::AddDlc({ 4, "Classics Pack #1", {"mp_cross_fire", "mp_cargoship", "mp_bloc"} });
Maps::AddDlc({ 5, "Classics Pack #2", {"mp_killhouse", "mp_bog_sh"} });
Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} });
Maps::AddDlc({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} });
Maps::AddDlc({ 8, "Recycled Pack", {"mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} });
@ -890,8 +766,7 @@ namespace Components
{
if (pack.index == dlc)
{
News::LaunchUpdater(Utils::String::VA("-dlc %i -c", pack.index));
//ShellExecuteA(nullptr, "open", pack.url.data(), nullptr, nullptr, SW_SHOWNORMAL);
ShellExecute(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW);
return;
}
}
@ -900,6 +775,10 @@ namespace Components
});
});
// disable turrets on CoD:OL 448+ maps for now
Utils::Hook(0x5EE577, Maps::G_SpawnTurretHook, HOOK_CALL).install()->quick();
Utils::Hook(0x44A4D5, Maps::G_SpawnTurretHook, HOOK_CALL).install()->quick();
//#define SORT_SMODELS
#if !defined(DEBUG) || !defined(SORT_SMODELS)
// Don't sort static models
@ -991,33 +870,6 @@ namespace Components
//Maps::AddDependency("co_hunted", "mp_storm");
//Maps::AddDependency("mp_shipment", "mp_shipment_long");
#if defined(DEBUG) && defined(ENABLE_DXSDK)
Command::Add("dumpmap", [](Command::Params*)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
Logger::Print("DirectX needs to be enabled, please start a client to use this command!\n");
return;
}
Game::GfxWorld* world = nullptr;
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_GFXWORLD, [](Game::XAssetHeader header, void* world)
{
*reinterpret_cast<Game::GfxWorld**>(world) = header.gfxWorld;
}, &world, false);
if (world)
{
Maps::ExportMap(world);
Logger::Print("Map '%s' exported!\n", world->baseName);
}
else
{
Logger::Print("No map loaded, unable to dump anything!\n");
}
});
#endif
// Allow hiding specific smodels
Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick();

View File

@ -100,10 +100,6 @@ namespace Components
static void AddDlc(DLC dlc);
static void UpdateDlcStatus();
#if defined(DEBUG) && defined(ENABLE_DXSDK)
static void ExportMap(Game::GfxWorld* world);
#endif
static void PrepareUsermap(const char* mapname);
static void SpawnServerStub();
static void LoadMapLoadscreenStub();
@ -123,5 +119,6 @@ namespace Components
static Game::dvar_t* GetSpecularDvar();
static void SetSpecularStub1();
static void SetSpecularStub2();
static void G_SpawnTurretHook(Game::gentity_s* ent, int unk, int unk2);
};
}

View File

@ -60,7 +60,7 @@ namespace Components
int handle = Menus::ReserveSourceHandle();
if (!Menus::IsValidSourceHandle(handle)) return 0; // No free source slot!
Game::script_t *script = Menus::LoadMenuScript(name, buffer);
Game::script_t* script = Menus::LoadMenuScript(name, buffer);
if (!script)
{
@ -70,7 +70,7 @@ namespace Components
script->next = nullptr;
Game::source_t *source = allocator->allocate<Game::source_t>();
Game::source_t* source = allocator->allocate<Game::source_t>();
if (!source)
{
Game::FreeMemory(script);
@ -83,7 +83,7 @@ namespace Components
source->defines = nullptr;
source->indentstack = nullptr;
source->skip = 0;
source->definehash = static_cast<Game::define_t**>(allocator->allocate(4096));
source->definehash = static_cast<Game::define_t * *>(allocator->allocate(4096));
Game::sourceFiles[handle] = source;
@ -173,6 +173,69 @@ namespace Components
return menu;
}
Game::MenuList* Menus::LoadCustomMenuList(const std::string& menu, Utils::Memory::Allocator* allocator)
{
std::vector<std::pair<bool, Game::menuDef_t*>> menus;
FileSystem::File menuFile(menu);
if (!menuFile.exists()) return nullptr;
Game::pc_token_t token;
int handle = Menus::LoadMenuSource(menu, menuFile.getBuffer());
if (Menus::IsValidSourceHandle(handle))
{
while (true)
{
ZeroMemory(&token, sizeof(token));
if (!Game::PC_ReadTokenHandle(handle, &token) || token.string[0] == '}')
{
break;
}
if (!_stricmp(token.string, "loadmenu"))
{
Game::PC_ReadTokenHandle(handle, &token);
Utils::Merge(&menus, Menus::LoadMenu(Utils::String::VA("ui_mp\\%s.menu", token.string)));
}
if (!_stricmp(token.string, "menudef"))
{
Game::menuDef_t* menudef = Menus::ParseMenu(handle);
if (menudef) menus.push_back({ true, menudef }); // Custom menu
}
}
Menus::FreeMenuSource(handle);
}
if (menus.empty()) return nullptr;
// Allocate new menu list
Game::MenuList* list = allocator->allocate<Game::MenuList>();
if (!list) return nullptr;
list->menus = allocator->allocateArray<Game::menuDef_t*>(menus.size());
if (!list->menus)
{
allocator->free(list);
return nullptr;
}
list->name = allocator->duplicateString(menu);
list->menuCount = menus.size();
// Copy new menus
for (unsigned int i = 0; i < menus.size(); ++i)
{
list->menus[i] = menus[i].second;
}
return list;
}
std::vector<std::pair<bool, Game::menuDef_t*>> Menus::LoadMenu(const std::string& menu)
{
std::vector<std::pair<bool, Game::menuDef_t*>> menus;
@ -221,15 +284,15 @@ namespace Components
if (menus.empty())
{
// // Try loading the original menu, if we can't load our custom one
// Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu;
//
// if (originalMenu)
// {
// menus.push_back({ false, originalMenu });
// }
// else
// {
// // Try loading the original menu, if we can't load our custom one
// Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu;
//
// if (originalMenu)
// {
// menus.push_back({ false, originalMenu });
// }
// else
// {
menus.push_back({ false, menudef }); // Native menu
// }
}
@ -313,7 +376,7 @@ namespace Components
}
}
if(increment) ++i;
if (increment) ++i;
}
Utils::Merge(menus, newMenus);
@ -337,7 +400,7 @@ namespace Components
for (auto menu : Menus::CustomMenus)
{
bool hasMenu = false;
for (auto &loadedMenu : menus)
for (auto& loadedMenu : menus)
{
if (loadedMenu.second->window.name == menu)
{
@ -383,7 +446,7 @@ namespace Components
if (!Menus::IsValidSourceHandle(handle)) return;
Game::source_t *source = Game::sourceFiles[handle];
Game::source_t* source = Game::sourceFiles[handle];
while (source->scriptstack)
{
@ -517,7 +580,7 @@ namespace Components
// EDIT: We might also remove the old instances inside RemoveMenu
// EDIT2: Removing old instances without having a menu to replace them with might leave a nullptr
// EDIT3: Wouldn't it be better to check if the new menu we're trying to load has already been loaded and not was not deallocated and return that one instead of loading a new one?
void Menus::OverrideMenu(Game::menuDef_t *menu)
void Menus::OverrideMenu(Game::menuDef_t* menu)
{
if (!menu || !menu->window.name) return;
std::string name = menu->window.name;
@ -580,12 +643,12 @@ namespace Components
Menus::MenuList.clear();
}
Game::XAssetHeader Menus::MenuLoad(Game::XAssetType /*type*/, const std::string& filename)
Game::XAssetHeader Menus::MenuFindHook(Game::XAssetType /*type*/, const std::string& filename)
{
return { Game::Menus_FindByName(Game::uiContext, filename.data()) };
}
Game::XAssetHeader Menus::MenuFileLoad(Game::XAssetType type, const std::string& filename)
Game::XAssetHeader Menus::MenuListFindHook(Game::XAssetType type, const std::string& filename)
{
Game::XAssetHeader header = { nullptr };
@ -643,7 +706,7 @@ namespace Components
return header;
}
bool Menus::IsMenuVisible(Game::UiContext *dc, Game::menuDef_t *menu)
bool Menus::IsMenuVisible(Game::UiContext* dc, Game::menuDef_t* menu)
{
if (menu && menu->window.name)
{
@ -664,7 +727,7 @@ namespace Components
return Game::Menu_IsVisible(dc, menu);
}
void Menus::RemoveMenuFromContext(Game::UiContext *dc, Game::menuDef_t *menu)
void Menus::RemoveMenuFromContext(Game::UiContext* dc, Game::menuDef_t* menu)
{
// Search menu in context
int i = 0;
@ -694,6 +757,54 @@ namespace Components
Menus::CustomMenus.push_back(menu);
}
void Menus::RegisterCustomMenusHook()
{
Game::UiContext* uiInfoArray = (Game::UiContext*)0x62E2858;
// Game::MenuList list;
Utils::Hook::Call<void()>(0x401700)(); // call original load functions
//Game::XAssetHeader header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/iw4x.txt");
//if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name)))
//{
// // Utils::Hook::Call<void(void*, Game::MenuList*, int)>(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus
// std::memcpy(&list, header.data, sizeof(Game::MenuList));
// for (int i = 0; i < uiInfoArray->menuCount; i++)
// {
// for (int j = 0; j < list.menuCount; j++)
// {
// if (!list.menus[j]) continue; // skip already used entries
// if (!stricmp(list.menus[j]->window.name, uiInfoArray->Menus[i]->window.name))
// {
// uiInfoArray->Menus[i] = list.menus[j]; // overwrite UiContext pointer
// list.menus[j] = nullptr; // clear entries that already exist so we don't add them later
// }
// }
// }
// for (int i = 0; i < list.menuCount; i++)
// {
// if (list.menus[i])
// {
// uiInfoArray->Menus[uiInfoArray->menuCount++] = list.menus[i];
// }
// }
//}
for (int i = 0; i < uiInfoArray->menuCount; i++)
{
OutputDebugStringA(Utils::String::VA("%s\n", uiInfoArray->Menus[i]->window.name));
}
/*
header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/mod.txt");
if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name)))
{
Utils::Hook::Call<void(void*, Game::MenuList*, int)>(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus
}
*/
}
Menus::Menus()
{
if (Dedicated::IsEnabled()) return;
@ -702,97 +813,101 @@ namespace Components
Menus::FreeEverything();
// Intercept asset finding
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuLoad);
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, Menus::MenuFileLoad);
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuFindHook);
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, Menus::MenuListFindHook);
// Don't open connect menu
//Utils::Hook::Nop(0x428E48, 5);
// register custom menufiles if they exist
Utils::Hook(0x4A58C3, Menus::RegisterCustomMenusHook, HOOK_CALL).install()->quick();
// Use the connect menu open call to update server motds
Utils::Hook(0x428E48, []()
{
if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost)
{
Dvar::Var("didyouknow").set(Party::GetMotd());
}
}, HOOK_CALL).install()->quick();
if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost)
{
Dvar::Var("didyouknow").set(Party::GetMotd());
}
}, HOOK_CALL).install()->quick();
// Intercept menu painting
Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick();
// Intercept menu painting
Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick();
// disable the 2 new tokens in ItemParse_rect
Utils::Hook::Set<BYTE>(0x640693, 0xEB);
// disable the 2 new tokens in ItemParse_rect
Utils::Hook::Set<BYTE>(0x640693, 0xEB);
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5);
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5);
//make Com_Error and similar go back to main_text instead of menu_xboxlive.
Utils::Hook::SetString(0x6FC790, "main_text");
//make Com_Error and similar go back to main_text instead of menu_xboxlive.
Utils::Hook::SetString(0x6FC790, "main_text");
Command::Add("openmenu", [](Command::Params* params)
{
if (params->length() != 2)
{
Logger::Print("USAGE: openmenu <menu name>\n");
return;
}
Command::Add("openmenu", [](Command::Params* params)
{
if (params->length() != 2)
{
Logger::Print("USAGE: openmenu <menu name>\n");
return;
}
// Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus.
if (Dvar::Var("cl_ingame").get<bool>())
{
Game::Key_SetCatcher(0, 16);
}
// Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus.
if (Dvar::Var("cl_ingame").get<bool>())
{
Game::Key_SetCatcher(0, 16);
}
Game::Menus_OpenByName(Game::uiContext, params->get(1));
});
Game::Menus_OpenByName(Game::uiContext, params->get(1));
});
Command::Add("reloadmenus", [](Command::Params*)
{
// Close all menus
Game::Menus_CloseAll(Game::uiContext);
Command::Add("reloadmenus", [](Command::Params*)
{
// Close all menus
Game::Menus_CloseAll(Game::uiContext);
// Free custom menus
Menus::FreeEverything();
// Free custom menus
Menus::FreeEverything();
// Only disconnect if in-game, context is updated automatically!
if (Game::CL_IsCgameInitialized())
{
Game::Cbuf_AddText(0, "disconnect\n");
}
else
{
// Reinitialize ui context
Utils::Hook::Call<void()>(0x401700)();
// Only disconnect if in-game, context is updated automatically!
if (Game::CL_IsCgameInitialized())
{
Game::Cbuf_AddText(0, "disconnect\n");
}
else
{
// Reinitialize ui context
Utils::Hook::Call<void()>(0x401700)();
// Reopen main menu
Game::Menus_OpenByName(Game::uiContext, "main_text");
}
});
// Reopen main menu
Game::Menus_OpenByName(Game::uiContext, "main_text");
}
});
#ifndef DISABLE_ANTICHEAT
Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner2);
Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner2);
#endif
Command::Add("mp_QuickMessage", [](Command::Params*)
{
Command::Execute("openmenu quickmessage");
});
Command::Add("mp_QuickMessage", [](Command::Params*)
{
Command::Execute("openmenu quickmessage");
});
// Define custom menus here
Menus::Add("ui_mp/changelog.menu");
Menus::Add("ui_mp/theater_menu.menu");
Menus::Add("ui_mp/pc_options_multi.menu");
Menus::Add("ui_mp/pc_options_game.menu");
Menus::Add("ui_mp/stats_reset.menu");
Menus::Add("ui_mp/stats_unlock.menu");
Menus::Add("ui_mp/security_increase_popmenu.menu");
Menus::Add("ui_mp/mod_download_popmenu.menu");
Menus::Add("ui_mp/popup_friends.menu");
Menus::Add("ui_mp/menu_first_launch.menu");
Menus::Add("ui_mp/startup_messages.menu");
Menus::Add("ui_mp/pc_store.menu");
Menus::Add("ui_mp/iw4x_credits.menu");
Menus::Add("ui_mp/resetclass.menu");
// Define custom menus here
Menus::Add("ui_mp/changelog.menu");
Menus::Add("ui_mp/theater_menu.menu");
Menus::Add("ui_mp/pc_options_multi.menu");
Menus::Add("ui_mp/pc_options_game.menu");
Menus::Add("ui_mp/stats_reset.menu");
Menus::Add("ui_mp/stats_unlock.menu");
Menus::Add("ui_mp/security_increase_popmenu.menu");
Menus::Add("ui_mp/mod_download_popmenu.menu");
Menus::Add("ui_mp/popup_friends.menu");
Menus::Add("ui_mp/menu_first_launch.menu");
Menus::Add("ui_mp/startup_messages.menu");
Menus::Add("ui_mp/pc_store.menu");
Menus::Add("ui_mp/iw4x_credits.menu");
Menus::Add("ui_mp/resetclass.menu");
Menus::Add("ui_mp/popup_customtitle.menu");
}
Menus::~Menus()

View File

@ -15,18 +15,21 @@ namespace Components
static void Add(const std::string& menu);
static Game::MenuList* LoadCustomMenuList(const std::string& menu, Utils::Memory::Allocator* allocator);
static std::vector<std::pair<bool, Game::menuDef_t*>> LoadMenu(Game::menuDef_t* menudef);
static std::vector<std::pair<bool, Game::menuDef_t*>> LoadMenu(const std::string& file);
private:
static std::unordered_map<std::string, Game::menuDef_t*> MenuList;
static std::unordered_map<std::string, Game::MenuList*> MenuListList;
static std::vector<std::string> CustomMenus;
static Game::XAssetHeader MenuLoad(Game::XAssetType type, const std::string& filename);
static Game::XAssetHeader MenuFileLoad(Game::XAssetType type, const std::string& filename);
static Game::XAssetHeader MenuFindHook(Game::XAssetType type, const std::string& filename);
static Game::XAssetHeader MenuListFindHook(Game::XAssetType type, const std::string& filename);
static Game::MenuList* LoadMenuList(Game::MenuList* menuList);
static Game::MenuList* LoadScriptMenu(const char* menu);
static std::vector<std::pair<bool, Game::menuDef_t*>> LoadMenu(Game::menuDef_t* menudef);
static std::vector<std::pair<bool, Game::menuDef_t*>> LoadMenu(const std::string& file);
static void SafeMergeMenus(std::vector<std::pair<bool, Game::menuDef_t*>>* menus, std::vector<std::pair<bool, Game::menuDef_t*>> newMenus);
static Game::script_t* LoadMenuScript(const std::string& name, const std::string& buffer);
@ -47,11 +50,13 @@ namespace Components
static void RemoveMenuList(const std::string& menuList);
static void RemoveMenuList(Game::MenuList* menuList);
static void OverrideMenu(Game::menuDef_t *menu);
static void OverrideMenu(Game::menuDef_t* menu);
static bool IsMenuVisible(Game::UiContext *dc, Game::menuDef_t *menu);
static bool IsMenuVisible(Game::UiContext* dc, Game::menuDef_t* menu);
static void RemoveMenuFromContext(Game::UiContext *dc, Game::menuDef_t *menu);
static void RemoveMenuFromContext(Game::UiContext* dc, Game::menuDef_t* menu);
static void RegisterCustomMenusHook();
// Ugly!
static int KeywordHash(char* key);

View File

@ -336,6 +336,12 @@ namespace Components
}
}
void Network::NET_DeferPacketToClientStub(Game::netadr_t* from, Game::msg_t* msg)
{
if (msg->cursize > 0 && msg->cursize <= 1404)
Game::NET_DeferPacketToClient(from, msg);
}
Network::Network()
{
AssertSize(Game::netadr_t, 20);
@ -358,7 +364,7 @@ namespace Components
Utils::Hook::Set<BYTE>(0x4050A5, 125);
// Parse port as short in Net_AddrToString
Utils::Hook::Set<char*>(0x4698E3, "%u.%u.%u.%u:%hu");
Utils::Hook::Set<const char*>(0x4698E3, "%u.%u.%u.%u:%hu");
// Install startup handler
Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).install()->quick();
@ -372,6 +378,9 @@ namespace Components
// Install packet deploy hook
Utils::Hook::RedirectJump(0x5AA713, Network::DeployPacketStub);
// Fix packets causing buffer overflow
Utils::Hook(0x6267E3, Network::NET_DeferPacketToClientStub, HOOK_CALL).install()->quick();
Network::Handle("resolveAddress", [](Address address, const std::string& /*data*/)
{
Network::SendRaw(address, address.getString());

View File

@ -58,7 +58,7 @@ namespace Components
static void Handle(const std::string& packet, Utils::Slot<Callback> callback);
static void OnStart(Utils::Slot<CallbackRaw> callback);
// Send quake-styled binary data
static void Send(Address target, const std::string& data);
static void Send(Game::netsrc_t type, Address target, const std::string& data);
@ -88,6 +88,7 @@ namespace Components
static void NetworkStartStub();
static void PacketErrorCheck();
static void NET_DeferPacketToClientStub(Game::netadr_t* from, Game::msg_t* msg);
};
}

View File

@ -6,9 +6,6 @@ namespace Components
{
bool News::Terminate;
std::thread News::Thread;
std::string News::UpdaterArgs;
std::string News::UpdaterHash;
std::mutex News::UpdaterMutex;
bool News::unitTest()
{
@ -33,165 +30,18 @@ namespace Components
return result;
}
void News::ExitProcessStub(unsigned int exitCode)
{
std::this_thread::sleep_for(10ms);
STARTUPINFOA sInfo;
PROCESS_INFORMATION pInfo;
ZeroMemory(&sInfo, sizeof(sInfo));
ZeroMemory(&pInfo, sizeof(pInfo));
sInfo.cb = sizeof(sInfo);
CreateProcessA("updater.exe", const_cast<char*>(Utils::String::VA("updater.exe %s", News::UpdaterArgs.data())), nullptr, nullptr, false, NULL, nullptr, nullptr, &sInfo, &pInfo);
if (pInfo.hThread && pInfo.hThread != INVALID_HANDLE_VALUE) CloseHandle(pInfo.hThread);
if (pInfo.hProcess && pInfo.hProcess != INVALID_HANDLE_VALUE) CloseHandle(pInfo.hProcess);
TerminateProcess(GetCurrentProcess(), exitCode);
}
bool News::GetLatestUpdater()
{
std::lock_guard<std::mutex> _(News::UpdaterMutex);
if (Utils::IO::FileExists("updater.exe"))
{
// Generate hash of local updater.exe
std::string localUpdater = Utils::IO::ReadFile("updater.exe");
localUpdater = Utils::Cryptography::SHA1::Compute(localUpdater, true);
static Utils::Time::Interval updateInterval;
if (News::UpdaterHash.empty() || updateInterval.elapsed(15min)) // Check for updater Update every 15 mins max
{
updateInterval.update();
std::string data = Utils::Cache::GetFile("/json/updater"); // {"updater.exe":{"SHA1":"*HASH*"}}
std::string error;
json11::Json listData = json11::Json::parse(data, error);
if (error.empty() || listData.is_object())
{
News::UpdaterHash = listData["updater.exe"]["SHA1"].string_value();
}
}
if (!News::UpdaterHash.empty() && localUpdater != News::UpdaterHash)
{
remove("updater.exe");
}
}
if (!Utils::IO::FileExists("updater.exe"))
{
return News::DownloadUpdater();
}
return true;
}
bool News::DownloadUpdater()
{
std::string data = Utils::Cache::GetFile("/iw4/updater.exe");
if (!data.empty())
{
Utils::IO::WriteFile("updater.exe", data);
return true;
}
return false;
}
const char* News::GetNewsText()
{
return Localization::Get("MPUI_MOTD_TEXT");
}
void News::CheckForUpdate()
{
std::string _client = Utils::Cache::GetFile("/json/client");
if (!_client.empty())
{
std::string error;
json11::Json client = json11::Json::parse(_client.data(), error);
int revisionNumber;
if (client["revision"].is_number())
{
revisionNumber = client["revision"].int_value();
}
else if (client["revision"].is_string())
{
revisionNumber = atoi(client["revision"].string_value().data());
}
else return;
Dvar::Var("cl_updateversion").get<Game::dvar_t*>()->current.integer = revisionNumber;
Dvar::Var("cl_updateavailable").get<Game::dvar_t*>()->current.enabled = (revisionNumber > REVISION);
// if there is an update then show the toast, but only once
static bool showToast = true;
if (revisionNumber > REVISION && showToast)
{
showToast = false;
Scheduler::OnReady([]()
{
Toast::Show("cardicon_gears", "^4Update Available", "There is an update available for your client!", 5000);
});
}
}
}
void News::LaunchUpdater(const std::string& params)
{
if (News::Updating()) return;
News::UpdaterArgs = params;
Localization::SetTemp("MENU_RECONNECTING_TO_PARTY", "Downloading updater");
Command::Execute("openmenu popup_reconnectingtoparty", true);
// Run the updater on shutdown
Utils::Hook::Set(0x6D72A0, News::ExitProcessStub);
std::thread([]()
{
if (News::GetLatestUpdater())
{
Console::SetSkipShutdown();
Command::Execute("wait 300; quit;", false);
}
else
{
Localization::ClearTemp();
News::UpdaterArgs.clear();
Command::Execute("closemenu popup_reconnectingtoparty", false);
Game::ShowMessageBox("Failed to download the updater!", "Error");
}
}).detach();
}
bool News::Updating()
{
return !News::UpdaterArgs.empty();
}
News::News()
{
News::UpdaterArgs.clear();
News::UpdaterHash.clear();
if (ZoneBuilder::IsEnabled() || Dedicated::IsEnabled()) return; // Maybe also dedi?
Dvar::Register<bool>("g_firstLaunch", true, Game::DVAR_FLAG_SAVED, "");
Dvar::Register<int>("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_FLAG_WRITEPROTECTED, "Current version number.");
Dvar::Register<int>("cl_updateversion", 0, 0, -1, Game::DVAR_FLAG_WRITEPROTECTED, "New version number.");
Dvar::Register<bool>("cl_updateavailable", false, Game::DVAR_FLAG_WRITEPROTECTED, "New update is available.");
UIScript::Add("checkFirstLaunch", [](UIScript::Token)
{
@ -209,14 +59,18 @@ namespace Components
UIScript::Add("visitWiki", [](UIScript::Token)
{
Utils::OpenUrl(Utils::Cache::GetStaticUrl("/wiki/"));
//Utils::OpenUrl(Utils::Cache::GetStaticUrl("/wiki/"));
Utils::OpenUrl("https://github.com/Jawesome99/IW4x/wiki");
});
UIScript::Add("visitDiscord", [](UIScript::Token)
{
Utils::OpenUrl("https://discord.gg/sKeVmR3");
});
Localization::Set("MPUI_CHANGELOG_TEXT", "Loading...");
Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFAULT);
//News::GetLatestUpdater();
// make newsfeed (ticker) menu items not cut off based on safe area
Utils::Hook::Nop(0x63892D, 5);
@ -224,17 +78,6 @@ namespace Components
Utils::Hook::Nop(0x6388BB, 2); // skip the "if (item->text[0] == '@')" localize check
Utils::Hook(0x6388C1, News::GetNewsText, HOOK_CALL).install()->quick();
Command::Add("checkforupdate", [](Command::Params*)
{
News::CheckForUpdate();
});
Command::Add("getautoupdate", [](Command::Params*)
{
if (!Dvar::Var("cl_updateavailable").get<Game::dvar_t*>()->current.enabled) return;
News::LaunchUpdater("-update -c");
});
if (!Utils::IsWineEnvironment() && !Loader::IsPerformingUnitTests())
{
News::Terminate = false;
@ -251,12 +94,8 @@ namespace Components
if (!Loader::IsPerformingUnitTests() && !News::Terminate)
{
News::GetLatestUpdater();
while (!News::Terminate)
{
News::CheckForUpdate();
// Sleep for 3 minutes
for (int i = 0; i < 180 && !News::Terminate; ++i)
{
@ -268,12 +107,6 @@ namespace Components
}
}
News::~News()
{
News::UpdaterArgs.clear();
News::UpdaterHash.clear();
}
void News::preDestroy()
{
News::Terminate = true;

View File

@ -6,27 +6,16 @@ namespace Components
{
public:
News();
~News();
void preDestroy() override;
bool unitTest() override;
static void LaunchUpdater(const std::string& params);
static bool Updating();
private:
static std::string UpdaterArgs;
static std::string UpdaterHash;
static std::thread Thread;
static std::mutex UpdaterMutex;
static bool Terminate;
static bool GetLatestUpdater();
static bool DownloadUpdater();
static void CheckForUpdate();
static void ExitProcessStub(unsigned int exitCode);
static const char* GetNewsText();
};
}

View File

@ -169,7 +169,7 @@ namespace Components
{
if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get<bool>()) return;
if (!Dedicated::IsEnabled() && Game::CL_IsCgameInitialized())
if (!Dedicated::IsEnabled() && *Game::clcState > 0)
{
wasIngame = true;
return; // don't run while ingame because it can still cause lag spikes on lower end PCs
@ -279,25 +279,48 @@ namespace Components
void Node::SendList(Network::Address address)
{
Proto::Node::List list;
list.set_isnode(Dedicated::IsEnabled());
list.set_protocol(PROTOCOL);
list.set_port(Node::GetPort());
std::lock_guard<std::recursive_mutex> _(Node::Mutex);
for (auto& node : Node::Nodes)
{
if (node.isValid())
{
std::string* str = list.add_nodes();
// need to keep the message size below 1404 bytes else recipient will just drop it
std::vector<std::string> nodeListReponseMessages;
sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
for (size_t curNode = 0; curNode < Node::Nodes.size();)
{
Proto::Node::List list;
list.set_isnode(Dedicated::IsEnabled());
list.set_protocol(PROTOCOL);
list.set_port(Node::GetPort());
for (size_t i = 0; i < NODE_MAX_NODES_TO_SEND;)
{
if (curNode >= Node::Nodes.size())
break;
auto node = Node::Nodes.at(curNode++);
if (node.isValid())
{
std::string* str = list.add_nodes();
sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
i++;
}
}
nodeListReponseMessages.push_back(list.SerializeAsString());
}
Session::Send(address, "nodeListResponse", list.SerializeAsString());
size_t i = 0;
for (auto& nodeListData : nodeListReponseMessages)
{
Scheduler::OnDelay([nodeListData, i, address]()
{
NODE_LOG("Sending %d nodeListResponse length to %s\n", nodeListData.length(), address.getCString());
Session::Send(address, "nodeListResponse", nodeListData);
}, NODE_SEND_RATE * i++);
}
}
unsigned short Node::GetPort()

View File

@ -1,6 +1,8 @@
#pragma once
#define NODE_HALFLIFE (3 * 60 * 1000) //3min
#define NODE_MAX_NODES_TO_SEND 64
#define NODE_SEND_RATE 500ms
#ifdef NODE_LOG_MESSAGES
#define NODE_LOG(x, ...) Logger::Print(x, __VA_ARGS__)

View File

@ -210,7 +210,7 @@ namespace Components
// Force xblive_privatematch 0 and rename it
//Utils::Hook::Set<BYTE>(0x420A6A, 4);
Utils::Hook::Set<BYTE>(0x420A6C, 0);
Utils::Hook::Set<char*>(0x420A6E, "xblive_privateserver");
Utils::Hook::Set<const char*>(0x420A6E, "xblive_privateserver");
// Remove migration shutdown, it causes crashes and will be destroyed when erroring anyways
Utils::Hook::Nop(0x5A8E1C, 12);
@ -244,9 +244,9 @@ namespace Components
//Utils::Hook(0x4D5D51, Party::RegisterMinPlayers, HOOK_CALL).install()->quick();
// Set ui_maxclients to sv_maxclients
Utils::Hook::Set<char*>(0x42618F, "sv_maxclients");
Utils::Hook::Set<char*>(0x4D3756, "sv_maxclients");
Utils::Hook::Set<char*>(0x5E3772, "sv_maxclients");
Utils::Hook::Set<const char*>(0x42618F, "sv_maxclients");
Utils::Hook::Set<const char*>(0x4D3756, "sv_maxclients");
Utils::Hook::Set<const char*>(0x5E3772, "sv_maxclients");
// Unlatch maxclient dvars
Utils::Hook::Xor<BYTE>(0x426187, Game::dvar_flag::DVAR_FLAG_LATCHED);

View File

@ -150,7 +150,7 @@ namespace Components
Playlist::Playlist()
{
// Default playlists
Utils::Hook::Set<char*>(0x60B06E, "playlists_default.info");
Utils::Hook::Set<const char*>(0x60B06E, "playlists_default.info");
// disable playlist download function
Utils::Hook::Set<BYTE>(0x4D4790, 0xC3);

View File

@ -119,6 +119,26 @@ namespace Components
}
}
__declspec(naked) int QuickPatch::G_GetClientScore()
{
__asm
{
mov eax, [esp + 4] // index
mov ecx, ds : 1A831A8h // level: &g_clients
test ecx, ecx;
jz invalid_ptr;
imul eax, 366Ch
mov eax, [eax + ecx + 3134h]
ret
invalid_ptr:
xor eax, eax
ret
}
}
bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size)
{
strncpy(dest, source, size - 1);
@ -128,7 +148,7 @@ namespace Components
{
if (!dest[i]) break;
if (dest[i] > 125 || dest[i] < 32)
if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%')
{
return false;
}
@ -139,7 +159,7 @@ namespace Components
__declspec(naked) void QuickPatch::InvalidNameStub()
{
static char* kick_reason = "Invalid name detected.";
static const char* kick_reason = "Invalid name detected.";
__asm
{
@ -163,6 +183,219 @@ namespace Components
}
}
Game::dvar_t* QuickPatch::g_antilag;
__declspec(naked) void QuickPatch::ClientEventsFireWeaponStub()
{
__asm
{
// check g_antilag dvar value
mov eax, g_antilag;
cmp byte ptr[eax + 16], 1;
// do antilag if 1
je fireWeapon
// do not do antilag if 0
mov eax, 0x1A83554 // level.time
mov ecx, [eax]
fireWeapon:
push edx
push ecx
push edi
mov eax, 0x4A4D50 // FireWeapon
call eax
add esp, 0Ch
pop edi
pop ecx
retn
}
}
__declspec(naked) void QuickPatch::ClientEventsFireWeaponMeleeStub()
{
__asm
{
// check g_antilag dvar value
mov eax, g_antilag;
cmp byte ptr[eax + 16], 1;
// do antilag if 1
je fireWeaponMelee
// do not do antilag if 0
mov eax, 0x1A83554 // level.time
mov edx, [eax]
fireWeaponMelee:
push edx
push edi
mov eax, 0x4F2470 // FireWeaponMelee
call eax
add esp, 8
pop edi
pop ecx
retn
}
}
Game::dvar_t* QuickPatch::sv_enableBounces;
__declspec(naked) void QuickPatch::BounceStub()
{
__asm
{
// check the value of sv_enableBounces
push eax;
mov eax, sv_enableBounces;
cmp byte ptr[eax + 16], 1;
pop eax;
// always bounce if sv_enableBounces is set to 1
je bounce;
// original code
cmp dword ptr[esp + 24h], 0;
jnz dontBounce;
bounce:
push 0x004B1B34;
retn;
dontBounce:
push 0x004B1B48;
retn;
}
}
Game::dvar_t* QuickPatch::r_customAspectRatio;
Game::dvar_t* QuickPatch::Dvar_RegisterAspectRatioDvar(const char* name, char**, int defaultVal, int flags, const char* description)
{
static std::vector < char * > values =
{
const_cast<char*>("auto"),
const_cast<char*>("standard"),
const_cast<char*>("wide 16:10"),
const_cast<char*>("wide 16:9"),
const_cast<char*>("custom"),
nullptr,
};
// register custom aspect ratio dvar
r_customAspectRatio = Game::Dvar_RegisterFloat("r_customAspectRatio", 16.0f / 9.0f, 4.0f / 3.0f, 63.0f / 9.0f, flags, "Screen aspect ratio. Divide the width by the height in order to get the aspect ratio value. For example: 16 / 9 = 1,77");
// register enumeration dvar
return Game::Dvar_RegisterEnum(name, values.data(), defaultVal, flags, description);
}
void QuickPatch::SetAspectRatio()
{
// set the aspect ratio
Utils::Hook::Set<float>(0x66E1C78, r_customAspectRatio->current.value);
}
__declspec(naked) void QuickPatch::SetAspectRatioStub()
{
__asm
{
cmp eax, 4;
ja goToDefaultCase;
je useCustomRatio;
// execute switch statement code
push 0x005063FC;
retn;
goToDefaultCase:
push 0x005064FC;
retn;
useCustomRatio:
// set custom resolution
pushad;
call SetAspectRatio;
popad;
// set widescreen to 1
mov eax, 1;
// continue execution
push 0x00506495;
retn;
}
}
Game::dvar_t* QuickPatch::g_playerCollision;
__declspec(naked) void QuickPatch::PlayerCollisionStub()
{
__asm
{
// check the value of g_playerCollision
push eax;
mov eax, g_playerCollision;
cmp byte ptr[eax + 16], 0;
pop eax;
// dont collide if g_playerCollision is set to 0
je dontcollide;
// original code
mov eax, dword ptr[esp + 0xa0];
jmp collide;
collide:
push 0x00478376;
retn;
dontcollide:
mov eax, dword ptr[esp + 0xa0];
mov ecx, dword ptr[esp + 9ch];
push eax;
push ecx;
lea edx, [esp + 48h];
push edx;
mov eax, esi;
push 0x0047838b;
retn;
}
}
Game::dvar_t* QuickPatch::g_playerEjection;
__declspec(naked) void QuickPatch::PlayerEjectionStub()
{
__asm
{
// check the value of g_playerEjection
push eax;
mov eax, g_playerEjection;
cmp byte ptr[eax + 16], 0;
pop eax;
// dont eject if g_playerEjection is set to 0
je donteject;
// original code
cmp dword ptr[ebx + 19ch], edi;
jle eject;
eject:
push 0x005d8152;
retn;
donteject:
push 0x005d815b;
retn;
}
}
template <typename T> std::function < T > ImportFunction(const std::string& dll, const std::string& function)
{
auto dllHandle = GetModuleHandleA(&dll[0]);
auto procAddr = GetProcAddress(dllHandle, &function[0]);
return std::function < T >(reinterpret_cast<T*>(procAddr));
}
QuickPatch::QuickPatch()
{
QuickPatch::FrameTime = 0;
@ -171,12 +404,70 @@ namespace Components
QuickPatch::FrameTime = Game::Sys_Milliseconds();
});
// quit_hard
Command::Add("quit_hard", [](Command::Params*)
{
typedef enum _HARDERROR_RESPONSE_OPTION {
OptionAbortRetryIgnore,
OptionOk,
OptionOkCancel,
OptionRetryCancel,
OptionYesNo,
OptionYesNoCancel,
OptionShutdownSystem
} HARDERROR_RESPONSE_OPTION, *PHARDERROR_RESPONSE_OPTION;
typedef enum _HARDERROR_RESPONSE {
ResponseReturnToCaller,
ResponseNotHandled,
ResponseAbort,
ResponseCancel,
ResponseIgnore,
ResponseNo,
ResponseOk,
ResponseRetry,
ResponseYes
} HARDERROR_RESPONSE, *PHARDERROR_RESPONSE;
BOOLEAN hasPerms;
HARDERROR_RESPONSE response;
auto result = ImportFunction<NTSTATUS __stdcall(ULONG, BOOLEAN, BOOLEAN, PBOOLEAN)>("ntdll.dll", "RtlAdjustPrivilege")
(19, true, false, &hasPerms);
result = ImportFunction<NTSTATUS __stdcall(NTSTATUS, ULONG, LPCSTR, PVOID, HARDERROR_RESPONSE_OPTION, PHARDERROR_RESPONSE)>("ntdll.dll", "NtRaiseHardError")
(0xC000007B /*0x0000000A*/, 0, nullptr, nullptr, OptionShutdownSystem, &response);
});
// bounce dvar
sv_enableBounces = Game::Dvar_RegisterBool("sv_enableBounces", false, Game::DVAR_FLAG_REPLICATED, "Enables bouncing on the server");
Utils::Hook(0x4B1B2D, QuickPatch::BounceStub, HOOK_JUMP).install()->quick();
// Intermission time dvar
Game::Dvar_RegisterFloat("scr_intermissionTime", 10, 0, 120, Game::DVAR_FLAG_REPLICATED | Game::DVAR_FLAG_DEDISAVED, "Time in seconds before match server loads the next map");
// Player Collision dvar
g_playerCollision = Game::Dvar_RegisterBool("g_playerCollision", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off");
Utils::Hook(0x47836F, QuickPatch::PlayerCollisionStub, HOOK_JUMP).install()->quick();
// Player Ejection dvar
g_playerEjection = Game::Dvar_RegisterBool("g_playerEjection", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off");
Utils::Hook(0x5D814A, QuickPatch::PlayerEjectionStub, HOOK_JUMP).install()->quick();
g_antilag = Game::Dvar_RegisterBool("g_antilag", true, Game::DVAR_FLAG_REPLICATED, "Perform antilag");
Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick();
// Disallow invalid player names
Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick();
// Javelin fix
Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick();
// Add ultrawide support
Utils::Hook(0x0051B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick();
Utils::Hook(0x005063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick();
// Make sure preDestroy is called when the game shuts down
Scheduler::OnShutdown(Loader::PreDestroy);
@ -213,16 +504,16 @@ namespace Components
Utils::Hook::Set<DWORD>(0x45ACE0, 0xC301B0);
// fs_basegame
Utils::Hook::Set<char*>(0x6431D1, BASEGAME);
Utils::Hook::Set<const char*>(0x6431D1, BASEGAME);
// UI version string
Utils::Hook::Set<char*>(0x43F73B, "IW4x: " VERSION);
Utils::Hook::Set<const char*>(0x43F73B, "IW4x: " VERSION);
// console version string
Utils::Hook::Set<char*>(0x4B12BB, "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")");
Utils::Hook::Set<const char*>(0x4B12BB, "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")");
// version string
Utils::Hook::Set<char*>(0x60BD56, "IW4x (" VERSION ")");
Utils::Hook::Set<const char*>(0x60BD56, "IW4x (" VERSION ")");
// version string color
static float buildLocColor[] = { 1.0f, 1.0f, 1.0f, 0.8f };
@ -239,33 +530,33 @@ namespace Components
// console title
if (ZoneBuilder::IsEnabled())
{
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder");
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder");
}
else if (Dedicated::IsEnabled())
{
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): Dedicated");
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Dedicated");
}
else
{
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): Console");
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Console");
}
// window title
Utils::Hook::Set<char*>(0x5076A0, "IW4x: Multiplayer");
Utils::Hook::Set<const char*>(0x5076A0, "IW4x: Multiplayer");
// sv_hostname
Utils::Hook::Set<char*>(0x4D378B, "IW4Host");
Utils::Hook::Set<const char*>(0x4D378B, "IW4Host");
// shortversion
Utils::Hook::Set<char*>(0x60BD91, SHORTVERSION);
Utils::Hook::Set<const char*>(0x60BD91, SHORTVERSION);
// console logo
Utils::Hook::Set<char*>(0x428A66, BASEGAME "/images/logo.bmp");
Utils::Hook::Set<const char*>(0x428A66, BASEGAME "/images/logo.bmp");
// splash logo
Utils::Hook::Set<char*>(0x475F9E, BASEGAME "/images/splash.bmp");
Utils::Hook::Set<const char*>(0x475F9E, BASEGAME "/images/splash.bmp");
Utils::Hook::Set<char*>(0x4876C6, "Successfully read stats data\n");
Utils::Hook::Set<const char*>(0x4876C6, "Successfully read stats data\n");
// Numerical ping (cg_scoreboardPingText 1)
Utils::Hook::Set<BYTE>(0x45888E, 1);
@ -366,21 +657,21 @@ namespace Components
// intro stuff
Utils::Hook::Nop(0x60BEE9, 5); // Don't show legals
Utils::Hook::Nop(0x60BEF6, 5); // Don't reset the intro dvar
Utils::Hook::Set<char*>(0x60BED2, "unskippablecinematic IW_logo\n");
Utils::Hook::Set<char*>(0x51C2A4, "%s\\" BASEGAME "\\video\\%s.bik");
Utils::Hook::Set<const char*>(0x60BED2, "unskippablecinematic IW_logo\n");
Utils::Hook::Set<const char*>(0x51C2A4, "%s\\" BASEGAME "\\video\\%s.bik");
Utils::Hook::Set<DWORD>(0x51C2C2, 0x78A0AC);
// Redirect logs
Utils::Hook::Set<char*>(0x5E44D8, "logs/games_mp.log");
Utils::Hook::Set<char*>(0x60A90C, "logs/console_mp.log");
Utils::Hook::Set<char*>(0x60A918, "logs/console_mp.log");
Utils::Hook::Set<const char*>(0x5E44D8, "logs/games_mp.log");
Utils::Hook::Set<const char*>(0x60A90C, "logs/console_mp.log");
Utils::Hook::Set<const char*>(0x60A918, "logs/console_mp.log");
// Rename config
Utils::Hook::Set<char*>(0x461B4B, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x47DCBB, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x6098F8, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x60B279, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x60BBD4, CLIENT_CONFIG);
Utils::Hook::Set<const char*>(0x461B4B, CLIENT_CONFIG);
Utils::Hook::Set<const char*>(0x47DCBB, CLIENT_CONFIG);
Utils::Hook::Set<const char*>(0x6098F8, CLIENT_CONFIG);
Utils::Hook::Set<const char*>(0x60B279, CLIENT_CONFIG);
Utils::Hook::Set<const char*>(0x60BBD4, CLIENT_CONFIG);
// Disable profile system
// Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Causes an error, when calling a harrier killstreak.
@ -417,6 +708,13 @@ namespace Components
// Patch SV_IsClientUsingOnlineStatsOffline
Utils::Hook::Set<DWORD>(0x46B710, 0x90C3C033);
// Fix mouse lag
Utils::Hook::Nop(0x4731F5, 8);
Scheduler::OnFrame([]()
{
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
});
// Fix mouse pitch adjustments
Dvar::Register<bool>("ui_mousePitch", false, Game::DVAR_FLAG_SAVED, "");
UIScript::Add("updateui_mousePitch", [](UIScript::Token)
@ -443,6 +741,12 @@ namespace Components
// Patch selectStringTableEntryInDvar
Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub);
// Patch G_GetClientScore for uninitialised game
Utils::Hook(0x469AC0, QuickPatch::G_GetClientScore, HOOK_JUMP).install()->quick();
// Ignore call to print 'Offhand class mismatch when giving weapon...'
Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick();
Command::Add("unlockstats", [](Command::Params*)
{
QuickPatch::UnlockStats();
@ -652,18 +956,21 @@ namespace Components
if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawAabbTrees").get<bool>()) return;
float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f };
float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
//Game::clipMap_t* clipMap = *reinterpret_cast<Game::clipMap_t**>(0x7998E0);
Game::GfxWorld* gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94);
if (!gameWorld) return;
Game::clipMap_t* clipMap = *reinterpret_cast<Game::clipMap_t**>(0x7998E0);
//Game::GfxWorld* gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94);
if (!clipMap) return;
for (int i = 0; i < gameWorld->dpvsPlanes.cellCount; ++i)
{
for (int j = 0; j < gameWorld->aabbTreeCounts[i].aabbTreeCount; ++j)
{
Game::R_AddDebugBounds(cyan, &gameWorld->aabbTrees[i].aabbTree[j].bounds);
}
}
for (unsigned short i = 0; i < clipMap->smodelNodeCount; ++i)
{
Game::R_AddDebugBounds(cyan, &clipMap->smodelNodes[i].bounds);
}
for (unsigned int i = 0; i < clipMap->numStaticModels; i += 2)
{
Game::R_AddDebugBounds(red, &clipMap->staticModelList[i].absBounds);
}
});

View File

@ -19,6 +19,7 @@ namespace Components
static void SelectStringTableEntryInDvarStub();
static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd);
static int G_GetClientScore();
static int MsgReadBitsCompressCheckSV(const char *from, char *to, int size);
static int MsgReadBitsCompressCheckCL(const char *from, char *to, int size);
@ -27,7 +28,24 @@ namespace Components
static void JavelinResetHookStub();
static bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size);
static void QuickPatch::InvalidNameStub();
static bool InvalidNameCheck(char *dest, char *source, int size);
static void InvalidNameStub();
static Game::dvar_t* sv_enableBounces;
static void BounceStub();
static Game::dvar_t* r_customAspectRatio;
static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* name, char** enumValues, int defaultVal, int flags, const char* description);
static void SetAspectRatioStub();
static void SetAspectRatio();
static Game::dvar_t* g_antilag;
static void ClientEventsFireWeaponStub();
static void ClientEventsFireWeaponMeleeStub();
static Game::dvar_t* g_playerCollision;
static void PlayerCollisionStub();
static Game::dvar_t* g_playerEjection;
static void PlayerEjectionStub();
};
}

View File

@ -7,6 +7,6 @@ namespace Components
public:
RawFiles();
static void* RawFiles::LoadModdableRawfileFunc(const char* filename);
static void* LoadModdableRawfileFunc(const char* filename);
};
}

View File

@ -7,6 +7,9 @@ namespace Components
std::vector<Script::Function> Script::ScriptFunctions;
std::vector<std::string> Script::ScriptNameStack;
unsigned short Script::FunctionName;
std::unordered_map<std::string, std::string> Script::ScriptStorage;
std::unordered_map<int, std::string> Script::ScriptBaseProgramNum;
int Script::LastFrameTime = -1;
Utils::Signal<Scheduler::Callback> Script::VMShutdownSignal;
@ -247,11 +250,6 @@ namespace Components
Script::ScriptFunctions.push_back({ name, function, isDev });
}
void Script::OnVMShutdown(Utils::Slot<Scheduler::Callback> callback)
{
Script::VMShutdownSignal.connect(callback);
}
Game::scr_function_t Script::GetFunction(void* caller, const char** name, int* isDev)
{
for (auto& function : Script::ScriptFunctions)
@ -293,6 +291,91 @@ namespace Components
}
}
void Script::StoreScriptBaseProgramNum()
{
Script::ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get<int>(0x1CFEEF8), Script::ScriptName);
}
void Script::Scr_PrintPrevCodePos(int scriptPos)
{
int bestCodePos = -1;
int nextCodePos = -1;
int offset = -1;
std::string file;
for (auto kv : Script::ScriptBaseProgramNum)
{
int codePos = kv.first;
if (codePos > scriptPos)
{
if (nextCodePos == -1 || codePos < nextCodePos)
nextCodePos = codePos;
continue;
}
if (codePos < bestCodePos)
continue;
bestCodePos = codePos;
file = kv.second;
offset = scriptPos - bestCodePos;
}
if (bestCodePos == -1)
return;
float onehundred = 100.0;
Logger::Print(23, "\n@ %d (%d - %d)\n", scriptPos, bestCodePos, nextCodePos);
Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.c_str(), ((offset * onehundred) / (nextCodePos - bestCodePos)));
}
__declspec(naked) void Script::Scr_PrintPrevCodePosStub()
{
__asm
{
push esi
call Script::Scr_PrintPrevCodePos
add esp, 4h
pop esi
retn
}
}
__declspec(naked) void Script::StoreScriptBaseProgramNumStub()
{
__asm
{
// execute our hook
pushad
pusha
call Script::StoreScriptBaseProgramNum
popa
popad
// execute overwritten code caused by the jump hook
sub eax, ds:201A460h // gScrVarPub_programBuffer
add esp, 0Ch
mov ds : 1CFEEF8h, eax // gScrCompilePub_programLen
// jump back to the original code
push 426C3Bh
retn
}
}
void Script::OnVMShutdown(Utils::Slot<Scheduler::Callback> callback)
{
Script::ScriptBaseProgramNum.clear();
Script::VMShutdownSignal.connect(callback);
}
void Script::ScrShutdownSystemStub(int num)
{
Script::VMShutdownSignal();
@ -316,11 +399,158 @@ namespace Components
return Game::Scr_GetNumParam();
}
Game::gentity_t* Script::getEntFromEntRef(Game::scr_entref_t entref)
{
Game::gentity_t* gentity = &Game::g_entities[entref];
return gentity;
}
Game::client_t* Script::getClientFromEnt(Game::gentity_t* gentity)
{
if (!gentity->client)
{
Logger::Error(5, "Entity: %i is not a client", gentity);
}
return &Game::svs_clients[gentity->number];
}
void Script::AddFunctions()
{
// System time
Script::AddFunction("GetSystemTime", [](Game::scr_entref_t) // gsc: GetSystemTime()
{
SYSTEMTIME time;
GetSystemTime(&time);
Game::Scr_AddInt(time.wSecond);
});
Script::AddFunction("GetSystemMilliseconds", [](Game::scr_entref_t) // gsc: GetSystemMilliseconds()
{
SYSTEMTIME time;
GetSystemTime(&time);
Game::Scr_AddInt(time.wMilliseconds);
});
// Print to console, even without being in 'developer 1'.
Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole(<string>)
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1PrintConsole: Needs one string parameter!\n");
return;
}
auto str = Game::Scr_GetString(0);
Game::Com_Printf(0, str);
});
// Executes command to the console
Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec(<string>)
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1Exec: Needs one string parameter!\n");
return;
}
auto str = Game::Scr_GetString(0);
Command::Execute(str, false);
});
// Script Storage Funcs
Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(<str key>, <str data>);
{
if (Game::Scr_GetNumParam() != 2 || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageSet: Needs two string parameters!\n");
return;
}
std::string key = Game::Scr_GetString(0);
std::string data = Game::Scr_GetString(1);
Script::ScriptStorage.insert_or_assign(key, data);
});
Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(<str key>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageRemove: Needs one string parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
if (!Script::ScriptStorage.count(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.c_str()));
return;
}
Script::ScriptStorage.erase(key);
});
Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(<str key>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageGet: Needs one string parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
if (!Script::ScriptStorage.count(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.c_str()));
return;
}
auto data = Script::ScriptStorage.at(key);
Game::Scr_AddString(data.c_str());
});
Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(<str key>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageHas: Needs one string parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
Game::Scr_AddInt(Script::ScriptStorage.count(key));
});
Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear();
{
Script::ScriptStorage.clear();
});
}
Script::Script()
{
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, Script::StoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
// enable scr_error printing if in developer
Dvar::OnInit([]()
{
int developer = Dvar::Var("developer").get<int>();
if (developer > 0)
Utils::Hook::Set<BYTE>(0x48D8C7, 0x75);
});
Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick();
@ -330,18 +560,38 @@ namespace Components
Utils::Hook(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick();
Utils::Hook(0x44E736, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetFunction
//Utils::Hook(0x4EC8E5, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetMethod
Utils::Hook(0x4EC8E5, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetMethod
Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick();
Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick();
Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick();
Scheduler::OnFrame([]()
{
if (!Game::SV_Loaded())
return;
int nowMs = Game::Sys_Milliseconds();
if (Script::LastFrameTime != -1)
{
int timeTaken = static_cast<int>((nowMs - Script::LastFrameTime) * Dvar::Var("timescale").get<float>());
if (timeTaken >= 500)
Logger::Print(23, "Hitch warning: %i msec frame time\n", timeTaken);
}
Script::LastFrameTime = nowMs;
});
Script::AddFunction("debugBox", [](Game::scr_entref_t)
{
MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0);
}, true);
Script::AddFunctions();
// Script::AddFunction("playviewmodelfx", [](Game::scr_entref_t /*index*/)
// {
// /*auto Scr_Error = Utils::Hook::Call<void(const char*)>(0x42EF40);
@ -376,5 +626,8 @@ namespace Components
Script::ScriptNameStack.clear();
Script::ScriptFunctions.clear();
Script::VMShutdownSignal.clear();
Script::ScriptStorage.clear();
Script::ScriptBaseProgramNum.clear();
}
}

View File

@ -29,12 +29,18 @@ namespace Components
static void OnVMShutdown(Utils::Slot<Scheduler::Callback> callback);
static Game::gentity_t* getEntFromEntRef(Game::scr_entref_t entref);
static Game::client_t* getClientFromEnt(Game::gentity_t* gentity);
private:
static std::string ScriptName;
static std::vector<int> ScriptHandles;
static std::vector<Function> ScriptFunctions;
static std::vector<std::string> ScriptNameStack;
static unsigned short FunctionName;
static std::unordered_map<std::string, std::string> ScriptStorage;
static std::unordered_map<int, std::string> ScriptBaseProgramNum;
static int LastFrameTime;
static Utils::Signal<Scheduler::Callback> VMShutdownSignal;
@ -57,7 +63,13 @@ namespace Components
static void GetFunctionStub();
static void ScrShutdownSystemStub(int);
static void StoreScriptBaseProgramNumStub();
static void StoreScriptBaseProgramNum();
static void Scr_PrintPrevCodePosStub();
static void Scr_PrintPrevCodePos(int);
static int SetExpFogStub();
static void AddFunctions();
};
}

View File

@ -110,6 +110,19 @@ namespace Components
}
}
Utils::InfoString ServerInfo::GetHostInfo()
{
Utils::InfoString info;
// TODO: Possibly add all Dvar starting with _
info.set("admin", Dvar::Var("_Admin").get<const char*>());
info.set("website", Dvar::Var("_Website").get<const char*>());
info.set("email", Dvar::Var("_Email").get<const char*>());
info.set("location", Dvar::Var("_Location").get<const char*>());
return info;
}
Utils::InfoString ServerInfo::GetInfo()
{
int maxclientCount = *Game::svs_numclients;

View File

@ -8,6 +8,7 @@ namespace Components
ServerInfo();
~ServerInfo();
static Utils::InfoString GetHostInfo();
static Utils::InfoString GetInfo();
private:

View File

@ -63,6 +63,18 @@ namespace Components
Stats::SendStats();
}
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length)
{
const auto fs_game = Game::Dvar_FindVar("fs_game");
if (fs_game && fs_game->current.string && strlen(fs_game->current.string) && !strncmp(fs_game->current.string, "mods/", 5))
{
folder = fs_game->current.string;
}
return Utils::Hook::Call<int(char*, const char*, const char*, size_t)>(0x426450)(dest, folder, buffer, length);
}
Stats::Stats()
{
// This UIScript should be added in the onClose code of the cac_popup menu,
@ -91,6 +103,9 @@ namespace Components
// Don't create stat backup
Utils::Hook::Nop(0x402CE6, 2);
// Write stats to mod folder if a mod is loaded
Utils::Hook(0x682F7B, Stats::SaveStats, HOOK_CALL).install()->quick();
}
Stats::~Stats()

View File

@ -13,6 +13,7 @@ namespace Components
private:
static void UpdateClasses(UIScript::Token token);
static void SendStats();
static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length);
static int64_t* GetStatsID();
};

View File

@ -371,7 +371,7 @@ namespace Components
UIFeeder::Add(10.0f, Theatre::GetDemoCount, Theatre::GetDemoText, Theatre::SelectDemo);
// set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps
if (!Dedicated::IsEnabled()) Utils::Hook::Set<char*>(0x47440B, "mp/defaultStringTable.csv");
if (!Dedicated::IsEnabled()) Utils::Hook::Set<const char*>(0x47440B, "mp/defaultStringTable.csv");
// Change font size
Utils::Hook::Set<BYTE>(0x5AC854, 2);

View File

@ -22,7 +22,7 @@ namespace Components
return this->token;
}
return "";
return const_cast<char*>("");
}
template<> const char* UIScript::Token::get()

View File

@ -207,6 +207,9 @@ namespace Components
{
Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data());
if (name.find(" ", 0) != std::string::npos)
Logger::Print("Warning: asset with name '%s' contains spaces. Check your zone source file to ensure this is correct!\n", name.data());
// Sanitize name for empty assets
if (name[0] == ',') name.erase(name.begin());
@ -220,8 +223,7 @@ namespace Components
Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this, isSubAsset);
if (!assetHeader.data)
{
Logger::Error("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type));
{ Logger::Error("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type));
return false;
}
@ -407,6 +409,8 @@ namespace Components
}
#endif
Utils::IO::WriteFile("uncompressed", zoneBuffer);
zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer);
outBuffer.append(zoneBuffer);
@ -506,7 +510,7 @@ namespace Components
// Add branding asset
void ZoneBuilder::Zone::addBranding()
{
char* data = "FastFile built using the IW4x ZoneBuilder!";
const char* data = "FastFile built using the IW4x ZoneBuilder!";
this->branding = { this->zoneName.data(), static_cast<int>(strlen(data)), 0, data };
if (this->findAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, this->branding.name) != -1)
@ -701,21 +705,20 @@ namespace Components
if (zoneIndex > 0)
{
Game::DB_EnumXAssetEntries(type, [&](Game::XAssetEntry* entry)
{
if (!header.data && entry->zoneIndex == zoneIndex && Game::DB_GetXAssetName(&entry->asset) == name)
{
// Allocate an empty asset (filled with zeros)
header.data = builder->getAllocator()->allocate(Game::DB_GetXAssetSizeHandlers[type]());
Game::XAssetEntry* entry = Game::DB_FindXAssetEntry(type, name.data());
// Set the name to the original name, so it can be stored
Game::DB_SetXAssetNameHandlers[type](&header, name.data());
AssetHandler::StoreTemporaryAsset(type, header);
if (entry && entry->zoneIndex == zoneIndex)
{
// Allocate an empty asset (filled with zeros)
header.data = builder->getAllocator()->allocate(Game::DB_GetXAssetSizeHandlers[type]());
// Set the name to the empty name
Game::DB_SetXAssetNameHandlers[type](&header, builder->getAllocator()->duplicateString("," + name));
}
}, true, true);
// Set the name to the original name, so it can be stored
Game::DB_SetXAssetNameHandlers[type](&header, name.data());
AssetHandler::StoreTemporaryAsset(type, header);
// Set the name to the empty name
Game::DB_SetXAssetNameHandlers[type](&header, builder->getAllocator()->duplicateString("," + name));
}
}
}
@ -868,6 +871,10 @@ namespace Components
// defaults need to load before we do this
Utils::Hook::Call<void()>(0x4E1F30)(); // G_SetupWeaponDef
Utils::Hook::Call<void()>(0x4454C0)(); // Item_SetupKeywordHash (for loading menus)
Utils::Hook::Call<void()>(0x501BC0)(); // Menu_SetupKeywordHash (for loading menus)
Utils::Hook::Call<void()>(0x4A1280)(); // something related to uiInfoArray
Utils::Hook::Call<void(const char*)>(0x464A90)(GetCommandLineA()); // Com_ParseCommandLine
Utils::Hook::Call<void()>(0x60C3D0)(); // Com_AddStartupCommands
@ -1102,12 +1109,29 @@ namespace Components
if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current())
{
ZoneBuilder::TraceAssets.push_back({ type, name });
OutputDebugStringA((name + "\n").data());
}
});
Command::Add("verifyzone", [](Command::Params* params)
{
if (params->length() < 2) return;
/*
Utils::Hook(0x4AE9C2, [] {
Game::WeaponCompleteDef** varPtr = (Game::WeaponCompleteDef**)0x112A9F4;
Game::WeaponCompleteDef* var = *varPtr;
OutputDebugStringA("");
Utils::Hook::Call<void()>(0x4D1D60)(); // DB_PopStreamPos
}, HOOK_JUMP).install()->quick();
Utils::Hook(0x4AE9B4, [] {
Game::WeaponCompleteDef** varPtr = (Game::WeaponCompleteDef**)0x112A9F4;
Game::WeaponCompleteDef* var = *varPtr;
OutputDebugStringA("");
Utils::Hook::Call<void()>(0x4D1D60)(); // DB_PopStreamPos
}, HOOK_JUMP).install()->quick();
*/
std::string zone = params->get(1);
@ -1282,7 +1306,7 @@ namespace Components
// HACK: set language to 'techsets' to load from that dir
char* language = Utils::Hook::Get<char*>(0x649E740);
Utils::Hook::Set<char*>(0x649E740, "techsets");
Utils::Hook::Set<const char*>(0x649E740, "techsets");
// load generated techset fastfiles
auto list = Utils::IO::ListFiles("zone/techsets");
@ -1342,7 +1366,7 @@ namespace Components
info.freeFlags = Game::DB_ZONE_MOD;
Game::DB_LoadXAssets(&info, 1, true);
Utils::Hook::Set<char*>(0x649E740, "techsets");
Utils::Hook::Set<const char*>(0x649E740, "techsets");
i = 0;
subCount++;

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,13 @@ namespace Components
class Zones : public Component
{
public:
struct FileData
{
std::uint32_t readPos;
std::uint32_t len;
std::string fileContents;
};
Zones();
~Zones();
@ -17,6 +24,7 @@ namespace Components
static int Version() { return Zones::ZoneVersion; };
private:
static int ZoneVersion;
static int FxEffectIndex;
@ -61,6 +69,36 @@ namespace Components
static void LoadImpactFxArray();
static int ImpactFxArrayCount();
static void LoadPathDataConstant();
static void GetCurrentAssetTypeStub();
static int LoadRandomFxGarbage(bool atStreamStart, char* buffer, int size);
static int LoadGfxXSurfaceArray(bool atStreamStart, char* buffer, int size);
static int LoadGfxXSurfaceExtraData(bool atStreamStart);
static int LoadGfxReflectionProbes(bool atStreamStart, char* buffer, int size);
static void LoadGfxLightMapExtraData();
static void LoadXModelColSurfPtr();
static int PathDataSize();
static int LoadMaterialTechniqueArray(bool atStreamStart, int count);
static int LoadMapEnts(bool atStreamStart, Game::MapEnts* buffer, int size);
static void Load_ClipInfo(bool atStreamStart);
static int LoadClipMap(bool atStreamStart);
static uint32_t HashCRC32StringInt(const std::string& Value, uint32_t Initial);
static std::unordered_map<int, Zones::FileData> fileDataMap;
static std::mutex fileDataMutex;
static int FS_FOpenFileReadForThreadOriginal(const char*, int*, int);
static int FS_FOpenFileReadForThreadHook(const char* file, int* filePointer, int thread);
static int FS_ReadOriginal(void*, size_t, int);
static int FS_ReadHook(void* buffer, size_t size, int filePointer);
static void FS_FCloseFileOriginal(int);
static void FS_FCloseFileHook(int filePointer);
static std::uint32_t FS_SeekOriginal(int, int, int);
static std::uint32_t FS_SeekHook(int fileHandle, int seekPosition, int seekOrigin);
static void LoadMapTriggersModelPointer();
static void LoadMapTriggersHullPointer();
static void LoadMapTriggersSlabPointer();
static void LoadFxWorldAsset(Game::FxWorld** asset);
static void LoadXModelAsset(Game::XModel** asset);
static void LoadMaterialAsset(Game::Material** asset);
static void LoadTracerDef(bool atStreamStart, Game::TracerDef* tracer, int size);
static void LoadTracerDefFxEffect();
};
}

View File

@ -2,6 +2,26 @@
namespace Game
{
std::vector<std::string> Sys_ListFilesWrapper(const std::string& directory, const std::string& extension)
{
auto fileCount = 0;
auto files = Game::Sys_ListFiles(directory.data(), extension.data(), 0, &fileCount, 0);
std::vector<std::string> result;
for (auto i = 0; i < fileCount; i++)
{
if (files[i])
{
result.push_back(files[i]);
}
}
Game::FS_FreeFileList(files);
return result;
}
AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360);
AllocObject_t AllocObject = AllocObject_t(0x434320);
@ -116,6 +136,7 @@ namespace Game
FS_BuildPathToFile_t FS_BuildPathToFile = FS_BuildPathToFile_t(0x4702C0);
FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440);
G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540);
G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840);
GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0);
@ -148,6 +169,7 @@ namespace Game
Load_snd_alias_list_nameArray_t Load_snd_alias_list_nameArray = Load_snd_alias_list_nameArray_t(0x4499F0);
Menus_CloseAll_t Menus_CloseAll = Menus_CloseAll_t(0x4BA5B0);
Menus_CloseRequest_t Menus_CloseRequest = Menus_CloseRequest_t(0x430D50);
Menus_OpenByName_t Menus_OpenByName = Menus_OpenByName_t(0x4CCE60);
Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240);
Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0);
@ -173,6 +195,7 @@ namespace Game
NET_AdrToString_t NET_AdrToString = NET_AdrToString_t(0x469880);
NET_CompareAdr_t NET_CompareAdr = NET_CompareAdr_t(0x4D0AA0);
NET_DeferPacketToClient_t NET_DeferPacketToClient = NET_DeferPacketToClient_t(0x4C8AA0);
NET_ErrorString_t NET_ErrorString = NET_ErrorString_t(0x4E7720);
NET_Init_t NET_Init = NET_Init_t(0x491860);
NET_IsLocalAddress_t NET_IsLocalAddress = NET_IsLocalAddress_t(0x402BD0);
@ -232,6 +255,8 @@ namespace Game
Scr_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40);
Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750);
Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30);
Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0);
Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900);
Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0);
@ -272,6 +297,7 @@ namespace Game
SV_DirectConnect_t SV_DirectConnect = SV_DirectConnect_t(0x460480);
SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0);
SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0);
SV_ClientThink_t SV_ClientThink = SV_ClientThink_t(0x44ADD0);
Sys_Error_t Sys_Error = Sys_Error_t(0x4E0200);
Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580);
@ -321,6 +347,7 @@ namespace Game
source_t **sourceFiles = reinterpret_cast<source_t **>(0x7C4A98);
keywordHash_t **menuParseKeywordHash = reinterpret_cast<keywordHash_t **>(0x63AE928);
int* svs_time = reinterpret_cast<int*>(0x31D9384);
int* svs_numclients = reinterpret_cast<int*>(0x31D938C);
client_t* svs_clients = reinterpret_cast<client_t*>(0x31D9390);
@ -390,6 +417,8 @@ namespace Game
ScriptContainer* scriptContainer = reinterpret_cast<ScriptContainer*>(0x2040D00);
clientstate_t* clcState = reinterpret_cast<clientstate_t*>(0xB2C540);
XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize)
{
int elSize = DB_GetXAssetSizeHandlers[type]();
@ -662,6 +691,43 @@ namespace Game
return atoi(StringTable_Lookup(rankTable, 0, maxrank, 7));
}
void Vec3Normalize(vec3_t& vec)
{
const float length = static_cast<float>(std::sqrt(std::pow(vec[0], 2) + std::pow(vec[1], 2) + std::pow(vec[2], 2)));
vec[0] /= length;
vec[1] /= length;
vec[2] /= length;
}
void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out)
{
unsigned int v3; // xmm1_4
if (LOWORD(in.packed))
v3 = ((in.packed & 0x8000) << 16) | (((((in.packed & 0x3FFF) << 14) - (~(LOWORD(in.packed) << 14) & 0x10000000)) ^ 0x80000001) >> 1);
else
v3 = 0;
(*out)[0] = *reinterpret_cast<float*>(&v3);
if (HIWORD(in.packed))
v3 = ((HIWORD(in.packed) & 0x8000) << 16) | (((((HIWORD(in.packed) & 0x3FFF) << 14)
- (~(HIWORD(in.packed) << 14) & 0x10000000)) ^ 0x80000001) >> 1);
else
v3 = 0;
(*out)[1] = *reinterpret_cast<float*>(&v3);
}
void MatrixVecMultiply(const float (& mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution)
{
vec3_t res;
res[0] = mulMat[0][0] * mulVec[0] + mulMat[1][0] * mulVec[1] + mulMat[2][0] * mulVec[2];
res[1] = mulMat[0][1] * mulVec[0] + mulMat[1][1] * mulVec[1] + mulMat[2][1] * mulVec[2];
res[2] = mulMat[0][2] * mulVec[0] + mulMat[1][2] * mulVec[1] + mulMat[2][2] * mulVec[2];
std::memmove(&solution[0], &res[0], sizeof(res));
}
void SortWorldSurfaces(GfxWorld* world)
{
DWORD* specular1 = reinterpret_cast<DWORD*>(0x69F105C);

View File

@ -2,6 +2,26 @@
namespace Game
{
template <typename T> static void DB_ConvertOffsetToPointer(T* pointer)
{
Utils::Hook::Call<void(T*)>(0x4A82B0)(pointer);
}
template <typename T> static T** DB_InsertPointer()
{
static auto DB_InsertPointer_Address = 0x43B290;
T** retval = nullptr;
__asm
{
call DB_InsertPointer_Address;
mov retval, eax;
}
return retval;
}
std::vector<std::string> Sys_ListFilesWrapper(const std::string& directory, const std::string& extension);
typedef void(__cdecl * AddRefToObject_t)(unsigned int id);
extern AddRefToObject_t AddRefToObject;
@ -297,6 +317,9 @@ namespace Game
typedef iwd_t*(__cdecl * FS_IsShippedIWD_t)(const char* fullpath, const char* iwd);
extern FS_IsShippedIWD_t FS_IsShippedIWD;
typedef int(__cdecl* G_GetWeaponIndexForName_t)(char*);
extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName;
typedef void(__cdecl* G_SpawnEntitiesFromString_t)();
extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString;
@ -319,7 +342,7 @@ namespace Game
typedef void(__cdecl * LargeLocalInit_t)();
extern LargeLocalInit_t LargeLocalInit;
typedef bool(__cdecl * Load_Stream_t)(bool atStreamStart, const void *ptr, int size);
typedef bool(__cdecl * Load_Stream_t)(bool atStreamStart, const void *ptr, unsigned int size);
extern Load_Stream_t Load_Stream;
typedef void(__cdecl * Load_XString_t)(bool atStreamStart);
@ -373,6 +396,9 @@ namespace Game
typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc);
extern Menus_CloseAll_t Menus_CloseAll;
typedef void(__cdecl * Menus_CloseRequest_t)(UiContext *dc, menuDef_t* menu);
extern Menus_CloseRequest_t Menus_CloseRequest;
typedef int(__cdecl * Menus_OpenByName_t)(UiContext *dc, const char *p);
extern Menus_OpenByName_t Menus_OpenByName;
@ -439,6 +465,9 @@ namespace Game
typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b);
extern NET_CompareAdr_t NET_CompareAdr;
typedef void(__cdecl * NET_DeferPacketToClient_t)(netadr_t *, msg_t *);
extern NET_DeferPacketToClient_t NET_DeferPacketToClient;
typedef const char* (__cdecl * NET_ErrorString_t)();
extern NET_ErrorString_t NET_ErrorString;
@ -592,6 +621,12 @@ namespace Game
typedef bool(__cdecl * Scr_IsSystemActive_t)();
extern Scr_IsSystemActive_t Scr_IsSystemActive;
typedef int(__cdecl* Scr_GetType_t)(int);
extern Scr_GetType_t Scr_GetType;
typedef void(__cdecl* Scr_Error_t)(const char*);
extern Scr_Error_t Scr_Error;
typedef script_t* (__cdecl * Script_Alloc_t)(int length);
extern Script_Alloc_t Script_Alloc;
@ -655,6 +690,9 @@ namespace Game
typedef bool(__cdecl * SV_Loaded_t)();
extern SV_Loaded_t SV_Loaded;
typedef void(__cdecl* SV_ClientThink_t)(client_s*, usercmd_s*);
extern SV_ClientThink_t SV_ClientThink;
typedef int(__cdecl * Sys_Error_t)(int, char *, ...);
extern Sys_Error_t Sys_Error;
@ -746,6 +784,7 @@ namespace Game
extern cmd_function_t** cmd_functions;
extern int* svs_time;
extern int* svs_numclients;
extern client_t* svs_clients;
@ -817,6 +856,8 @@ namespace Game
extern ScriptContainer* scriptContainer;
extern clientstate_t* clcState;
XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize);
void Menu_FreeItemMemory(Game::itemDef_s* item);
const char* TableLookup(StringTable* stringtable, int row, int column);
@ -862,6 +903,10 @@ namespace Game
void Image_Setup(GfxImage* image, unsigned int width, unsigned int height, unsigned int depth, unsigned int flags, _D3DFORMAT format);
void Vec3Normalize(vec3_t& vec);
void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out);
void MatrixVecMultiply(const float(&mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution);
void SortWorldSurfaces(GfxWorld* world);
void R_AddDebugLine(float* color, float* v1, float* v2);
void R_AddDebugString(float *color, float *pos, float scale, const char *str);

View File

@ -121,6 +121,16 @@ namespace Game
DVAR_TYPE_COUNT = 0xA,
} dvar_type;
typedef enum
{
CS_FREE = 0x0,
CS_UNKNOWN1 = 0x1,
CS_UNKNOWN2 = 0x2,
CS_CONNECTED = 0x3,
CS_CLIENTLOADING = 0x4,
CS_ACTIVE = 0x5,
} clientstate_t;
struct FxEffectDef;
struct pathnode_t;
struct pathnode_tree_t;
@ -4469,9 +4479,17 @@ namespace Game
typedef struct client_s
{
// 0
int state;
clientstate_t state;
// 4
char pad[36];
char _pad[4];
// 8
int deltaMessage;
// 12
char __pad[12];
// 24
int outgoingSequence;
// 28
char pad[12];
// 40
netadr_t addr;
// 60

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