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 *.til
*.idb *.idb
*.i64 *.i64
ida/*
### Custom user files ### Custom user files
# User scripts # User scripts

5
.gitmodules vendored
View File

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

View File

@ -2,7 +2,9 @@
![forks](https://img.shields.io/github/forks/IW4x/iw4x-client.svg) ![forks](https://img.shields.io/github/forks/IW4x/iw4x-client.svg)
![stars](https://img.shields.io/github/stars/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) ![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 # IW4x: Client
@ -38,7 +40,6 @@
| `--disable-bitmessage` | Disable use of BitMessage completely. | | `--disable-bitmessage` | Disable use of BitMessage completely. |
| `--disable-base128` | Disable base128 encoding for minidumps. | | `--disable-base128` | Disable base128 encoding for minidumps. |
| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). | | `--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 ## 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) # Requires a decent modern Docker version (v1.10.x at least ideally)
# Use semi-official Arch Linux image with fixed versioning # Use semi-official Arch Linux image with fixed versioning
FROM base/archlinux:2018.11.01 FROM archlinux/base
# Environment variables # Environment variables
ENV WINEPREFIX /wine32 ENV WINEPREFIX /wine32
@ -10,7 +10,7 @@ ENV WINEDEBUG -all
# Install Wine (32-bit) # Install Wine (32-bit)
RUN \ 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 &&\ chmod +x /usr/local/bin/wine-wrapper &&\
\ \
(\ (\
@ -19,11 +19,13 @@ RUN \
echo 'Include = /etc/pacman.d/mirrorlist'\ echo 'Include = /etc/pacman.d/mirrorlist'\
) >> /etc/pacman.conf &&\ ) >> /etc/pacman.conf &&\
pacman -Sy --noconfirm \ pacman -Sy --noconfirm \
awk \
lib32-gnutls \ lib32-gnutls \
wine \ wine \
wget \ wget \
xorg-server-xvfb \ xorg-server-xvfb \
pacman-contrib \ pacman-contrib \
awk \
&&\ &&\
\ \
wine-wrapper wineboot.exe -i &&\ wine-wrapper wineboot.exe -i &&\
@ -39,7 +41,6 @@ RUN \
find /. -name "*~" -type f -delete &&\ find /. -name "*~" -type f -delete &&\
rm -rf /tmp/* /var/tmp/* /usr/share/man/* /usr/share/info/* /usr/share/doc/* &&\ rm -rf /tmp/* /var/tmp/* /usr/share/man/* /usr/share/info/* /usr/share/doc/* &&\
pacman -Scc --noconfirm &&\ pacman -Scc --noconfirm &&\
paccache -rk0 &&\
rm -rf /var/lib/pacman/sync/* rm -rf /var/lib/pacman/sync/*
USER 0 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" gitVersioningCommand = "git describe --tags --dirty --always"
gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD"
-- Quote the given string input as a C string -- Quote the given string input as a C string
function cstrquote(value) function cstrquote(value)
@ -73,11 +74,6 @@ newoption {
description = "Upload minidumps even for Debug builds." description = "Upload minidumps even for Debug builds."
} }
newoption {
trigger = "enable-dxsdk",
description = "Enable DirectX SDK (required for GfxMap exporting)."
}
newaction { newaction {
trigger = "version", trigger = "version",
description = "Returns the version string for the current commit of the source code.", 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 proc = assert(io.popen(gitVersioningCommand, "r"))
local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "") local gitDescribeOutput = assert(proc:read('*a')):gsub("%s+", "")
proc:close() 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) os.exit(0)
end end
} }
@ -182,6 +189,7 @@ require "premake/protobuf"
require "premake/zlib" require "premake/zlib"
require "premake/udis86" require "premake/udis86"
require "premake/iw4mvm" require "premake/iw4mvm"
require "premake/dxsdk"
json11.setup json11.setup
{ {
@ -236,8 +244,13 @@ iw4mvm.setup
}, },
source = path.join(depsBasePath, "iw4mvm"), source = path.join(depsBasePath, "iw4mvm"),
} }
dxsdk.setup
{
source = path.join(depsBasePath, "dxsdk"),
}
workspace "iw4x" workspace "iw4x"
startproject "iw4x"
location "./build" location "./build"
objdir "%{wks.location}/obj" objdir "%{wks.location}/obj"
targetdir "%{wks.location}/bin/%{cfg.buildcfg}" targetdir "%{wks.location}/bin/%{cfg.buildcfg}"
@ -311,11 +324,6 @@ workspace "iw4x"
if _OPTIONS["force-exception-handler"] then if _OPTIONS["force-exception-handler"] then
defines { "FORCE_EXCEPTION_HANDLER" } defines { "FORCE_EXCEPTION_HANDLER" }
end end
if _OPTIONS["enable-dxsdk"] then
defines { "ENABLE_DXSDK" }
includedirs { "%DXSDK_DIR%Include" }
libdirs { "%DXSDK_DIR%Lib/x86" }
end
-- Pre-compiled header -- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives
@ -332,6 +340,7 @@ workspace "iw4x"
zlib.import() zlib.import()
udis86.import() udis86.import()
--iw4mvm.import() --iw4mvm.import()
dxsdk.import()
-- fix vpaths for protobuf sources -- fix vpaths for protobuf sources
vpaths vpaths
@ -450,7 +459,7 @@ workspace "*"
buildoptions { buildoptions {
"/std:c++latest" "/std:c++latest"
} }
systemversion "10.0.17763.0" systemversion "latest"
defines { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" } defines { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" }
rule "ProtobufCompiler" rule "ProtobufCompiler"

View File

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

View File

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

View File

@ -824,6 +824,64 @@ namespace Components
__VMProtectEnd; __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() AntiCheat::AntiCheat()
{ {
__VMProtectBeginUltra(""); __VMProtectBeginUltra("");
@ -831,13 +889,12 @@ namespace Components
time(nullptr); time(nullptr);
AntiCheat::Flags = NO_FLAG; AntiCheat::Flags = NO_FLAG;
#ifdef DEBUG #ifdef DISABLE_ANTICHEAT
Command::Add("penis", [](Command::Params*) Command::Add("penis", [](Command::Params*)
{ {
AntiCheat::CrashClient(); AntiCheat::CrashClient();
}); });
#else #else
Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick(); Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick();
Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick();
Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick(); Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick();
@ -866,6 +923,7 @@ namespace Components
// Set the integrity flag // Set the integrity flag
AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION; AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION;
#endif #endif
__VMProtectEnd; __VMProtectEnd;

View File

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

View File

@ -112,5 +112,9 @@ namespace Components
Utils::Hook::Set<BYTE>(0x4A95F8, 32); Utils::Hook::Set<BYTE>(0x4A95F8, 32);
Utils::Hook::Set<int>(0x42F22B, offsetof(Game::newMapArena_t, mapName) - offsetof(Game::newMapArena_t, other)); 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 namespace Components
{ {
thread_local int AssetHandler::BypassState = 0; thread_local int AssetHandler::BypassState = 0;
bool AssetHandler::ShouldSearchTempAssets = false;
std::map<Game::XAssetType, AssetHandler::IAsset*> AssetHandler::AssetInterfaces; std::map<Game::XAssetType, AssetHandler::IAsset*> AssetHandler::AssetInterfaces;
std::map<Game::XAssetType, Utils::Slot<AssetHandler::Callback>> AssetHandler::TypeCallbacks; std::map<Game::XAssetType, Utils::Slot<AssetHandler::Callback>> AssetHandler::TypeCallbacks;
Utils::Signal<AssetHandler::RestrictCallback> AssetHandler::RestrictSignal; Utils::Signal<AssetHandler::RestrictCallback> AssetHandler::RestrictSignal;
@ -69,6 +70,21 @@ namespace Components
return header; 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() int AssetHandler::HasThreadBypass()
{ {
return AssetHandler::BypassState > 0; return AssetHandler::BypassState > 0;
@ -116,7 +132,7 @@ namespace Components
test al, al test al, al
jnz finishOriginal jnz checkTempAssets
mov ecx, [esp + 18h] // Asset type mov ecx, [esp + 18h] // Asset type
mov ebx, [esp + 1Ch] // Filename mov ebx, [esp + 1Ch] // Filename
@ -139,9 +155,28 @@ namespace Components
test eax, eax test eax, eax
jnz finishFound jnz finishFound
checkTempAssets:
mov al, AssetHandler::ShouldSearchTempAssets // check to see if enabled
test eax, eax
jz finishOriginal
finishOriginal: mov ecx, [esp + 18h] // Asset type
// Asset not found using custom handlers, redirect to DB_FindXAssetHeader 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 ebx, ds:6D7190h // InterlockedDecrement
mov eax, 40793Bh mov eax, 40793Bh
jmp eax jmp eax
@ -181,7 +216,7 @@ namespace Components
} }
// Fix shader const stuff // 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) for (int i = 0; i < 48; ++i)
{ {
@ -454,6 +489,11 @@ namespace Components
Utils::Hook::Set<Game::XAssetEntry*>(0x5BAEA2, entryPool + 1); Utils::Hook::Set<Game::XAssetEntry*>(0x5BAEA2, entryPool + 1);
} }
void AssetHandler::ExposeTemporaryAssets(bool expose)
{
AssetHandler::ShouldSearchTempAssets = expose;
}
AssetHandler::AssetHandler() AssetHandler::AssetHandler()
{ {
this->reallocateEntryPool(); this->reallocateEntryPool();
@ -493,6 +533,210 @@ namespace Components
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool*) 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") if (Dvar::Var("r_noVoid").get<bool>() && type == Game::XAssetType::ASSET_TYPE_XMODEL && name == "void")
{ {
asset.model->numLods = 0; 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_GAMEWORLD_SP, 1);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, ZoneBuilder::IsEnabled() ? 14336 : 7168); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, ZoneBuilder::IsEnabled() ? 14336 * 2 : 7168);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FX, 1200 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, 14000); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, 14000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIMPARTS, 8192); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XANIMPARTS, 8192 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_XMODEL, 5125 * 2);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_PHYSPRESET, 128); 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_PIXELSHADER, ZoneBuilder::IsEnabled() ? 0x4000 : 10000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, ZoneBuilder::IsEnabled() ? 0x2000 : 3072); 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_VERTEXDECL, ZoneBuilder::IsEnabled() ? 0x400 : 196);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_WEAPON, WEAPON_LIMIT); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_WEAPON, WEAPON_LIMIT);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_STRINGTABLE, 800); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_STRINGTABLE, 800);
@ -519,12 +763,14 @@ namespace Components
if (ZoneBuilder::IsEnabled()) if (ZoneBuilder::IsEnabled())
{ {
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_MAP_ENTS, 10); 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_TECHNIQUE_SET, 0x2000);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FONT, 32); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_FONT, 32);
Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_RAWFILE, 2048); 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::IFont_s());
AssetHandler::RegisterInterface(new Assets::IWeapon());
AssetHandler::RegisterInterface(new Assets::IXModel()); AssetHandler::RegisterInterface(new Assets::IXModel());
AssetHandler::RegisterInterface(new Assets::IFxWorld()); AssetHandler::RegisterInterface(new Assets::IFxWorld());
AssetHandler::RegisterInterface(new Assets::IMapEnts()); AssetHandler::RegisterInterface(new Assets::IMapEnts());
@ -535,8 +781,9 @@ namespace Components
AssetHandler::RegisterInterface(new Assets::ISndCurve()); AssetHandler::RegisterInterface(new Assets::ISndCurve());
AssetHandler::RegisterInterface(new Assets::IMaterial()); AssetHandler::RegisterInterface(new Assets::IMaterial());
AssetHandler::RegisterInterface(new Assets::IMenuList()); AssetHandler::RegisterInterface(new Assets::IMenuList());
AssetHandler::RegisterInterface(new Assets::IclipMap_t());
AssetHandler::RegisterInterface(new Assets::ImenuDef_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::IPhysPreset());
AssetHandler::RegisterInterface(new Assets::IXAnimParts()); AssetHandler::RegisterInterface(new Assets::IXAnimParts());
AssetHandler::RegisterInterface(new Assets::IFxEffectDef()); AssetHandler::RegisterInterface(new Assets::IFxEffectDef());

View File

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

View File

@ -1,7 +1,56 @@
#include "STDInclude.hpp" #include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets 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) void IMaterialPixelShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::MaterialPixelShader, 16); AssertSize(Game::MaterialPixelShader, 16);

View File

@ -8,5 +8,9 @@ namespace Assets
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PIXELSHADER; }; virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PIXELSHADER; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; 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" #include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets 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) void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
Game::MaterialTechniqueSet* asset = header.techniqueSet; Game::MaterialTechniqueSet* asset = header.techniqueSet;
for (int i = 0; i < ARRAYSIZE(Game::MaterialTechniqueSet::techniques); ++i) 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 save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(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" #include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets 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) void IMaterialVertexDeclaration::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::MaterialVertexDeclaration, 100); AssertSize(Game::MaterialVertexDeclaration, 100);

View File

@ -8,5 +8,9 @@ namespace Assets
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_VERTEXDECL; }; virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_VERTEXDECL; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; 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" #include "STDInclude.hpp"
#define IW4X_TECHSET_VERSION "0"
namespace Assets 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) void IMaterialVertexShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::MaterialVertexShader, 16); AssertSize(Game::MaterialVertexShader, 16);

View File

@ -8,5 +8,9 @@ namespace Assets
virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_VERTEXSHADER; }; virtual Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_VERTEXSHADER; };
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; 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 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) void IMenuList::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
Game::MenuList *asset = header.menuList; 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 save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(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) if (surf->vertInfo.vertsBlend)
{ {
@ -48,13 +48,23 @@ namespace Assets
} }
// Access index block // Access index block
if (surf->triIndices) if (surf->triIndices)
{ {
surf->triIndices = reader->readArray<unsigned short>(surf->triCount * 3); 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) if (asset->name)
{ {
@ -67,7 +77,7 @@ namespace Assets
for (int i = 0; i < asset->numsurfs; ++i) 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) if (asset->lodInfo[i].modelSurfs)
{ {
asset->lodInfo[i].modelSurfs = reader.readObject<Game::XModelSurfs>(); 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 }); Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { asset->lodInfo[i].modelSurfs });
asset->lodInfo[i].surfs = asset->lodInfo[i].modelSurfs->surfs; 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 }); 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; virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
private: private:
void loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader); std::map<void*, void*> triIndicies;
void loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader); 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); void loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader);
}; };
} }

View File

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

View File

@ -2,6 +2,21 @@
namespace Assets 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) void ImenuDef_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
Game::menuDef_t *asset = header.menu; Game::menuDef_t *asset = header.menu;
@ -37,6 +52,10 @@ namespace Assets
AssertSize(Game::ExpressionSupportingData, 24); AssertSize(Game::ExpressionSupportingData, 24);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("ExpressionSupportingData");
#endif
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
Game::ExpressionSupportingData *dest = buffer->dest<Game::ExpressionSupportingData>(); 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) void ImenuDef_t::save_Statement_s(Game::Statement_s* asset, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::Statement_s, 24); AssertSize(Game::Statement_s, 24);
AssertSize(Game::expressionEntry, 12);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("Statement_s");
#endif
// Write header data // Write header data
Game::Statement_s *dest = buffer->dest<Game::Statement_s>(); Game::Statement_s *dest = buffer->dest<Game::Statement_s>();
buffer->save(asset); buffer->save(asset);
@ -121,6 +148,9 @@ namespace Assets
// Write statement entries // Write statement entries
if (asset->entries) if (asset->entries)
{ {
#ifdef WRITE_LOGS
buffer->enterStruct("statement entries");
#endif
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
// Write entries // Write entries
@ -130,6 +160,9 @@ namespace Assets
// Loop through entries // Loop through entries
for (int i = 0; i < asset->numEntries; ++i) for (int i = 0; i < asset->numEntries; ++i)
{ {
#ifdef WRITE_LOGS
buffer->enterStruct("entry");
#endif
if (asset->entries[i].type) if (asset->entries[i].type)
{ {
switch (asset->entries[i].data.operand.dataType) switch (asset->entries[i].data.operand.dataType)
@ -159,13 +192,23 @@ namespace Assets
break; break;
} }
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
if (asset->supportingData) if (asset->supportingData)
{ {
this->save_ExpressionSupportingData(asset->supportingData, builder); this->save_ExpressionSupportingData(asset->supportingData, builder);
Utils::Stream::ClearPointer(&dest->supportingData); Utils::Stream::ClearPointer(&dest->supportingData);
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
void ImenuDef_t::save_MenuEventHandlerSet(Game::MenuEventHandlerSet* asset, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::save_MenuEventHandlerSet(Game::MenuEventHandlerSet* asset, Components::ZoneBuilder::Zone* builder)
@ -173,6 +216,10 @@ namespace Assets
AssertSize(Game::MenuEventHandlerSet, 8); AssertSize(Game::MenuEventHandlerSet, 8);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("MenuEventHandlerSet");
#endif
// Write header data // Write header data
Game::MenuEventHandlerSet *destset = buffer->dest<Game::MenuEventHandlerSet>(); Game::MenuEventHandlerSet *destset = buffer->dest<Game::MenuEventHandlerSet>();
buffer->save(asset); buffer->save(asset);
@ -191,6 +238,9 @@ namespace Assets
if (asset->eventHandlers[i]) if (asset->eventHandlers[i])
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
#ifdef WRITE_LOGS
buffer->enterStruct("MenuEventHandler");
#endif
// Write menu event handler // Write menu event handler
Game::MenuEventHandler *dest = buffer->dest<Game::MenuEventHandler>(); Game::MenuEventHandler *dest = buffer->dest<Game::MenuEventHandler>();
@ -278,11 +328,17 @@ namespace Assets
} }
break; break;
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
} }
Utils::Stream::ClearPointer(&destset->eventHandlers); Utils::Stream::ClearPointer(&destset->eventHandlers);
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
void ImenuDef_t::save_ItemKeyHandler(Game::ItemKeyHandler* asset, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::save_ItemKeyHandler(Game::ItemKeyHandler* asset, Components::ZoneBuilder::Zone* builder)
@ -290,6 +346,10 @@ namespace Assets
AssertSize(Game::ItemKeyHandler, 12); AssertSize(Game::ItemKeyHandler, 12);
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("ItemKeyHandler");
#endif
while (asset) while (asset)
{ {
// Write header // Write header
@ -313,6 +373,9 @@ namespace Assets
// Next key handler // Next key handler
asset = asset->next; asset = asset->next;
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
#define EVENTHANDLERSET(__indice) \ #define EVENTHANDLERSET(__indice) \
@ -340,6 +403,10 @@ namespace Assets
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
#ifdef WRITE_LOGS
buffer->enterStruct("itemDefData_t");
#endif
// feeder // feeder
if (type == 6) if (type == 6)
{ {
@ -421,6 +488,10 @@ namespace Assets
} }
Utils::Stream::ClearPointer(&dest->typeData.data); 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) 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(); Utils::Stream* buffer = builder->getBuffer();
Game::itemDef_s* dest = buffer->dest<Game::itemDef_s>(); 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); buffer->save(asset);
// window data // window data
@ -458,6 +538,7 @@ namespace Assets
buffer->saveString(asset->dvar); buffer->saveString(asset->dvar);
Utils::Stream::ClearPointer(&dest->dvar); Utils::Stream::ClearPointer(&dest->dvar);
} }
if (asset->dvarTest) if (asset->dvarTest)
{ {
buffer->saveString(asset->dvarTest); buffer->saveString(asset->dvarTest);
@ -478,6 +559,7 @@ namespace Assets
buffer->saveString(asset->enableDvar); buffer->saveString(asset->enableDvar);
Utils::Stream::ClearPointer(&dest->enableDvar); Utils::Stream::ClearPointer(&dest->enableDvar);
} }
if (asset->localVar) if (asset->localVar)
{ {
buffer->saveString(asset->localVar); buffer->saveString(asset->localVar);
@ -500,6 +582,9 @@ namespace Assets
if (asset->floatExpressions) if (asset->floatExpressions)
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
#ifdef WRITE_LOGS
buffer->enterStruct("floatExpressions");
#endif
Game::ItemFloatExpression* destExp = buffer->dest<Game::ItemFloatExpression>(); Game::ItemFloatExpression* destExp = buffer->dest<Game::ItemFloatExpression>();
buffer->saveArray(asset->floatExpressions, asset->floatExpressionCount); buffer->saveArray(asset->floatExpressions, asset->floatExpressionCount);
@ -508,10 +593,14 @@ namespace Assets
{ {
buffer->align(Utils::Stream::ALIGN_4); buffer->align(Utils::Stream::ALIGN_4);
this->save_Statement_s(asset->floatExpressions[i].expression, builder); 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); Utils::Stream::ClearPointer(&dest->floatExpressions);
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
// Statements // Statements
@ -519,16 +608,23 @@ namespace Assets
STATEMENT(disabledExp); STATEMENT(disabledExp);
STATEMENT(textExp); STATEMENT(textExp);
STATEMENT(materialExp); STATEMENT(materialExp);
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
} }
void ImenuDef_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void ImenuDef_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
{ {
AssertSize(Game::menuDef_t, 400); AssertSize(Game::menuDef_t, 400);
#ifdef WRITE_LOGS
buffer->enterStruct("ImenuDef_t");
#endif
Utils::Stream* buffer = builder->getBuffer(); Utils::Stream* buffer = builder->getBuffer();
Game::menuDef_t* asset = header.menu; Game::menuDef_t* asset = header.menu;
Game::menuDef_t* dest = buffer->dest<Game::menuDef_t>(); Game::menuDef_t* dest = buffer->dest<Game::menuDef_t>();
buffer->save(asset); buffer->save(asset);
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL); buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
@ -603,6 +699,9 @@ namespace Assets
} }
} }
} }
#ifdef WRITE_LOGS
buffer->leaveStruct();
#endif
buffer->popBlock(); buffer->popBlock();
} }

View File

@ -9,7 +9,9 @@ namespace Assets
virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
virtual void mark(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: private:
template <typename T> void save_windowDef_t(Game::windowDef_t* asset, T* dest, Components::ZoneBuilder::Zone* builder) 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::Token Auth::ComputeToken;
Utils::Cryptography::ECC::Key Auth::GuidKey; Utils::Cryptography::ECC::Key Auth::GuidKey;
std::vector<std::uint64_t> Auth::BannedUids = {
0xf4d2c30b712ac6e3,
0xf7e33c4081337fa3,
0x6f5597f103cc50e9
};
void Auth::Frame() void Auth::Frame()
{ {
if (Auth::TokenContainer.generating) if (Auth::TokenContainer.generating)
@ -53,7 +59,7 @@ namespace Components
Auth::TokenContainer.cancel = false; Auth::TokenContainer.cancel = false;
} }
} }
void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len) void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len)
{ {
// Ensure our certificate is loaded // Ensure our certificate is loaded
@ -64,6 +70,13 @@ namespace Components
return; 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); std::string connectString(format, len);
Game::SV_Cmd_TokenizeString(connectString.data()); Game::SV_Cmd_TokenizeString(connectString.data());
@ -86,7 +99,7 @@ namespace Components
return; 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)); infostr.set("realsteamId", Utils::String::VA("%llX", Steam::Proxy::SteamUser_->GetSteamID().bits));
} }
@ -187,6 +200,12 @@ namespace Components
return; 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())) if (xuid != Auth::GetKeyHash(connectData.publickey()))
{ {
Network::Send(address, "error\nXUID doesn't match the certificate!"); 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) void Auth::LoadKey(bool force)
{ {
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
@ -287,10 +314,7 @@ namespace Components
if (!Auth::GuidKey.isValid()) if (!Auth::GuidKey.isValid())
{ {
Auth::GuidToken.clear(); Auth::GenerateKey();
Auth::ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey();
} }
} }

View File

@ -13,6 +13,8 @@ namespace Components
static void StoreKey(); static void StoreKey();
static void LoadKey(bool force = false); static void LoadKey(bool force = false);
static void GenerateKey();
static unsigned __int64 GetKeyHash(); static unsigned __int64 GetKeyHash();
static unsigned __int64 GetKeyHash(const std::string& key); static unsigned __int64 GetKeyHash(const std::string& key);
@ -41,7 +43,8 @@ namespace Components
static Utils::Cryptography::Token GuidToken; static Utils::Cryptography::Token GuidToken;
static Utils::Cryptography::Token ComputeToken; static Utils::Cryptography::Token ComputeToken;
static Utils::Cryptography::ECC::Key GuidKey; 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 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 ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr);
static void DirectConnectStub(); static void DirectConnectStub();

View File

@ -1,12 +1,94 @@
#include "STDInclude.hpp" #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 namespace Components
{ {
std::vector<std::string> Bots::BotNames; 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) 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; static int botId = 0;
const char* botName;
if (Bots::BotNames.empty()) if (Bots::BotNames.empty())
{ {
@ -27,15 +109,19 @@ namespace Components
} }
} }
} }
if (Bots::BotNames.empty())
{
Bots::BotNames.push_back("bot");
}
} }
botId %= Bots::BotNames.size(); if (!Bots::BotNames.empty())
strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, Bots::BotNames[botId++].data(), protocol, checksum, statVer, statStuff, port), 0x400); {
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) 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() Bots::Bots()
{ {
// Replace connect string // Replace connect string
@ -78,6 +399,50 @@ namespace Components
// Intercept sprintf for the connect string // Intercept sprintf for the connect string
Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); 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) Command::Add("spawnBot", [](Command::Params* params)
{ {
unsigned int count = 1; unsigned int count = 1;
@ -103,6 +468,8 @@ namespace Components
Bots::Spawn(count); Bots::Spawn(count);
}); });
Bots::AddMethods();
} }
Bots::~Bots() Bots::~Bots()

View File

@ -7,6 +7,8 @@ namespace Components
public: public:
Bots(); Bots();
~Bots(); ~Bots();
static unsigned int GetClientNum(Game::client_s*);
static bool IsValidClientNum(unsigned int);
private: private:
static std::vector<std::string> BotNames; 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 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 Spawn(unsigned int count);
static void AddMethods();
}; };
} }

View File

@ -220,6 +220,12 @@ namespace Components
// Table lookup stuff // Table lookup stuff
Utils::Hook(0x62DCC1, CardTitles::TableLookupByRowHookStub).install()->quick(); Utils::Hook(0x62DCC1, CardTitles::TableLookupByRowHookStub).install()->quick();
Utils::Hook::Nop(0x62DCC6, 1); 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() CardTitles::~CardTitles()

View File

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

View File

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

View File

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

View File

@ -748,7 +748,7 @@ namespace Components
{ {
if (Dedicated::IsEnabled()) return; 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 // Hook Interface creation
Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub); Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub);

View File

@ -120,65 +120,7 @@ namespace Components
if (ev == MG_EV_RECV) if (ev == MG_EV_RECV)
{ {
size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data)); size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
fDownload->receivedBytes += bytes; Download::DownloadProgress(fDownload, 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;
}
} }
if (ev == MG_EV_HTTP_REPLY) if (ev == MG_EV_HTTP_REPLY)
@ -259,7 +201,7 @@ namespace Components
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : ""); + (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; Download::FileDownload fDownload;
fDownload.file = file; fDownload.file = file;
@ -270,17 +212,42 @@ namespace Components
Utils::String::Replace(url, " ", "%20"); Utils::String::Replace(url, " ", "%20");
// Just a speedtest ;)
//download->totalBytes = 1048576000;
//url = "http://speed.hetzner.de/1GB.bin";
download->valid = true; download->valid = true;
ZeroMemory(&download->mgr, sizeof download->mgr); /*ZeroMemory(&download->mgr, sizeof download->mgr);
mg_mgr_init(&download->mgr, &fDownload); mg_mgr_init(&download->mgr, &fDownload);
mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr); mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr);
while (fDownload.downloading && !fDownload.download->terminateThread) 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; download->valid = false;
if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash) if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash)
@ -430,6 +397,69 @@ namespace Components
return nullptr; 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) bool Download::VerifyPassword(mg_connection *nc, http_message* message)
{ {
std::string g_password = Dvar::Var("g_password").get<std::string>(); 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; //if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
Utils::InfoString status = ServerInfo::GetInfo(); Utils::InfoString status = ServerInfo::GetInfo();
Utils::InfoString host = ServerInfo::GetHostInfo();
std::map<std::string, json11::Json> info; std::map<std::string, json11::Json> info;
info["status"] = status.to_json(); info["status"] = status.to_json();
info["host"] = host.to_json();
std::vector<json11::Json> players; std::vector<json11::Json> players;
@ -932,36 +964,35 @@ namespace Components
Download::ScriptDownloads.clear(); 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; if (object == download->getObject())
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()) download->cancel();
{ break;
download->cancel();
break;
}
} }
}); }
} });
} }
Download::~Download() Download::~Download()

View File

@ -26,7 +26,7 @@ namespace Components
bool terminateThread; bool terminateThread;
bool isMap; bool isMap;
bool isPrivate; bool isPrivate;
mg_mgr mgr; //mg_mgr mgr;
Network::Address target; Network::Address target;
std::string hashedPassword; std::string hashedPassword;
std::string mod; std::string mod;
@ -64,7 +64,7 @@ namespace Components
if (this->valid) if (this->valid)
{ {
this->valid = false; this->valid = false;
mg_mgr_free(&(this->mgr)); //mg_mgr_free(&(this->mgr));
} }
} }
}; };
@ -212,6 +212,8 @@ namespace Components
static bool Terminate; static bool Terminate;
static bool ServerRunning; static bool ServerRunning;
static void DownloadProgress(FileDownload* fDownload, size_t bytes);
static bool VerifyPassword(mg_connection *nc, http_message* message); static bool VerifyPassword(mg_connection *nc, http_message* message);
static void EventHandler(mg_connection *nc, int ev, void *ev_data); 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 const_cast<char*>(this->dvar->current.string);
} }
return ""; return const_cast<char*>("");
} }
template <> const char* Dvar::Var::get() template <> const char* Dvar::Var::get()
{ {
@ -138,6 +138,10 @@ namespace Components
{ {
return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description); 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) void Dvar::OnInit(Utils::Slot<Scheduler::Callback> callback)
{ {
@ -223,6 +227,9 @@ namespace Components
// un-cheat cg_fov and add archive flags // 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); 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 // 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); 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 // Hook dvar 'name' registration
Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).install()->quick(); 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 // Don't allow setting cheat protected dvars via menus
Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
Utils::Hook(0x63CA96, 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); 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"); bool doFullDump = Flags::HasFlag("bigdumps") || Flags::HasFlag("reallybigdumps");
/*if (!doFullDump) /*if (!doFullDump)
@ -131,15 +131,15 @@ namespace Components
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
} }
#ifndef DISABLE_ANTICHEAT //if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
AntiCheat::InstallLibHook();
#endif
if (ExceptionInfo->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
{ {
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
} }
#ifndef DISABLE_ANTICHEAT
AntiCheat::InstallLibHook();
#endif
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
@ -231,26 +231,6 @@ namespace Components
auto oldHandler = Exception::Hook(); auto oldHandler = Exception::Hook();
Logger::Print("Old exception handler was 0x%010X.\n", oldHandler); 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() Exception::~Exception()

View File

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

View File

@ -320,7 +320,7 @@ namespace Components
Zones::SetVersion(*version); Zones::SetVersion(*version);
// Allow loading of codo versions // Allow loading of codo versions
if (*version >= VERSION_ALPHA2 && *version <= 360) if ((*version >= VERSION_ALPHA2 && *version <= 360) || (*version >= 423 && *version <= 461))
{ {
*version = XFILE_VERSION; *version = XFILE_VERSION;
} }
@ -369,7 +369,7 @@ namespace Components
unsigned long outLen = sizeof(FastFiles::CurrentKey); unsigned long outLen = sizeof(FastFiles::CurrentKey);
rsa_import(FastFiles::ZoneKey, sizeof(FastFiles::ZoneKey), &key); 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); rsa_free(&key);
ctr_start(aes, FastFiles::CurrentKey.iv, FastFiles::CurrentKey.key, sizeof(FastFiles::CurrentKey.key), 0, 0, &FastFiles::CurrentCTR); ctr_start(aes, FastFiles::CurrentKey.iv, FastFiles::CurrentKey.key, sizeof(FastFiles::CurrentKey.key), 0, 0, &FastFiles::CurrentCTR);
@ -484,7 +484,7 @@ namespace Components
} }
#endif #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 // read the actual count from the varXModelSurfs ptr
auto surface = *reinterpret_cast<Game::XModelSurfs**>(0x0112A95C); auto surface = *reinterpret_cast<Game::XModelSurfs**>(0x0112A95C);

View File

@ -172,11 +172,11 @@ namespace Components
return fileList; 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 }; 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_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) int FileSystem::ReadFile(const char* path, char** buffer)
@ -263,10 +263,18 @@ namespace Components
void FileSystem::FsRestartSync(int a1, int a2) void FileSystem::FsRestartSync(int a1, int a2)
{ {
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex); std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
Maps::GetUserMap()->freeIwd();
Utils::Hook::Call<void(int, int)>(0x461A50)(a1, a2); // FS_Restart Utils::Hook::Call<void(int, int)>(0x461A50)(a1, a2); // FS_Restart
Maps::GetUserMap()->reloadIwd(); 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() void FileSystem::DelayLoadImagesSync()
{ {
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex); 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(0x4C8609, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart
Utils::Hook(0x5AC68E, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // CL_ParseServerMessage 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 // Synchronize db image loading
Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick(); Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick();
Utils::Hook(0x4D32BC, FileSystem::LoadTextureSync, 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> 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 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: private:
static std::mutex Mutex; static std::mutex Mutex;
@ -109,6 +109,7 @@ namespace Components
static void FsStartupSync(const char* a1); static void FsStartupSync(const char* a1);
static void FsRestartSync(int a1, int a2); static void FsRestartSync(int a1, int a2);
static void FsShutdownSync(int a1);
static void DelayLoadImagesSync(); static void DelayLoadImagesSync();
static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image); static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image);

View File

@ -124,7 +124,7 @@ namespace Components
void Friends::UpdateState(bool force) 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) if (force)
{ {
@ -186,7 +186,7 @@ namespace Components
{ {
std::lock_guard<std::recursive_mutex> _(Friends::Mutex); 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 // Split up the list
for (auto entry : Friends::FriendsList) for (auto entry : Friends::FriendsList)
@ -228,7 +228,7 @@ namespace Components
void Friends::SetPresence(const std::string& key, const std::string& value) 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()); Friends::SetRawPresence(key.data(), value.data());
} }
@ -494,6 +494,11 @@ namespace Components
return appId; return appId;
} }
bool Friends::IsInvisible()
{
return Friends::InitialState == 7;
}
void Friends::UpdateTimeStamp() void Friends::UpdateTimeStamp()
{ {
Friends::SetPresence("iw4x_playing", Utils::String::VA("%d", Steam::SteamUtils()->GetServerRealTime())); Friends::SetPresence("iw4x_playing", Utils::String::VA("%d", Steam::SteamUtils()->GetServerRealTime()));
@ -696,10 +701,10 @@ namespace Components
{ {
if (Steam::Proxy::SteamFriends) 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) if (Steam::Proxy::ClientFriends)
{ {

View File

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

View File

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

View File

@ -185,15 +185,23 @@ namespace Components
"Dasfonia", "Dasfonia",
"Deity", "Deity",
"Dizzy", "Dizzy",
"Dss0",
"H3X1C",
"HardNougat", "HardNougat",
"Homura",
"INeedGames",
"Killera", "Killera",
"Lithium", "Lithium",
"OneFourOne",
"quaK",
"RaidMax",
"Revo", "Revo",
"RezTech", "RezTech",
"Shadow the Hedgehog", "Shadow the Hedgehog",
"Slykuiper", "Slykuiper",
"st0rm", "st0rm",
"VVLNT", "VVLNT",
"X3RX35"
}; };
static const char* specials[] = static const char* specials[] =
@ -202,7 +210,7 @@ namespace Components
"aerosoul94", "aerosoul94",
"ReactIW4", "ReactIW4",
"IW4Play", "IW4Play",
"Rocket V2", "V2",
"luckyy" "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::SPMap = false;
Maps::CurrentMainZone = zoneInfo->name; Maps::CurrentMainZone = zoneInfo->name;
Maps::CurrentDependencies.clear(); Maps::CurrentDependencies.clear();
for (auto i = Maps::DependencyList.begin(); i != Maps::DependencyList.end(); ++i) for (auto i = Maps::DependencyList.begin(); i != Maps::DependencyList.end(); ++i)
{ {
@ -149,7 +149,7 @@ namespace Components
std::vector<Game::XZoneInfo> data; std::vector<Game::XZoneInfo> data;
Utils::Merge(&data, zoneInfo, zoneCount); Utils::Merge(&data, zoneInfo, zoneCount);
Game::XZoneInfo team; Game::XZoneInfo team;
team.allocFlags = zoneInfo->allocFlags; team.allocFlags = zoneInfo->allocFlags;
team.freeFlags = zoneInfo->freeFlags; team.freeFlags = zoneInfo->freeFlags;
@ -252,12 +252,8 @@ namespace Components
asset.mapEnts->entityString = const_cast<char*>(mapEntities.data()); asset.mapEnts->entityString = const_cast<char*>(mapEntities.data());
asset.mapEnts->numEntityChars = mapEntities.size() + 1; 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 // This is broken
if ((type == Game::XAssetType::ASSET_TYPE_MENU || type == Game::XAssetType::ASSET_TYPE_MENULIST) && Zones::Version() >= 359) 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"); Logger::Print("Waiting for database...\n");
while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); 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; return Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP].gameWorldSp->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_MP].gameWorldMp->g_glassData;
} }
__declspec(naked) void Maps::GetWorldDataStub() __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) void Maps::AddDlc(Maps::DLC dlc)
{ {
for (auto& pack : Maps::DlcPacks) for (auto& pack : Maps::DlcPacks)
@ -821,12 +682,16 @@ namespace Components
Game::dvar_t* Maps::GetSpecularDvar() Game::dvar_t* Maps::GetSpecularDvar()
{ {
Game::dvar_t*& r_specular = *reinterpret_cast<Game::dvar_t**>(0x69F0D94); 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()) if (Maps::IsCustomMap())
{ {
static Game::dvar_t noSpecular; if (!r_specularCustomMaps->current.enabled)
ZeroMemory(&noSpecular, sizeof noSpecular); {
return &noSpecular; static Game::dvar_t noSpecular;
ZeroMemory(&noSpecular, sizeof noSpecular);
return &noSpecular;
}
} }
return r_specular; 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() Maps::Maps()
{ {
Dvar::OnInit([]() 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({ 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({ 2, "Resurgence Pack", {"mp_abandon", "mp_vacant", "mp_trailerpark", "mp_strike", "mp_fuel2"} });
Maps::AddDlc({ 3, "Nuketown", {"mp_nuked"} }); Maps::AddDlc({ 3, "Nuketown", {"mp_nuked"} });
Maps::AddDlc({ 4, "Classics Pack", {"mp_cross_fire", "mp_cargoship", "mp_bloc"} }); Maps::AddDlc({ 4, "Classics Pack #1", {"mp_cross_fire", "mp_cargoship", "mp_bloc"} });
Maps::AddDlc({ 5, "Classics Pack", {"mp_killhouse", "mp_bog_sh"} }); Maps::AddDlc({ 5, "Classics Pack #2", {"mp_killhouse", "mp_bog_sh"} });
Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} }); Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} });
Maps::AddDlc({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} }); 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"} }); 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) if (pack.index == dlc)
{ {
News::LaunchUpdater(Utils::String::VA("-dlc %i -c", pack.index)); ShellExecute(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW);
//ShellExecuteA(nullptr, "open", pack.url.data(), nullptr, nullptr, SW_SHOWNORMAL);
return; 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 //#define SORT_SMODELS
#if !defined(DEBUG) || !defined(SORT_SMODELS) #if !defined(DEBUG) || !defined(SORT_SMODELS)
// Don't sort static models // Don't sort static models
@ -991,33 +870,6 @@ namespace Components
//Maps::AddDependency("co_hunted", "mp_storm"); //Maps::AddDependency("co_hunted", "mp_storm");
//Maps::AddDependency("mp_shipment", "mp_shipment_long"); //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 // Allow hiding specific smodels
Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick(); Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick();

View File

@ -100,10 +100,6 @@ namespace Components
static void AddDlc(DLC dlc); static void AddDlc(DLC dlc);
static void UpdateDlcStatus(); static void UpdateDlcStatus();
#if defined(DEBUG) && defined(ENABLE_DXSDK)
static void ExportMap(Game::GfxWorld* world);
#endif
static void PrepareUsermap(const char* mapname); static void PrepareUsermap(const char* mapname);
static void SpawnServerStub(); static void SpawnServerStub();
static void LoadMapLoadscreenStub(); static void LoadMapLoadscreenStub();
@ -123,5 +119,6 @@ namespace Components
static Game::dvar_t* GetSpecularDvar(); static Game::dvar_t* GetSpecularDvar();
static void SetSpecularStub1(); static void SetSpecularStub1();
static void SetSpecularStub2(); 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(); int handle = Menus::ReserveSourceHandle();
if (!Menus::IsValidSourceHandle(handle)) return 0; // No free source slot! 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) if (!script)
{ {
@ -70,7 +70,7 @@ namespace Components
script->next = nullptr; script->next = nullptr;
Game::source_t *source = allocator->allocate<Game::source_t>(); Game::source_t* source = allocator->allocate<Game::source_t>();
if (!source) if (!source)
{ {
Game::FreeMemory(script); Game::FreeMemory(script);
@ -83,7 +83,7 @@ namespace Components
source->defines = nullptr; source->defines = nullptr;
source->indentstack = nullptr; source->indentstack = nullptr;
source->skip = 0; 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; Game::sourceFiles[handle] = source;
@ -173,6 +173,69 @@ namespace Components
return menu; 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::LoadMenu(const std::string& menu)
{ {
std::vector<std::pair<bool, Game::menuDef_t*>> menus; std::vector<std::pair<bool, Game::menuDef_t*>> menus;
@ -221,15 +284,15 @@ namespace Components
if (menus.empty()) if (menus.empty())
{ {
// // Try loading the original menu, if we can't load our custom one // // 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; // Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu;
// //
// if (originalMenu) // if (originalMenu)
// { // {
// menus.push_back({ false, originalMenu }); // menus.push_back({ false, originalMenu });
// } // }
// else // else
// { // {
menus.push_back({ false, menudef }); // Native menu menus.push_back({ false, menudef }); // Native menu
// } // }
} }
@ -313,7 +376,7 @@ namespace Components
} }
} }
if(increment) ++i; if (increment) ++i;
} }
Utils::Merge(menus, newMenus); Utils::Merge(menus, newMenus);
@ -337,7 +400,7 @@ namespace Components
for (auto menu : Menus::CustomMenus) for (auto menu : Menus::CustomMenus)
{ {
bool hasMenu = false; bool hasMenu = false;
for (auto &loadedMenu : menus) for (auto& loadedMenu : menus)
{ {
if (loadedMenu.second->window.name == menu) if (loadedMenu.second->window.name == menu)
{ {
@ -383,7 +446,7 @@ namespace Components
if (!Menus::IsValidSourceHandle(handle)) return; if (!Menus::IsValidSourceHandle(handle)) return;
Game::source_t *source = Game::sourceFiles[handle]; Game::source_t* source = Game::sourceFiles[handle];
while (source->scriptstack) while (source->scriptstack)
{ {
@ -517,7 +580,7 @@ namespace Components
// EDIT: We might also remove the old instances inside RemoveMenu // 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 // 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? // 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; if (!menu || !menu->window.name) return;
std::string name = menu->window.name; std::string name = menu->window.name;
@ -580,12 +643,12 @@ namespace Components
Menus::MenuList.clear(); 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()) }; 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 }; Game::XAssetHeader header = { nullptr };
@ -643,7 +706,7 @@ namespace Components
return header; 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) if (menu && menu->window.name)
{ {
@ -664,7 +727,7 @@ namespace Components
return Game::Menu_IsVisible(dc, menu); 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 // Search menu in context
int i = 0; int i = 0;
@ -694,6 +757,54 @@ namespace Components
Menus::CustomMenus.push_back(menu); 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() Menus::Menus()
{ {
if (Dedicated::IsEnabled()) return; if (Dedicated::IsEnabled()) return;
@ -702,97 +813,101 @@ namespace Components
Menus::FreeEverything(); Menus::FreeEverything();
// Intercept asset finding // Intercept asset finding
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuLoad); AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENU, Menus::MenuFindHook);
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, Menus::MenuFileLoad); AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_MENULIST, Menus::MenuListFindHook);
// Don't open connect menu // Don't open connect menu
//Utils::Hook::Nop(0x428E48, 5); //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 // Use the connect menu open call to update server motds
Utils::Hook(0x428E48, []() Utils::Hook(0x428E48, []()
{
if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost)
{ {
Dvar::Var("didyouknow").set(Party::GetMotd()); if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost)
} {
}, HOOK_CALL).install()->quick(); Dvar::Var("didyouknow").set(Party::GetMotd());
}
}, HOOK_CALL).install()->quick();
// Intercept menu painting // Intercept menu painting
Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick(); Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick();
// disable the 2 new tokens in ItemParse_rect // disable the 2 new tokens in ItemParse_rect
Utils::Hook::Set<BYTE>(0x640693, 0xEB); Utils::Hook::Set<BYTE>(0x640693, 0xEB);
// don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail) // don't load ASSET_TYPE_MENU assets for every menu (might cause patch menus to fail)
Utils::Hook::Nop(0x453406, 5); Utils::Hook::Nop(0x453406, 5);
//make Com_Error and similar go back to main_text instead of menu_xboxlive. //make Com_Error and similar go back to main_text instead of menu_xboxlive.
Utils::Hook::SetString(0x6FC790, "main_text"); Utils::Hook::SetString(0x6FC790, "main_text");
Command::Add("openmenu", [](Command::Params* params) Command::Add("openmenu", [](Command::Params* params)
{ {
if (params->length() != 2) if (params->length() != 2)
{ {
Logger::Print("USAGE: openmenu <menu name>\n"); Logger::Print("USAGE: openmenu <menu name>\n");
return; return;
} }
// Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. // 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>()) if (Dvar::Var("cl_ingame").get<bool>())
{ {
Game::Key_SetCatcher(0, 16); 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*) Command::Add("reloadmenus", [](Command::Params*)
{ {
// Close all menus // Close all menus
Game::Menus_CloseAll(Game::uiContext); Game::Menus_CloseAll(Game::uiContext);
// Free custom menus // Free custom menus
Menus::FreeEverything(); Menus::FreeEverything();
// Only disconnect if in-game, context is updated automatically! // Only disconnect if in-game, context is updated automatically!
if (Game::CL_IsCgameInitialized()) if (Game::CL_IsCgameInitialized())
{ {
Game::Cbuf_AddText(0, "disconnect\n"); Game::Cbuf_AddText(0, "disconnect\n");
} }
else else
{ {
// Reinitialize ui context // Reinitialize ui context
Utils::Hook::Call<void()>(0x401700)(); Utils::Hook::Call<void()>(0x401700)();
// Reopen main menu // Reopen main menu
Game::Menus_OpenByName(Game::uiContext, "main_text"); Game::Menus_OpenByName(Game::uiContext, "main_text");
} }
}); });
#ifndef DISABLE_ANTICHEAT #ifndef DISABLE_ANTICHEAT
Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner2); Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner2);
#endif #endif
Command::Add("mp_QuickMessage", [](Command::Params*) Command::Add("mp_QuickMessage", [](Command::Params*)
{ {
Command::Execute("openmenu quickmessage"); Command::Execute("openmenu quickmessage");
}); });
// Define custom menus here // Define custom menus here
Menus::Add("ui_mp/changelog.menu"); Menus::Add("ui_mp/changelog.menu");
Menus::Add("ui_mp/theater_menu.menu"); Menus::Add("ui_mp/theater_menu.menu");
Menus::Add("ui_mp/pc_options_multi.menu"); Menus::Add("ui_mp/pc_options_multi.menu");
Menus::Add("ui_mp/pc_options_game.menu"); Menus::Add("ui_mp/pc_options_game.menu");
Menus::Add("ui_mp/stats_reset.menu"); Menus::Add("ui_mp/stats_reset.menu");
Menus::Add("ui_mp/stats_unlock.menu"); Menus::Add("ui_mp/stats_unlock.menu");
Menus::Add("ui_mp/security_increase_popmenu.menu"); Menus::Add("ui_mp/security_increase_popmenu.menu");
Menus::Add("ui_mp/mod_download_popmenu.menu"); Menus::Add("ui_mp/mod_download_popmenu.menu");
Menus::Add("ui_mp/popup_friends.menu"); Menus::Add("ui_mp/popup_friends.menu");
Menus::Add("ui_mp/menu_first_launch.menu"); Menus::Add("ui_mp/menu_first_launch.menu");
Menus::Add("ui_mp/startup_messages.menu"); Menus::Add("ui_mp/startup_messages.menu");
Menus::Add("ui_mp/pc_store.menu"); Menus::Add("ui_mp/pc_store.menu");
Menus::Add("ui_mp/iw4x_credits.menu"); Menus::Add("ui_mp/iw4x_credits.menu");
Menus::Add("ui_mp/resetclass.menu"); Menus::Add("ui_mp/resetclass.menu");
Menus::Add("ui_mp/popup_customtitle.menu");
} }
Menus::~Menus() Menus::~Menus()

View File

@ -15,18 +15,21 @@ namespace Components
static void Add(const std::string& menu); 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: private:
static std::unordered_map<std::string, Game::menuDef_t*> MenuList; static std::unordered_map<std::string, Game::menuDef_t*> MenuList;
static std::unordered_map<std::string, Game::MenuList*> MenuListList; static std::unordered_map<std::string, Game::MenuList*> MenuListList;
static std::vector<std::string> CustomMenus; static std::vector<std::string> CustomMenus;
static Game::XAssetHeader MenuLoad(Game::XAssetType type, const std::string& filename); static Game::XAssetHeader MenuFindHook(Game::XAssetType type, const std::string& filename);
static Game::XAssetHeader MenuFileLoad(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* LoadMenuList(Game::MenuList* menuList);
static Game::MenuList* LoadScriptMenu(const char* menu); 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 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); 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(const std::string& menuList);
static void RemoveMenuList(Game::MenuList* 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! // Ugly!
static int KeywordHash(char* key); 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() Network::Network()
{ {
AssertSize(Game::netadr_t, 20); AssertSize(Game::netadr_t, 20);
@ -358,7 +364,7 @@ namespace Components
Utils::Hook::Set<BYTE>(0x4050A5, 125); Utils::Hook::Set<BYTE>(0x4050A5, 125);
// Parse port as short in Net_AddrToString // 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 // Install startup handler
Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).install()->quick();
@ -372,6 +378,9 @@ namespace Components
// Install packet deploy hook // Install packet deploy hook
Utils::Hook::RedirectJump(0x5AA713, Network::DeployPacketStub); 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::Handle("resolveAddress", [](Address address, const std::string& /*data*/)
{ {
Network::SendRaw(address, address.getString()); 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 Handle(const std::string& packet, Utils::Slot<Callback> callback);
static void OnStart(Utils::Slot<CallbackRaw> callback); static void OnStart(Utils::Slot<CallbackRaw> callback);
// Send quake-styled binary data // Send quake-styled binary data
static void Send(Address target, const std::string& data); static void Send(Address target, const std::string& data);
static void Send(Game::netsrc_t type, 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 NetworkStartStub();
static void PacketErrorCheck(); 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; bool News::Terminate;
std::thread News::Thread; std::thread News::Thread;
std::string News::UpdaterArgs;
std::string News::UpdaterHash;
std::mutex News::UpdaterMutex;
bool News::unitTest() bool News::unitTest()
{ {
@ -33,165 +30,18 @@ namespace Components
return result; 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() const char* News::GetNewsText()
{ {
return Localization::Get("MPUI_MOTD_TEXT"); 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::News()
{ {
News::UpdaterArgs.clear();
News::UpdaterHash.clear();
if (ZoneBuilder::IsEnabled() || Dedicated::IsEnabled()) return; // Maybe also dedi? if (ZoneBuilder::IsEnabled() || Dedicated::IsEnabled()) return; // Maybe also dedi?
Dvar::Register<bool>("g_firstLaunch", true, Game::DVAR_FLAG_SAVED, ""); 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_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) UIScript::Add("checkFirstLaunch", [](UIScript::Token)
{ {
@ -209,14 +59,18 @@ namespace Components
UIScript::Add("visitWiki", [](UIScript::Token) 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_CHANGELOG_TEXT", "Loading...");
Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFAULT); Localization::Set("MPUI_MOTD_TEXT", NEWS_MOTD_DEFAULT);
//News::GetLatestUpdater();
// make newsfeed (ticker) menu items not cut off based on safe area // make newsfeed (ticker) menu items not cut off based on safe area
Utils::Hook::Nop(0x63892D, 5); 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::Nop(0x6388BB, 2); // skip the "if (item->text[0] == '@')" localize check
Utils::Hook(0x6388C1, News::GetNewsText, HOOK_CALL).install()->quick(); 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()) if (!Utils::IsWineEnvironment() && !Loader::IsPerformingUnitTests())
{ {
News::Terminate = false; News::Terminate = false;
@ -251,12 +94,8 @@ namespace Components
if (!Loader::IsPerformingUnitTests() && !News::Terminate) if (!Loader::IsPerformingUnitTests() && !News::Terminate)
{ {
News::GetLatestUpdater();
while (!News::Terminate) while (!News::Terminate)
{ {
News::CheckForUpdate();
// Sleep for 3 minutes // Sleep for 3 minutes
for (int i = 0; i < 180 && !News::Terminate; ++i) 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() void News::preDestroy()
{ {
News::Terminate = true; News::Terminate = true;

View File

@ -6,27 +6,16 @@ namespace Components
{ {
public: public:
News(); News();
~News();
void preDestroy() override; void preDestroy() override;
bool unitTest() override; bool unitTest() override;
static void LaunchUpdater(const std::string& params);
static bool Updating();
private: private:
static std::string UpdaterArgs;
static std::string UpdaterHash;
static std::thread Thread; static std::thread Thread;
static std::mutex UpdaterMutex;
static bool Terminate; static bool Terminate;
static bool GetLatestUpdater();
static bool DownloadUpdater(); static bool DownloadUpdater();
static void CheckForUpdate();
static void ExitProcessStub(unsigned int exitCode);
static const char* GetNewsText(); 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() && Dvar::Var("sv_lanOnly").get<bool>()) return;
if (!Dedicated::IsEnabled() && Game::CL_IsCgameInitialized()) if (!Dedicated::IsEnabled() && *Game::clcState > 0)
{ {
wasIngame = true; wasIngame = true;
return; // don't run while ingame because it can still cause lag spikes on lower end PCs 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) 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); std::lock_guard<std::recursive_mutex> _(Node::Mutex);
for (auto& node : Node::Nodes) // need to keep the message size below 1404 bytes else recipient will just drop it
{ std::vector<std::string> nodeListReponseMessages;
if (node.isValid())
{
std::string* str = list.add_nodes();
sockaddr addr = node.address.getSockAddr(); for (size_t curNode = 0; curNode < Node::Nodes.size();)
str->append(reinterpret_cast<char*>(&addr), sizeof(addr)); {
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() unsigned short Node::GetPort()

View File

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

View File

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

View File

@ -150,7 +150,7 @@ namespace Components
Playlist::Playlist() Playlist::Playlist()
{ {
// Default playlists // Default playlists
Utils::Hook::Set<char*>(0x60B06E, "playlists_default.info"); Utils::Hook::Set<const char*>(0x60B06E, "playlists_default.info");
// disable playlist download function // disable playlist download function
Utils::Hook::Set<BYTE>(0x4D4790, 0xC3); 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) bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size)
{ {
strncpy(dest, source, size - 1); strncpy(dest, source, size - 1);
@ -128,7 +148,7 @@ namespace Components
{ {
if (!dest[i]) break; if (!dest[i]) break;
if (dest[i] > 125 || dest[i] < 32) if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%')
{ {
return false; return false;
} }
@ -139,7 +159,7 @@ namespace Components
__declspec(naked) void QuickPatch::InvalidNameStub() __declspec(naked) void QuickPatch::InvalidNameStub()
{ {
static char* kick_reason = "Invalid name detected."; static const char* kick_reason = "Invalid name detected.";
__asm __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::QuickPatch()
{ {
QuickPatch::FrameTime = 0; QuickPatch::FrameTime = 0;
@ -171,12 +404,70 @@ namespace Components
QuickPatch::FrameTime = Game::Sys_Milliseconds(); 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 // Disallow invalid player names
Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick();
// Javelin fix // Javelin fix
Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick(); 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 // Make sure preDestroy is called when the game shuts down
Scheduler::OnShutdown(Loader::PreDestroy); Scheduler::OnShutdown(Loader::PreDestroy);
@ -213,16 +504,16 @@ namespace Components
Utils::Hook::Set<DWORD>(0x45ACE0, 0xC301B0); Utils::Hook::Set<DWORD>(0x45ACE0, 0xC301B0);
// fs_basegame // fs_basegame
Utils::Hook::Set<char*>(0x6431D1, BASEGAME); Utils::Hook::Set<const char*>(0x6431D1, BASEGAME);
// UI version string // UI version string
Utils::Hook::Set<char*>(0x43F73B, "IW4x: " VERSION); Utils::Hook::Set<const char*>(0x43F73B, "IW4x: " VERSION);
// console version string // 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 // version string
Utils::Hook::Set<char*>(0x60BD56, "IW4x (" VERSION ")"); Utils::Hook::Set<const char*>(0x60BD56, "IW4x (" VERSION ")");
// version string color // version string color
static float buildLocColor[] = { 1.0f, 1.0f, 1.0f, 0.8f }; static float buildLocColor[] = { 1.0f, 1.0f, 1.0f, 0.8f };
@ -239,33 +530,33 @@ namespace Components
// console title // console title
if (ZoneBuilder::IsEnabled()) if (ZoneBuilder::IsEnabled())
{ {
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder"); Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder");
} }
else if (Dedicated::IsEnabled()) else if (Dedicated::IsEnabled())
{ {
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): Dedicated"); Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Dedicated");
} }
else else
{ {
Utils::Hook::Set<char*>(0x4289E8, "IW4x (" VERSION "): Console"); Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Console");
} }
// window title // window title
Utils::Hook::Set<char*>(0x5076A0, "IW4x: Multiplayer"); Utils::Hook::Set<const char*>(0x5076A0, "IW4x: Multiplayer");
// sv_hostname // sv_hostname
Utils::Hook::Set<char*>(0x4D378B, "IW4Host"); Utils::Hook::Set<const char*>(0x4D378B, "IW4Host");
// shortversion // shortversion
Utils::Hook::Set<char*>(0x60BD91, SHORTVERSION); Utils::Hook::Set<const char*>(0x60BD91, SHORTVERSION);
// console logo // console logo
Utils::Hook::Set<char*>(0x428A66, BASEGAME "/images/logo.bmp"); Utils::Hook::Set<const char*>(0x428A66, BASEGAME "/images/logo.bmp");
// splash logo // 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) // Numerical ping (cg_scoreboardPingText 1)
Utils::Hook::Set<BYTE>(0x45888E, 1); Utils::Hook::Set<BYTE>(0x45888E, 1);
@ -366,21 +657,21 @@ namespace Components
// intro stuff // intro stuff
Utils::Hook::Nop(0x60BEE9, 5); // Don't show legals Utils::Hook::Nop(0x60BEE9, 5); // Don't show legals
Utils::Hook::Nop(0x60BEF6, 5); // Don't reset the intro dvar Utils::Hook::Nop(0x60BEF6, 5); // Don't reset the intro dvar
Utils::Hook::Set<char*>(0x60BED2, "unskippablecinematic IW_logo\n"); Utils::Hook::Set<const char*>(0x60BED2, "unskippablecinematic IW_logo\n");
Utils::Hook::Set<char*>(0x51C2A4, "%s\\" BASEGAME "\\video\\%s.bik"); Utils::Hook::Set<const char*>(0x51C2A4, "%s\\" BASEGAME "\\video\\%s.bik");
Utils::Hook::Set<DWORD>(0x51C2C2, 0x78A0AC); Utils::Hook::Set<DWORD>(0x51C2C2, 0x78A0AC);
// Redirect logs // Redirect logs
Utils::Hook::Set<char*>(0x5E44D8, "logs/games_mp.log"); Utils::Hook::Set<const char*>(0x5E44D8, "logs/games_mp.log");
Utils::Hook::Set<char*>(0x60A90C, "logs/console_mp.log"); Utils::Hook::Set<const char*>(0x60A90C, "logs/console_mp.log");
Utils::Hook::Set<char*>(0x60A918, "logs/console_mp.log"); Utils::Hook::Set<const char*>(0x60A918, "logs/console_mp.log");
// Rename config // Rename config
Utils::Hook::Set<char*>(0x461B4B, CLIENT_CONFIG); Utils::Hook::Set<const char*>(0x461B4B, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x47DCBB, CLIENT_CONFIG); Utils::Hook::Set<const char*>(0x47DCBB, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x6098F8, CLIENT_CONFIG); Utils::Hook::Set<const char*>(0x6098F8, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x60B279, CLIENT_CONFIG); Utils::Hook::Set<const char*>(0x60B279, CLIENT_CONFIG);
Utils::Hook::Set<char*>(0x60BBD4, CLIENT_CONFIG); Utils::Hook::Set<const char*>(0x60BBD4, CLIENT_CONFIG);
// Disable profile system // Disable profile system
// Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Causes an error, when calling a harrier killstreak. // Utils::Hook::Nop(0x60BEB1, 5); // GamerProfile_InitAllProfiles - Causes an error, when calling a harrier killstreak.
@ -417,6 +708,13 @@ namespace Components
// Patch SV_IsClientUsingOnlineStatsOffline // Patch SV_IsClientUsingOnlineStatsOffline
Utils::Hook::Set<DWORD>(0x46B710, 0x90C3C033); Utils::Hook::Set<DWORD>(0x46B710, 0x90C3C033);
// Fix mouse lag
Utils::Hook::Nop(0x4731F5, 8);
Scheduler::OnFrame([]()
{
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
});
// Fix mouse pitch adjustments // Fix mouse pitch adjustments
Dvar::Register<bool>("ui_mousePitch", false, Game::DVAR_FLAG_SAVED, ""); Dvar::Register<bool>("ui_mousePitch", false, Game::DVAR_FLAG_SAVED, "");
UIScript::Add("updateui_mousePitch", [](UIScript::Token) UIScript::Add("updateui_mousePitch", [](UIScript::Token)
@ -443,6 +741,12 @@ namespace Components
// Patch selectStringTableEntryInDvar // Patch selectStringTableEntryInDvar
Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub); 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*) Command::Add("unlockstats", [](Command::Params*)
{ {
QuickPatch::UnlockStats(); QuickPatch::UnlockStats();
@ -652,18 +956,21 @@ namespace Components
if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawAabbTrees").get<bool>()) return; if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawAabbTrees").get<bool>()) return;
float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; 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::clipMap_t* clipMap = *reinterpret_cast<Game::clipMap_t**>(0x7998E0);
Game::GfxWorld* gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94); //Game::GfxWorld* gameWorld = *reinterpret_cast<Game::GfxWorld**>(0x66DEE94);
if (!gameWorld) return; if (!clipMap) return;
for (int i = 0; i < gameWorld->dpvsPlanes.cellCount; ++i) for (unsigned short i = 0; i < clipMap->smodelNodeCount; ++i)
{ {
for (int j = 0; j < gameWorld->aabbTreeCounts[i].aabbTreeCount; ++j) Game::R_AddDebugBounds(cyan, &clipMap->smodelNodes[i].bounds);
{ }
Game::R_AddDebugBounds(cyan, &gameWorld->aabbTrees[i].aabbTree[j].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 void SelectStringTableEntryInDvarStub();
static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd); 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 MsgReadBitsCompressCheckSV(const char *from, char *to, int size);
static int MsgReadBitsCompressCheckCL(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 void JavelinResetHookStub();
static bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size); static bool InvalidNameCheck(char *dest, char *source, int size);
static void QuickPatch::InvalidNameStub(); 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: public:
RawFiles(); 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<Script::Function> Script::ScriptFunctions;
std::vector<std::string> Script::ScriptNameStack; std::vector<std::string> Script::ScriptNameStack;
unsigned short Script::FunctionName; 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; Utils::Signal<Scheduler::Callback> Script::VMShutdownSignal;
@ -247,11 +250,6 @@ namespace Components
Script::ScriptFunctions.push_back({ name, function, isDev }); 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) Game::scr_function_t Script::GetFunction(void* caller, const char** name, int* isDev)
{ {
for (auto& function : Script::ScriptFunctions) 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) void Script::ScrShutdownSystemStub(int num)
{ {
Script::VMShutdownSignal(); Script::VMShutdownSignal();
@ -316,11 +399,158 @@ namespace Components
return Game::Scr_GetNumParam(); 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() Script::Script()
{ {
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, Script::StoreScriptNameStub, 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(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, 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(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick();
Utils::Hook(0x44E736, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetFunction 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(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick();
Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick();
Utils::Hook(0x4D06BA, 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) Script::AddFunction("debugBox", [](Game::scr_entref_t)
{ {
MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0); MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0);
}, true); }, true);
Script::AddFunctions();
// Script::AddFunction("playviewmodelfx", [](Game::scr_entref_t /*index*/) // Script::AddFunction("playviewmodelfx", [](Game::scr_entref_t /*index*/)
// { // {
// /*auto Scr_Error = Utils::Hook::Call<void(const char*)>(0x42EF40); // /*auto Scr_Error = Utils::Hook::Call<void(const char*)>(0x42EF40);
@ -376,5 +626,8 @@ namespace Components
Script::ScriptNameStack.clear(); Script::ScriptNameStack.clear();
Script::ScriptFunctions.clear(); Script::ScriptFunctions.clear();
Script::VMShutdownSignal.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 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: private:
static std::string ScriptName; static std::string ScriptName;
static std::vector<int> ScriptHandles; static std::vector<int> ScriptHandles;
static std::vector<Function> ScriptFunctions; static std::vector<Function> ScriptFunctions;
static std::vector<std::string> ScriptNameStack; static std::vector<std::string> ScriptNameStack;
static unsigned short FunctionName; 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; static Utils::Signal<Scheduler::Callback> VMShutdownSignal;
@ -57,7 +63,13 @@ namespace Components
static void GetFunctionStub(); static void GetFunctionStub();
static void ScrShutdownSystemStub(int); static void ScrShutdownSystemStub(int);
static void StoreScriptBaseProgramNumStub();
static void StoreScriptBaseProgramNum();
static void Scr_PrintPrevCodePosStub();
static void Scr_PrintPrevCodePos(int);
static int SetExpFogStub(); 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() Utils::InfoString ServerInfo::GetInfo()
{ {
int maxclientCount = *Game::svs_numclients; int maxclientCount = *Game::svs_numclients;

View File

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

View File

@ -63,6 +63,18 @@ namespace Components
Stats::SendStats(); 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() Stats::Stats()
{ {
// This UIScript should be added in the onClose code of the cac_popup menu, // 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 // Don't create stat backup
Utils::Hook::Nop(0x402CE6, 2); 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() Stats::~Stats()

View File

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

View File

@ -371,7 +371,7 @@ namespace Components
UIFeeder::Add(10.0f, Theatre::GetDemoCount, Theatre::GetDemoText, Theatre::SelectDemo); 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 // 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 // Change font size
Utils::Hook::Set<BYTE>(0x5AC854, 2); Utils::Hook::Set<BYTE>(0x5AC854, 2);

View File

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

View File

@ -207,6 +207,9 @@ namespace Components
{ {
Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data()); 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 // Sanitize name for empty assets
if (name[0] == ',') name.erase(name.begin()); if (name[0] == ',') name.erase(name.begin());
@ -220,8 +223,7 @@ namespace Components
Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this, isSubAsset); Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this, isSubAsset);
if (!assetHeader.data) 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; return false;
} }
@ -407,6 +409,8 @@ namespace Components
} }
#endif #endif
Utils::IO::WriteFile("uncompressed", zoneBuffer);
zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer); zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer);
outBuffer.append(zoneBuffer); outBuffer.append(zoneBuffer);
@ -506,7 +510,7 @@ namespace Components
// Add branding asset // Add branding asset
void ZoneBuilder::Zone::addBranding() 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 }; this->branding = { this->zoneName.data(), static_cast<int>(strlen(data)), 0, data };
if (this->findAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, this->branding.name) != -1) if (this->findAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, this->branding.name) != -1)
@ -701,21 +705,20 @@ namespace Components
if (zoneIndex > 0) if (zoneIndex > 0)
{ {
Game::DB_EnumXAssetEntries(type, [&](Game::XAssetEntry* entry) Game::XAssetEntry* entry = Game::DB_FindXAssetEntry(type, name.data());
{
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]());
// Set the name to the original name, so it can be stored if (entry && entry->zoneIndex == zoneIndex)
Game::DB_SetXAssetNameHandlers[type](&header, name.data()); {
AssetHandler::StoreTemporaryAsset(type, header); // Allocate an empty asset (filled with zeros)
header.data = builder->getAllocator()->allocate(Game::DB_GetXAssetSizeHandlers[type]());
// Set the name to the empty name // Set the name to the original name, so it can be stored
Game::DB_SetXAssetNameHandlers[type](&header, builder->getAllocator()->duplicateString("," + name)); Game::DB_SetXAssetNameHandlers[type](&header, name.data());
} AssetHandler::StoreTemporaryAsset(type, header);
}, true, true);
// 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 // defaults need to load before we do this
Utils::Hook::Call<void()>(0x4E1F30)(); // G_SetupWeaponDef 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(const char*)>(0x464A90)(GetCommandLineA()); // Com_ParseCommandLine
Utils::Hook::Call<void()>(0x60C3D0)(); // Com_AddStartupCommands Utils::Hook::Call<void()>(0x60C3D0)(); // Com_AddStartupCommands
@ -1102,12 +1109,29 @@ namespace Components
if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current())
{ {
ZoneBuilder::TraceAssets.push_back({ type, name }); ZoneBuilder::TraceAssets.push_back({ type, name });
OutputDebugStringA((name + "\n").data());
} }
}); });
Command::Add("verifyzone", [](Command::Params* params) Command::Add("verifyzone", [](Command::Params* params)
{ {
if (params->length() < 2) return; 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); std::string zone = params->get(1);
@ -1282,7 +1306,7 @@ namespace Components
// HACK: set language to 'techsets' to load from that dir // HACK: set language to 'techsets' to load from that dir
char* language = Utils::Hook::Get<char*>(0x649E740); char* language = Utils::Hook::Get<char*>(0x649E740);
Utils::Hook::Set<char*>(0x649E740, "techsets"); Utils::Hook::Set<const char*>(0x649E740, "techsets");
// load generated techset fastfiles // load generated techset fastfiles
auto list = Utils::IO::ListFiles("zone/techsets"); auto list = Utils::IO::ListFiles("zone/techsets");
@ -1342,7 +1366,7 @@ namespace Components
info.freeFlags = Game::DB_ZONE_MOD; info.freeFlags = Game::DB_ZONE_MOD;
Game::DB_LoadXAssets(&info, 1, true); Game::DB_LoadXAssets(&info, 1, true);
Utils::Hook::Set<char*>(0x649E740, "techsets"); Utils::Hook::Set<const char*>(0x649E740, "techsets");
i = 0; i = 0;
subCount++; subCount++;

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,13 @@ namespace Components
class Zones : public Component class Zones : public Component
{ {
public: public:
struct FileData
{
std::uint32_t readPos;
std::uint32_t len;
std::string fileContents;
};
Zones(); Zones();
~Zones(); ~Zones();
@ -17,6 +24,7 @@ namespace Components
static int Version() { return Zones::ZoneVersion; }; static int Version() { return Zones::ZoneVersion; };
private: private:
static int ZoneVersion; static int ZoneVersion;
static int FxEffectIndex; static int FxEffectIndex;
@ -61,6 +69,36 @@ namespace Components
static void LoadImpactFxArray(); static void LoadImpactFxArray();
static int ImpactFxArrayCount(); static int ImpactFxArrayCount();
static void LoadPathDataConstant(); 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 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 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); AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360);
AllocObject_t AllocObject = AllocObject_t(0x434320); AllocObject_t AllocObject = AllocObject_t(0x434320);
@ -116,6 +136,7 @@ namespace Game
FS_BuildPathToFile_t FS_BuildPathToFile = FS_BuildPathToFile_t(0x4702C0); FS_BuildPathToFile_t FS_BuildPathToFile = FS_BuildPathToFile_t(0x4702C0);
FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440); 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); G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840);
GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0); 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); 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_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_OpenByName_t Menus_OpenByName = Menus_OpenByName_t(0x4CCE60);
Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240); Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240);
Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0); 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_AdrToString_t NET_AdrToString = NET_AdrToString_t(0x469880);
NET_CompareAdr_t NET_CompareAdr = NET_CompareAdr_t(0x4D0AA0); 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_ErrorString_t NET_ErrorString = NET_ErrorString_t(0x4E7720);
NET_Init_t NET_Init = NET_Init_t(0x491860); NET_Init_t NET_Init = NET_Init_t(0x491860);
NET_IsLocalAddress_t NET_IsLocalAddress = NET_IsLocalAddress_t(0x402BD0); 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_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40);
Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750); Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750);
Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30); 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); 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_DirectConnect_t SV_DirectConnect = SV_DirectConnect_t(0x460480);
SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0); SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0);
SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0); 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_Error_t Sys_Error = Sys_Error_t(0x4E0200);
Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580); Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580);
@ -321,6 +347,7 @@ namespace Game
source_t **sourceFiles = reinterpret_cast<source_t **>(0x7C4A98); source_t **sourceFiles = reinterpret_cast<source_t **>(0x7C4A98);
keywordHash_t **menuParseKeywordHash = reinterpret_cast<keywordHash_t **>(0x63AE928); keywordHash_t **menuParseKeywordHash = reinterpret_cast<keywordHash_t **>(0x63AE928);
int* svs_time = reinterpret_cast<int*>(0x31D9384);
int* svs_numclients = reinterpret_cast<int*>(0x31D938C); int* svs_numclients = reinterpret_cast<int*>(0x31D938C);
client_t* svs_clients = reinterpret_cast<client_t*>(0x31D9390); client_t* svs_clients = reinterpret_cast<client_t*>(0x31D9390);
@ -390,6 +417,8 @@ namespace Game
ScriptContainer* scriptContainer = reinterpret_cast<ScriptContainer*>(0x2040D00); ScriptContainer* scriptContainer = reinterpret_cast<ScriptContainer*>(0x2040D00);
clientstate_t* clcState = reinterpret_cast<clientstate_t*>(0xB2C540);
XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize)
{ {
int elSize = DB_GetXAssetSizeHandlers[type](); int elSize = DB_GetXAssetSizeHandlers[type]();
@ -662,6 +691,43 @@ namespace Game
return atoi(StringTable_Lookup(rankTable, 0, maxrank, 7)); 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) void SortWorldSurfaces(GfxWorld* world)
{ {
DWORD* specular1 = reinterpret_cast<DWORD*>(0x69F105C); DWORD* specular1 = reinterpret_cast<DWORD*>(0x69F105C);

View File

@ -2,6 +2,26 @@
namespace Game 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); typedef void(__cdecl * AddRefToObject_t)(unsigned int id);
extern AddRefToObject_t AddRefToObject; extern AddRefToObject_t AddRefToObject;
@ -297,6 +317,9 @@ namespace Game
typedef iwd_t*(__cdecl * FS_IsShippedIWD_t)(const char* fullpath, const char* iwd); typedef iwd_t*(__cdecl * FS_IsShippedIWD_t)(const char* fullpath, const char* iwd);
extern FS_IsShippedIWD_t FS_IsShippedIWD; 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)(); typedef void(__cdecl* G_SpawnEntitiesFromString_t)();
extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString;
@ -319,7 +342,7 @@ namespace Game
typedef void(__cdecl * LargeLocalInit_t)(); typedef void(__cdecl * LargeLocalInit_t)();
extern LargeLocalInit_t LargeLocalInit; 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; extern Load_Stream_t Load_Stream;
typedef void(__cdecl * Load_XString_t)(bool atStreamStart); typedef void(__cdecl * Load_XString_t)(bool atStreamStart);
@ -373,6 +396,9 @@ namespace Game
typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc); typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc);
extern Menus_CloseAll_t Menus_CloseAll; 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); typedef int(__cdecl * Menus_OpenByName_t)(UiContext *dc, const char *p);
extern Menus_OpenByName_t Menus_OpenByName; extern Menus_OpenByName_t Menus_OpenByName;
@ -439,6 +465,9 @@ namespace Game
typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b); typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b);
extern NET_CompareAdr_t NET_CompareAdr; 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)(); typedef const char* (__cdecl * NET_ErrorString_t)();
extern NET_ErrorString_t NET_ErrorString; extern NET_ErrorString_t NET_ErrorString;
@ -592,6 +621,12 @@ namespace Game
typedef bool(__cdecl * Scr_IsSystemActive_t)(); typedef bool(__cdecl * Scr_IsSystemActive_t)();
extern Scr_IsSystemActive_t Scr_IsSystemActive; 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); typedef script_t* (__cdecl * Script_Alloc_t)(int length);
extern Script_Alloc_t Script_Alloc; extern Script_Alloc_t Script_Alloc;
@ -655,6 +690,9 @@ namespace Game
typedef bool(__cdecl * SV_Loaded_t)(); typedef bool(__cdecl * SV_Loaded_t)();
extern SV_Loaded_t SV_Loaded; 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 *, ...); typedef int(__cdecl * Sys_Error_t)(int, char *, ...);
extern Sys_Error_t Sys_Error; extern Sys_Error_t Sys_Error;
@ -746,6 +784,7 @@ namespace Game
extern cmd_function_t** cmd_functions; extern cmd_function_t** cmd_functions;
extern int* svs_time;
extern int* svs_numclients; extern int* svs_numclients;
extern client_t* svs_clients; extern client_t* svs_clients;
@ -817,6 +856,8 @@ namespace Game
extern ScriptContainer* scriptContainer; extern ScriptContainer* scriptContainer;
extern clientstate_t* clcState;
XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize);
void Menu_FreeItemMemory(Game::itemDef_s* item); void Menu_FreeItemMemory(Game::itemDef_s* item);
const char* TableLookup(StringTable* stringtable, int row, int column); 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 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 SortWorldSurfaces(GfxWorld* world);
void R_AddDebugLine(float* color, float* v1, float* v2); void R_AddDebugLine(float* color, float* v1, float* v2);
void R_AddDebugString(float *color, float *pos, float scale, const char *str); 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_COUNT = 0xA,
} dvar_type; } 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 FxEffectDef;
struct pathnode_t; struct pathnode_t;
struct pathnode_tree_t; struct pathnode_tree_t;
@ -4469,9 +4479,17 @@ namespace Game
typedef struct client_s typedef struct client_s
{ {
// 0 // 0
int state; clientstate_t state;
// 4 // 4
char pad[36]; char _pad[4];
// 8
int deltaMessage;
// 12
char __pad[12];
// 24
int outgoingSequence;
// 28
char pad[12];
// 40 // 40
netadr_t addr; netadr_t addr;
// 60 // 60

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