commit
3cdc6a914b
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
- Release
|
||||
steps:
|
||||
- name: Check out files
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
@ -32,7 +32,7 @@ jobs:
|
||||
lfs: false
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.1.3
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
|
||||
- name: Generate project files
|
||||
run: tools/premake5 vs2022
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=Win32 build/iw4x.sln
|
||||
|
||||
- name: Upload ${{matrix.configuration}} binaries
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: ${{matrix.configuration}} binaries
|
||||
path: |
|
||||
@ -66,13 +66,13 @@ jobs:
|
||||
run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Release binaries
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v3.0.2
|
||||
with:
|
||||
name: Release binaries
|
||||
|
||||
# Set up committer info and GPG key
|
||||
- name: Install SSH key
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
uses: shimataro/ssh-key-action@v2.5.0
|
||||
with:
|
||||
key: ${{ secrets.XLABS_MASTER_SSH_PRIVATE_KEY }}
|
||||
known_hosts: 'just-a-placeholder-so-we-dont-get-errors'
|
||||
|
83
.gitmodules
vendored
83
.gitmodules
vendored
@ -1,37 +1,46 @@
|
||||
[submodule "deps/zlib"]
|
||||
path = deps/zlib
|
||||
url = https://github.com/madler/zlib.git
|
||||
branch = develop
|
||||
[submodule "deps/libtommath"]
|
||||
path = deps/libtommath
|
||||
url = https://github.com/libtom/libtommath.git
|
||||
branch = develop
|
||||
[submodule "deps/libtomcrypt"]
|
||||
path = deps/libtomcrypt
|
||||
url = https://github.com/libtom/libtomcrypt.git
|
||||
branch = develop
|
||||
[submodule "deps/pdcurses"]
|
||||
path = deps/pdcurses
|
||||
url = https://github.com/wmcbrine/PDCurses.git
|
||||
branch = master
|
||||
[submodule "deps/mongoose"]
|
||||
path = deps/mongoose
|
||||
url = https://github.com/cesanta/mongoose.git
|
||||
branch = master
|
||||
[submodule "deps/protobuf"]
|
||||
path = deps/protobuf
|
||||
url = https://github.com/google/protobuf.git
|
||||
branch = 3.20.x
|
||||
[submodule "deps/udis86"]
|
||||
path = deps/udis86
|
||||
url = https://github.com/vmt/udis86.git
|
||||
[submodule "deps/dxsdk"]
|
||||
path = deps/dxsdk
|
||||
url = https://github.com/devKlausS/dxsdk.git
|
||||
[submodule "deps/GSL"]
|
||||
path = deps/GSL
|
||||
url = https://github.com/microsoft/GSL.git
|
||||
[submodule "deps/nlohmannjson"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
branch = v3.11.2
|
||||
[submodule "deps/zlib"]
|
||||
path = deps/zlib
|
||||
url = https://github.com/madler/zlib.git
|
||||
branch = develop
|
||||
[submodule "deps/libtommath"]
|
||||
path = deps/libtommath
|
||||
url = https://github.com/libtom/libtommath.git
|
||||
branch = develop
|
||||
[submodule "deps/libtomcrypt"]
|
||||
path = deps/libtomcrypt
|
||||
url = https://github.com/libtom/libtomcrypt.git
|
||||
branch = develop
|
||||
[submodule "deps/pdcurses"]
|
||||
path = deps/pdcurses
|
||||
url = https://github.com/wmcbrine/PDCurses.git
|
||||
branch = master
|
||||
[submodule "deps/mongoose"]
|
||||
path = deps/mongoose
|
||||
url = https://github.com/cesanta/mongoose.git
|
||||
branch = 7.9
|
||||
[submodule "deps/protobuf"]
|
||||
path = deps/protobuf
|
||||
url = https://github.com/google/protobuf.git
|
||||
branch = 3.20.x
|
||||
[submodule "deps/udis86"]
|
||||
path = deps/udis86
|
||||
url = https://github.com/vmt/udis86.git
|
||||
[submodule "deps/dxsdk"]
|
||||
path = deps/dxsdk
|
||||
url = https://github.com/devKlausS/dxsdk.git
|
||||
[submodule "deps/GSL"]
|
||||
path = deps/GSL
|
||||
url = https://github.com/microsoft/GSL.git
|
||||
[submodule "deps/nlohmannjson"]
|
||||
path = deps/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
branch = v3.11.2
|
||||
[submodule "deps/discord-rpc"]
|
||||
path = deps/discord-rpc
|
||||
url = https://github.com/discord/discord-rpc.git
|
||||
[submodule "deps/rapidjson"]
|
||||
path = deps/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson.git
|
||||
[submodule "deps/iw4-open-formats"]
|
||||
path = deps/iw4-open-formats
|
||||
url = https://github.com/XLabsProject/iw4-open-formats.git
|
||||
|
43
CHANGELOG.md
43
CHANGELOG.md
@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.7.8] - 2023-03-17
|
||||
|
||||
### Added
|
||||
- Clients can unprotect "saved" Dvars using the command line argument `-unprotect-dvars` (#694)
|
||||
- Clients can protect all Dvars from being modified by the server using the command line argument `-protect-dvars` (#823)
|
||||
- Add helpful information to script-related errors (#721)
|
||||
- Add command line `-disable-mongoose` to disable IW4x's built-in webserver (#728)
|
||||
- Add command line `-disable-rate-limit-check` to disable the rate check on RCon requests (#769)
|
||||
- Muted clients GUID is saved to the `muted-users.json` file in the `userraw` folder (#732)
|
||||
- Add `sv_nextMap` Dvar (#736)
|
||||
- Add `elifdef` and `elifndef` directives to the menu preprocessor (#747)
|
||||
- Add `r_drawRunners` Dvar (#758)
|
||||
- Add `r_drawLights` Dvar (#814)
|
||||
- Add Discord Rich Presence (#761)
|
||||
- Add `rcon_timeout` Dvar (#769)
|
||||
- Add `sv_clanName` Dvar (#771)
|
||||
- Add `InitialWeaponRaise` GSC method (#804)
|
||||
- Add `RconWhitelistAdd` SV Command (#804)
|
||||
- Add more options to `bg_bouncesAllAngles` Dvar (#806)
|
||||
- Add `GetChar` GSC function (#813)
|
||||
- Add `bg_bunnyHopAuto` Dvar (#818)
|
||||
- Add `bg_disableLandingSlowdown` Dvar (#818)
|
||||
- Add new map porting utility tool that makes the map porting process between CoD8 to CoD6 easy
|
||||
|
||||
### Changed
|
||||
- Servers can no longer modify client Dvars that are saved in the client's config (#694)
|
||||
- `banClient` and `muteClient` server commands do not apply to bots anymore (#730)
|
||||
- Remove `zb_prefer_disk_assets` Dvar (#772)
|
||||
- The max value of `perk_extendedMeleeRange`Dvar was increased (#782)
|
||||
|
||||
### Fixed
|
||||
- Fix bug where`reloadmenus` command would not free resources used by custom menus (#740)
|
||||
- Fix bug where demo playback would stop when opening a laptop based killstreak (#699)
|
||||
- Fix bug where mod download speed was inexplicably slow (#707)
|
||||
- Fix bug where `g_hardcore` could not be set by servers (#708)
|
||||
- Fix bots "user cmd angles" (#707)
|
||||
- Fix bug where `FileRead` GSC function could return buffers that are too big (#767)
|
||||
- Fix bug where the `OnPlayerSay` GSC function would cause issues (#776)
|
||||
|
||||
### Known issues
|
||||
|
||||
- Sound issue fix is experimental as the bug is not fully understood.
|
||||
|
||||
## [0.7.7] - 2022-12-31
|
||||
|
||||
### Added
|
||||
|
@ -39,7 +39,7 @@
|
||||
| `-version` | Print IW4x build info on startup. |
|
||||
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
|
||||
| `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. |
|
||||
| `-protect-saved-dvars` | Block the server from modifying saved/archive dvars. |
|
||||
| `-unprotect-dvars` | Allow the server to modify saved/archive dvars. |
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
2
deps/GSL
vendored
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit f94c1f6f2b5e141d5f6eb3d284cd4a8cf9a81aac
|
||||
Subproject commit 43d60c5e3891dab6491a76d0bac554a4a89d57f6
|
1
deps/discord-rpc
vendored
Submodule
1
deps/discord-rpc
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 963aa9f3e5ce81a4682c6ca3d136cddda614db33
|
1
deps/iw4-open-formats
vendored
Submodule
1
deps/iw4-open-formats
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit cf45b460fe32a8c858b30445df779e29821cfdee
|
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4
|
||||
Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447
|
2
deps/mongoose
vendored
2
deps/mongoose
vendored
@ -1 +1 @@
|
||||
Subproject commit 73813a838386f6ebca447eb54c626803163ee257
|
||||
Subproject commit 4236405b90e051310aadda818e21c811e404b4d8
|
40
deps/premake/discord-rpc.lua
vendored
Normal file
40
deps/premake/discord-rpc.lua
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
discordrpc = {
|
||||
source = path.join(dependencies.basePath, "discord-rpc"),
|
||||
}
|
||||
|
||||
function discordrpc.import()
|
||||
links "discord-rpc"
|
||||
discordrpc.includes()
|
||||
end
|
||||
|
||||
function discordrpc.includes()
|
||||
includedirs {
|
||||
path.join(discordrpc.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
function discordrpc.project()
|
||||
project "discord-rpc"
|
||||
language "C++"
|
||||
cppdialect "C++17"
|
||||
|
||||
discordrpc.includes()
|
||||
rapidjson.import();
|
||||
|
||||
files {
|
||||
path.join(discordrpc.source, "src/*.h"),
|
||||
path.join(discordrpc.source, "src/*.cpp"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(discordrpc.source, "src/dllmain.cpp"),
|
||||
path.join(discordrpc.source, "src/*_linux.cpp"),
|
||||
path.join(discordrpc.source, "src/*_unix.cpp"),
|
||||
path.join(discordrpc.source, "src/*_osx.cpp"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, discordrpc)
|
8
deps/premake/fonts.lua
vendored
8
deps/premake/fonts.lua
vendored
@ -1,4 +1,6 @@
|
||||
fonts = {}
|
||||
fonts = {
|
||||
source = path.join(dependencies.basePath, "extra/font"),
|
||||
}
|
||||
|
||||
function fonts.import()
|
||||
fonts.includes()
|
||||
@ -6,7 +8,7 @@ end
|
||||
|
||||
function fonts.includes()
|
||||
includedirs {
|
||||
path.join(dependencies.basePath, "extra/font"),
|
||||
fonts.source,
|
||||
}
|
||||
end
|
||||
|
||||
@ -17,7 +19,7 @@ function fonts.project()
|
||||
fonts.includes()
|
||||
|
||||
files {
|
||||
path.join(dependencies.basePath, "extra/font/*.hpp"),
|
||||
path.join(fonts.source, "Terminus_4.49.1.ttf.hpp"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
|
2
deps/premake/gsl.lua
vendored
2
deps/premake/gsl.lua
vendored
@ -8,7 +8,7 @@ end
|
||||
|
||||
function gsl.includes()
|
||||
includedirs {
|
||||
path.join(gsl.source, "include")
|
||||
path.join(gsl.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
|
44
deps/premake/iw4-open-formats.lua
vendored
Normal file
44
deps/premake/iw4-open-formats.lua
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
iw4_open_formats = {
|
||||
source = path.join(dependencies.basePath, "iw4-open-formats"),
|
||||
}
|
||||
|
||||
function iw4_open_formats.import()
|
||||
links "iw4-open-formats"
|
||||
|
||||
iw4_open_formats.includes()
|
||||
end
|
||||
|
||||
function iw4_open_formats.includes()
|
||||
includedirs {
|
||||
path.join(iw4_open_formats.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function iw4_open_formats.project()
|
||||
project "iw4-open-formats"
|
||||
language "C++"
|
||||
|
||||
iw4_open_formats.includes()
|
||||
|
||||
pchheader "std_include.hpp"
|
||||
pchsource (path.join(iw4_open_formats.source, "src/iw4-of/std_include.cpp"))
|
||||
|
||||
files {
|
||||
path.join(iw4_open_formats.source, "src/iw4-of/**.hpp"),
|
||||
path.join(iw4_open_formats.source, "src/iw4-of/**.cpp"),
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(iw4_open_formats.source, "src/iw4-of"),
|
||||
path.join(iw4_open_formats.source, "include"),
|
||||
}
|
||||
|
||||
libtomcrypt.includes()
|
||||
libtommath.includes()
|
||||
rapidjson.includes()
|
||||
zlib.includes()
|
||||
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, iw4_open_formats)
|
6
deps/premake/mongoose.lua
vendored
6
deps/premake/mongoose.lua
vendored
@ -3,13 +3,15 @@ mongoose = {
|
||||
}
|
||||
|
||||
function mongoose.import()
|
||||
links {"mongoose"}
|
||||
links "mongoose"
|
||||
|
||||
mongoose.includes()
|
||||
end
|
||||
|
||||
function mongoose.includes()
|
||||
includedirs {mongoose.source}
|
||||
includedirs {
|
||||
mongoose.source,
|
||||
}
|
||||
end
|
||||
|
||||
function mongoose.project()
|
||||
|
4
deps/premake/protobuf.lua
vendored
4
deps/premake/protobuf.lua
vendored
@ -38,10 +38,6 @@ function protobuf.project()
|
||||
}
|
||||
|
||||
rules {"ProtobufCompiler"}
|
||||
|
||||
defines {"_SCL_SECURE_NO_WARNINGS"}
|
||||
|
||||
linkoptions {"-IGNORE:4221"}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
|
19
deps/premake/rapidjson.lua
vendored
Normal file
19
deps/premake/rapidjson.lua
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
rapidjson = {
|
||||
source = path.join(dependencies.basePath, "rapidjson"),
|
||||
}
|
||||
|
||||
function rapidjson.import()
|
||||
rapidjson.includes()
|
||||
end
|
||||
|
||||
function rapidjson.includes()
|
||||
includedirs {
|
||||
path.join(rapidjson.source, "include"),
|
||||
}
|
||||
end
|
||||
|
||||
function rapidjson.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, rapidjson)
|
2
deps/protobuf
vendored
2
deps/protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375
|
||||
Subproject commit 53a1c5c1d8b61984899b6877e491e5117ad486ba
|
1
deps/rapidjson
vendored
Submodule
1
deps/rapidjson
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
||||
Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c
|
||||
Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485
|
@ -5008,4 +5008,4 @@ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
*/
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include "Modules/Bans.hpp"
|
||||
#include "Modules/Bots.hpp"
|
||||
@ -14,15 +15,18 @@
|
||||
#include "Modules/Console.hpp"
|
||||
#include "Modules/D3D9Ex.hpp"
|
||||
#include "Modules/Debug.hpp"
|
||||
#include "Modules/Discord.hpp"
|
||||
#include "Modules/Discovery.hpp"
|
||||
#include "Modules/Download.hpp"
|
||||
#include "Modules/Elevators.hpp"
|
||||
#include "Modules/FastFiles.hpp"
|
||||
#include "Modules/Gamepad.hpp"
|
||||
#include "Modules/Lean.hpp"
|
||||
#include "Modules/MapDump.hpp"
|
||||
#include "Modules/MapRotation.hpp"
|
||||
#include "Modules/NetworkDebug.hpp"
|
||||
#include "Modules/News.hpp"
|
||||
#include "Modules/Party.hpp"
|
||||
#include "Modules/PlayerMovement.hpp"
|
||||
#include "Modules/PlayerName.hpp"
|
||||
#include "Modules/Playlist.hpp"
|
||||
@ -44,12 +48,13 @@
|
||||
#include "Modules/Theatre.hpp"
|
||||
#include "Modules/Threading.hpp"
|
||||
#include "Modules/UIFeeder.hpp"
|
||||
#include "Modules/UserInfo.hpp"
|
||||
#include "Modules/VisionFile.hpp"
|
||||
#include "Modules/Voice.hpp"
|
||||
#include "Modules/Vote.hpp"
|
||||
#include "Modules/Weapon.hpp"
|
||||
|
||||
#include "Modules/BotLib/lPrecomp.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
bool Loader::Pregame = true;
|
||||
@ -79,14 +84,16 @@ namespace Components
|
||||
Uninitializing = false;
|
||||
Utils::Memory::GetAllocator()->clear();
|
||||
|
||||
// High priority
|
||||
Register(new Singleton());
|
||||
|
||||
Register(new Auth());
|
||||
Register(new Command());
|
||||
Register(new Dvar());
|
||||
Register(new Exception()); // Install our exception handler as early as posssible to get better debug dumps from startup crashes
|
||||
Register(new Flags());
|
||||
Register(new Exception()); // Install our exception handler as early as possible to get better debug dumps from startup crashes
|
||||
Register(new IPCPipe());
|
||||
Register(new Network());
|
||||
Register(new Logger());
|
||||
Register(new Singleton());
|
||||
Register(new UIScript());
|
||||
Register(new ZoneBuilder());
|
||||
|
||||
@ -107,6 +114,7 @@ namespace Components
|
||||
Register(new D3D9Ex());
|
||||
Register(new Debug());
|
||||
Register(new Dedicated());
|
||||
Register(new Discord());
|
||||
Register(new Discovery());
|
||||
Register(new Download());
|
||||
Register(new Elevators());
|
||||
@ -115,7 +123,6 @@ namespace Components
|
||||
Register(new FileSystem());
|
||||
Register(new Friends());
|
||||
Register(new Gamepad());
|
||||
Register(new IPCPipe());
|
||||
Register(new Lean());
|
||||
Register(new Localization());
|
||||
Register(new MapDump());
|
||||
@ -154,7 +161,6 @@ namespace Components
|
||||
Register(new Threading());
|
||||
Register(new Toast());
|
||||
Register(new UIFeeder());
|
||||
Register(new UserInfo());
|
||||
Register(new VisionFile());
|
||||
Register(new Voice());
|
||||
Register(new Vote());
|
||||
@ -162,7 +168,9 @@ namespace Components
|
||||
Register(new Window());
|
||||
Register(new Zones());
|
||||
|
||||
Register(new GSC());
|
||||
Register(new GSC::GSC());
|
||||
|
||||
Register(new BotLib::lPrecomp());
|
||||
|
||||
Pregame = false;
|
||||
|
||||
|
@ -77,7 +77,6 @@ namespace Components
|
||||
#include "Modules/AssetHandler.hpp"
|
||||
#include "Modules/Dedicated.hpp"
|
||||
#include "Modules/Events.hpp"
|
||||
#include "Modules/FastFiles.hpp"
|
||||
#include "Modules/FileSystem.hpp"
|
||||
#include "Modules/Friends.hpp"
|
||||
#include "Modules/IPCPipe.hpp"
|
||||
@ -88,7 +87,6 @@ namespace Components
|
||||
#include "Modules/ModList.hpp"
|
||||
#include "Modules/ModelSurfs.hpp"
|
||||
#include "Modules/Node.hpp"
|
||||
#include "Modules/Party.hpp"
|
||||
#include "Modules/Renderer.hpp"
|
||||
#include "Modules/Scheduler.hpp"
|
||||
#include "Modules/TextRenderer.hpp"
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Game::newMapArena_t ArenaLength::NewArenas[128];
|
||||
constexpr auto NEW_ARENA_COUNT = 128;
|
||||
|
||||
Game::newMapArena_t ArenaLength::NewArenas[NEW_ARENA_COUNT];
|
||||
char* ArenaLength::NewArenaInfos[NEW_ARENA_COUNT];
|
||||
|
||||
__declspec(naked) void ArenaLength::ArenaMapOffsetHook1()
|
||||
{
|
||||
@ -57,16 +60,28 @@ namespace Components
|
||||
|
||||
ArenaLength::ArenaLength()
|
||||
{
|
||||
// Reallocate array
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x417807, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x420717, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x49BD22, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x4A9649, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x4A97C2, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x4D077E, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x630B00, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x630B2E, &ArenaLength::NewArenas[0]);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x632782, &ArenaLength::NewArenas[0]);
|
||||
// Reallocate ui_arenaInfos
|
||||
Utils::Hook::Set<char**>(0x4A95F0 + 3, ArenaLength::NewArenaInfos);
|
||||
Utils::Hook::Set<char**>(0x4A9620 + 3, ArenaLength::NewArenaInfos);
|
||||
Utils::Hook::Set<char**>(0x4A9653 + 3, ArenaLength::NewArenaInfos);
|
||||
Utils::Hook::Set<char**>(0x4A9684 + 3, ArenaLength::NewArenaInfos);
|
||||
Utils::Hook::Set<char**>(0x4A96B7 + 3, ArenaLength::NewArenaInfos);
|
||||
Utils::Hook::Set<char**>(0x4A97B3 + 3, ArenaLength::NewArenaInfos);
|
||||
Utils::Hook::Set<char**>(0x630A9A + 3, ArenaLength::NewArenaInfos);
|
||||
|
||||
// Increase size - patch max arena count
|
||||
Utils::Hook::Set<unsigned int>(0x630AA2 + 1, NEW_ARENA_COUNT);
|
||||
|
||||
// Reallocate sharedUiInfo.mapList
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x417807, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x420717, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x49BD22, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x4A9649, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x4A97C2, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x4D077E, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x630B00, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x630B2E, ArenaLength::NewArenas);
|
||||
Utils::Hook::Set<Game::newMapArena_t*>(0x632782, ArenaLength::NewArenas);
|
||||
|
||||
Utils::Hook::Set<char*>(0x4A967A, ArenaLength::NewArenas[0].description);
|
||||
Utils::Hook::Set<char*>(0x4A96AD, ArenaLength::NewArenas[0].mapimage);
|
||||
@ -109,12 +124,7 @@ namespace Components
|
||||
Utils::Hook(0x632799, ArenaLength::ArenaMapOffsetHook3, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4064B2, ArenaLength::ArenaMapOffsetHook4, HOOK_JUMP).install()->quick();
|
||||
|
||||
Utils::Hook::Set<BYTE>(0x4A95F8, 32);
|
||||
|
||||
Utils::Hook::Set<BYTE>(0x4A95F8, sizeof(Game::newMapArena_t::mapName));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ namespace Components
|
||||
public:
|
||||
ArenaLength();
|
||||
|
||||
static Game::newMapArena_t NewArenas[128];
|
||||
static Game::newMapArena_t NewArenas[];
|
||||
static char* NewArenaInfos[];
|
||||
|
||||
private:
|
||||
static void ArenaMapOffsetHook1();
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "FastFiles.hpp"
|
||||
#include "Weapon.hpp"
|
||||
|
||||
#include "AssetInterfaces/IFont_s.hpp"
|
||||
@ -570,210 +571,6 @@ namespace Components
|
||||
|
||||
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool*)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// #define DUMP_TECHSETS
|
||||
#ifdef DUMP_TECHSETS
|
||||
if (type == Game::XAssetType::ASSET_TYPE_VERTEXDECL && !name.empty() && name[0] != ',')
|
||||
{
|
||||
std::filesystem::create_directories("techsets/vertexdecl");
|
||||
|
||||
auto vertexdecl = asset.vertexDecl;
|
||||
|
||||
std::vector<json11::Json> routingData;
|
||||
for (int i = 0; i < vertexdecl->streamCount; i++)
|
||||
{
|
||||
routingData.push_back(json11::Json::object
|
||||
{
|
||||
{ "source", (int)vertexdecl->routing.data[i].source },
|
||||
{ "dest", (int)vertexdecl->routing.data[i].dest },
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<json11::Json> declData;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (vertexdecl->routing.decl[i])
|
||||
{
|
||||
routingData.push_back(int(vertexdecl->routing.decl[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
routingData.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::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]);
|
||||
}
|
||||
|
||||
nlohmann::json argData = json11::Json::object
|
||||
{
|
||||
{ "type", curArg->type },
|
||||
{ "value", literalConsts },
|
||||
};
|
||||
argDataArray.push_back(argData);
|
||||
}
|
||||
else if (curArg->type == 3 || curArg->type == 5)
|
||||
{
|
||||
nlohmann::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
|
||||
{
|
||||
nlohmann::json argData = json11::Json::object
|
||||
{
|
||||
{ "type", curArg->type },
|
||||
{ "value", static_cast<int>(curArg->u.codeSampler) },
|
||||
};
|
||||
argDataArray.push_back(argData);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::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);
|
||||
}
|
||||
|
||||
nlohmann::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);
|
||||
|
||||
nlohmann::json techsetTechnique = json11::Json::object
|
||||
{
|
||||
{ "name", curTech->name },
|
||||
{ "index", technique },
|
||||
};
|
||||
techniques.push_back(techsetTechnique);
|
||||
}
|
||||
else
|
||||
{
|
||||
techniques.push_back(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json techsetData = json11::Json::object
|
||||
{
|
||||
{ "name", techset->name },
|
||||
{ "techniques", techniques },
|
||||
};
|
||||
|
||||
auto stringData = techsetData.dump();
|
||||
|
||||
auto fp = fopen(Utils::String::VA("techsets/%s.%s.json", techset->name, Zones::Version() > 276 ? "codo" : "iw4"), "wb");
|
||||
fwrite(&stringData[0], stringData.size(), 1, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (type == Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET && Zones::Version() >= 460)
|
||||
{
|
||||
auto techset = asset.techniqueSet;
|
||||
if (techset)
|
||||
{
|
||||
for (int t = 0; t < 48; t++)
|
||||
{
|
||||
if (techset->techniques[t])
|
||||
{
|
||||
for (int p = 0; p < techset->techniques[t]->passCount; p++)
|
||||
{
|
||||
for (int a = 0; a < techset->techniques[t]->passArray[p].perObjArgCount +
|
||||
techset->techniques[t]->passArray[p].perPrimArgCount +
|
||||
techset->techniques[t]->passArray[p].stableArgCount; a++)
|
||||
{
|
||||
auto arg = &techset->techniques[t]->passArray[p].args[a];
|
||||
if (arg->type == 3 || arg->type == 5)
|
||||
{
|
||||
if (arg->u.codeConst.index > 140)
|
||||
{
|
||||
OutputDebugStringA(Utils::String::VA("codeConst %i is out of range for %s::%s[%i]\n", arg->u.codeConst.index,
|
||||
techset->name, techset->techniques[t]->name, p));
|
||||
|
||||
if (!ZoneBuilder::IsEnabled())
|
||||
{
|
||||
__debugbreak();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (Dvar::Var("r_noVoid").get<bool>() && type == Game::XAssetType::ASSET_TYPE_XMODEL && name == "void")
|
||||
{
|
||||
asset.model->numLods = 0;
|
||||
|
@ -7,52 +7,7 @@ namespace Assets
|
||||
{
|
||||
void IComWorld::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
std::string name = _name;
|
||||
Utils::String::Replace(name, "maps/mp/", "");
|
||||
Utils::String::Replace(name, ".d3dbsp", "");
|
||||
|
||||
Components::FileSystem::File mapFile(std::format("comworld/{}.iw4xComWorld", name));
|
||||
|
||||
if (mapFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer());
|
||||
|
||||
__int64 magic = reader.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xComW", 8))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading comworld '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
int version = reader.read<int>();
|
||||
if (version != IW4X_COMMAP_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading comworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_COMMAP_VERSION, version);
|
||||
}
|
||||
|
||||
Game::ComWorld* asset = reader.readObject<Game::ComWorld>();
|
||||
header->comWorld = asset;
|
||||
|
||||
if (asset->name)
|
||||
{
|
||||
asset->name = reader.readCString();
|
||||
}
|
||||
|
||||
if (asset->primaryLights)
|
||||
{
|
||||
asset->primaryLights = reader.readArray<Game::ComPrimaryLight>(asset->primaryLightCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
|
||||
{
|
||||
Game::ComPrimaryLight* light = &asset->primaryLights[i];
|
||||
|
||||
if (light->defName)
|
||||
{
|
||||
light->defName = reader.readCString();
|
||||
Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, light->defName, builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
header->comWorld = builder->getIW4OfApi()->read<Game::ComWorld>(Game::XAssetType::ASSET_TYPE_COMWORLD, _name);
|
||||
}
|
||||
|
||||
void IComWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "IFont_s.hpp"
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb_truetype.h>
|
||||
#include <stb_truetype.hpp>
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
@ -262,9 +262,9 @@ namespace Assets
|
||||
AssertSize(Game::Font_s, 24);
|
||||
AssertSize(Game::Glyph, 24);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::Font_s* asset = header.font;
|
||||
Game::Font_s* dest = buffer->dest<Game::Font_s>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.font;
|
||||
auto* dest = buffer->dest<Game::Font_s>();
|
||||
|
||||
buffer->save(asset);
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IFxEffectDef.hpp"
|
||||
|
||||
#define IW4X_FX_VERSION 1
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IFxEffectDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
if (!header->data) this->loadEfx(header, name, builder); // Check if we have an editor fx
|
||||
if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game
|
||||
if (!header->data) this->loadFromIW4OF(header, name, builder); // Check if we need to import a new one into the game
|
||||
if (!header->data /*&& !builder->isPrimaryAsset()*/) this->loadNative(header, name, builder); // Check if there is a native one
|
||||
|
||||
assert(header->data);
|
||||
}
|
||||
|
||||
void IFxEffectDef::loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
|
||||
@ -21,7 +21,7 @@ namespace Assets
|
||||
{
|
||||
if (visuals->model)
|
||||
{
|
||||
visuals->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString().data(), builder).model;
|
||||
visuals->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString(), builder).model;
|
||||
}
|
||||
|
||||
break;
|
||||
@ -44,7 +44,7 @@ namespace Assets
|
||||
{
|
||||
if (visuals->effectDef.handle)
|
||||
{
|
||||
visuals->effectDef.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, reader->readString().data(), builder).fx;
|
||||
visuals->effectDef.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, reader->readString(), builder).fx;
|
||||
}
|
||||
|
||||
break;
|
||||
@ -54,7 +54,7 @@ namespace Assets
|
||||
{
|
||||
if (visuals->material)
|
||||
{
|
||||
visuals->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString().data(), builder).material;
|
||||
visuals->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString(), builder).material;
|
||||
}
|
||||
|
||||
break;
|
||||
@ -62,139 +62,9 @@ namespace Assets
|
||||
}
|
||||
}
|
||||
|
||||
void IFxEffectDef::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
void IFxEffectDef::loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File fxFile(std::format("fx/{}.iw4xFx", name));
|
||||
|
||||
if (fxFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader buffer(builder->getAllocator(), fxFile.getBuffer());
|
||||
|
||||
__int64 magic = buffer.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xFx ", 8))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
int version = buffer.read<int>();
|
||||
if (version != IW4X_FX_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading fx '{}' failed, expected version is {}, but it was {}!", name, IW4X_FX_VERSION, version);
|
||||
}
|
||||
|
||||
Game::FxEffectDef* asset = buffer.readObject<Game::FxEffectDef>();
|
||||
header->fx = asset;
|
||||
|
||||
if (asset->name)
|
||||
{
|
||||
asset->name = buffer.readCString();
|
||||
}
|
||||
|
||||
if (asset->elemDefs)
|
||||
{
|
||||
asset->elemDefs = buffer.readArray<Game::FxElemDef>(asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot);
|
||||
|
||||
for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i)
|
||||
{
|
||||
Game::FxElemDef* elemDef = &asset->elemDefs[i];
|
||||
|
||||
if (elemDef->velSamples)
|
||||
{
|
||||
elemDef->velSamples = buffer.readArray<Game::FxElemVelStateSample>(elemDef->velIntervalCount + 1);
|
||||
}
|
||||
|
||||
if (elemDef->visSamples)
|
||||
{
|
||||
elemDef->visSamples = buffer.readArray<Game::FxElemVisStateSample>(elemDef->visStateIntervalCount + 1);
|
||||
}
|
||||
|
||||
// Save_FxElemDefVisuals
|
||||
{
|
||||
if (elemDef->elemType == Game::FX_ELEM_TYPE_DECAL)
|
||||
{
|
||||
if (elemDef->visuals.markArray)
|
||||
{
|
||||
elemDef->visuals.markArray = buffer.readArray<Game::FxElemMarkVisuals>(elemDef->visualCount);
|
||||
|
||||
for (char j = 0; j < elemDef->visualCount; ++j)
|
||||
{
|
||||
if (elemDef->visuals.markArray[j].materials[0])
|
||||
{
|
||||
elemDef->visuals.markArray[j].materials[0] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString().data(), builder).material;
|
||||
}
|
||||
|
||||
if (elemDef->visuals.markArray[j].materials[1])
|
||||
{
|
||||
elemDef->visuals.markArray[j].materials[1] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, buffer.readString().data(), builder).material;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (elemDef->visualCount > 1)
|
||||
{
|
||||
if (elemDef->visuals.array)
|
||||
{
|
||||
elemDef->visuals.array = buffer.readArray<Game::FxElemVisuals>(elemDef->visualCount);
|
||||
|
||||
for (char j = 0; j < elemDef->visualCount; ++j)
|
||||
{
|
||||
this->loadFxElemVisuals(&elemDef->visuals.array[j], elemDef->elemType, builder, &buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->loadFxElemVisuals(&elemDef->visuals.instance, elemDef->elemType, builder, &buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (elemDef->effectOnImpact.handle)
|
||||
{
|
||||
elemDef->effectOnImpact.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx;
|
||||
}
|
||||
|
||||
if (elemDef->effectOnDeath.handle)
|
||||
{
|
||||
elemDef->effectOnDeath.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx;
|
||||
}
|
||||
|
||||
if (elemDef->effectEmitted.handle)
|
||||
{
|
||||
elemDef->effectEmitted.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, buffer.readString().data(), builder).fx;
|
||||
}
|
||||
|
||||
// Save_FxElemExtendedDefPtr
|
||||
{
|
||||
|
||||
if (elemDef->elemType == Game::FX_ELEM_TYPE_TRAIL)
|
||||
{
|
||||
// Save_FxTrailDef
|
||||
{
|
||||
if (elemDef->extended.trailDef)
|
||||
{
|
||||
Game::FxTrailDef* trailDef = buffer.readObject<Game::FxTrailDef>();
|
||||
elemDef->extended.trailDef = trailDef;
|
||||
|
||||
if (trailDef->verts)
|
||||
{
|
||||
trailDef->verts = buffer.readArray<Game::FxTrailVertex>(trailDef->vertCount);
|
||||
}
|
||||
|
||||
if (trailDef->inds)
|
||||
{
|
||||
trailDef->inds = buffer.readArray<unsigned short>(trailDef->indCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (elemDef->extended.trailDef)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Fx element of type {} has traildef, that's impossible?\n", elemDef->elemType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
header->fx = builder->getIW4OfApi()->read<Game::FxEffectDef>(Game::XAssetType::ASSET_TYPE_FX, name);
|
||||
}
|
||||
|
||||
void IFxEffectDef::loadEfx(Game::XAssetHeader* /*header*/, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
|
||||
@ -270,7 +140,7 @@ namespace Assets
|
||||
// TODO: Convert editor fx to real fx
|
||||
}
|
||||
#else
|
||||
(name);
|
||||
(void)name;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace Assets
|
||||
|
||||
void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
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 loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
|
||||
void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
|
||||
};
|
||||
|
@ -187,6 +187,19 @@ namespace Assets
|
||||
}
|
||||
|
||||
void IFxWorld::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
if (!header->fxWorld) loadFromDisk(header, name, builder);
|
||||
if (!header->fxWorld) generate(header, name, builder);
|
||||
|
||||
assert(header->fxWorld);
|
||||
}
|
||||
|
||||
void IFxWorld::loadFromDisk(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
header->fxWorld = builder->getIW4OfApi()->read<Game::FxWorld>(Game::XAssetType::ASSET_TYPE_FXWORLD, _name);
|
||||
}
|
||||
|
||||
void IFxWorld::generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Game::FxWorld* map = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FXWORLD, name.data()).fxWorld;
|
||||
if (map) return;
|
||||
@ -194,7 +207,7 @@ namespace Assets
|
||||
// Generate
|
||||
map = builder->getAllocator()->allocate<Game::FxWorld>();
|
||||
map->name = builder->getAllocator()->duplicateString(name);
|
||||
|
||||
|
||||
// No glass for you!
|
||||
ZeroMemory(&map->glassSys, sizeof(map->glassSys));
|
||||
|
||||
|
@ -10,5 +10,7 @@ namespace Assets
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||
void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
void generate(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
};
|
||||
}
|
||||
|
@ -80,115 +80,6 @@ namespace Assets
|
||||
|
||||
void IGameWorldMp::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
std::string name = _name;
|
||||
Utils::String::Replace(name, "maps/mp/", "");
|
||||
Utils::String::Replace(name, ".d3dbsp", "");
|
||||
|
||||
Components::FileSystem::File gameWorld(std::format("gameworld/{}.iw4x.json", name));
|
||||
|
||||
if (gameWorld.exists())
|
||||
{
|
||||
nlohmann::json gameWorldJson;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
gameWorldJson = nlohmann::json::parse(gameWorld.getBuffer());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid JSON for gameworld {}! {}", name, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* asset = builder->getAllocator()->allocate<Game::GameWorldMp>();
|
||||
|
||||
if (!gameWorldJson.is_object())
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid GameWorldMp json for {}\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
auto version = gameWorldJson["version"].is_number() ? gameWorldJson["version"].get<int>() : 0;
|
||||
if (version != IW4X_GAMEWORLD_VERSION)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid GameWorld json version for {}, expected {} and got {}\n", name, IW4X_GAMEWORLD_VERSION, version);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gameWorldJson["name"].is_string())
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Missing gameworld name! on {}\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
asset->name = builder->getAllocator()->duplicateString(gameWorldJson["name"].get<std::string>());
|
||||
auto glassData = builder->getAllocator()->allocate<Game::G_GlassData>();
|
||||
|
||||
if (gameWorldJson["glassData"].is_object())
|
||||
{
|
||||
auto jsonGlassData = gameWorldJson["glassData"];
|
||||
|
||||
try
|
||||
{
|
||||
glassData->damageToDestroy = jsonGlassData["damageToDestroy"].get<unsigned short>();
|
||||
glassData->damageToWeaken = jsonGlassData["damageToWeaken"].get<unsigned short>();
|
||||
|
||||
if (jsonGlassData["glassNames"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t glassNames = jsonGlassData["glassNames"];
|
||||
glassData->glassNameCount = glassNames.size();
|
||||
glassData->glassNames = builder->getAllocator()->allocateArray<Game::G_GlassName>(glassData->glassNameCount);
|
||||
|
||||
for (size_t i = 0; i < glassData->glassNameCount; i++)
|
||||
{
|
||||
auto jsonGlassName = glassNames[i];
|
||||
glassData->glassNames[i].nameStr = builder->getAllocator()->duplicateString(jsonGlassName["nameStr"]);
|
||||
|
||||
glassData->glassNames[i].name = jsonGlassName["name"].get<unsigned short>();
|
||||
|
||||
if (jsonGlassName["piecesIndices"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t jsonPiecesIndices = jsonGlassName["piecesIndices"];
|
||||
glassData->glassNames[i].pieceCount = static_cast<unsigned short>(jsonPiecesIndices.size());
|
||||
|
||||
for (size_t j = 0; j < glassData->glassNames[i].pieceCount; j++)
|
||||
{
|
||||
glassData->glassNames[i].pieceIndices[j] = jsonPiecesIndices[j].get<unsigned short>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameWorldJson["glassPieces"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t glassPieces = gameWorldJson["glassPieces"];
|
||||
glassData->pieceCount = glassPieces.size();
|
||||
glassData->glassPieces = builder->getAllocator()->allocateArray<Game::G_GlassPiece>(glassData->pieceCount);
|
||||
|
||||
for (size_t i = 0; i < glassData->pieceCount; i++)
|
||||
{
|
||||
glassData->glassPieces[i].collapseTime = glassPieces[i]["collapseTime"].get<unsigned short>();
|
||||
glassData->glassPieces[i].damageTaken = glassPieces[i]["damageTaken"].get<unsigned short>();
|
||||
glassData->glassPieces[i].lastStateChangeTime = glassPieces[i]["lastStateChangeTime"].get<int>();
|
||||
glassData->glassPieces[i].impactDir = glassPieces[i]["impactDir"].get<char>();
|
||||
|
||||
nlohmann::json::array_t jsonPos = glassPieces[i]["impactPos"];
|
||||
glassData->glassPieces[i].impactPos[0] = jsonPos[0].get<char>();
|
||||
glassData->glassPieces[i].impactPos[1] = jsonPos[1].get<char>();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Malformed GameWorldMp json for {} ({})\n", name, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
asset->g_glassData = glassData;
|
||||
|
||||
header->gameWorldMp = asset;
|
||||
}
|
||||
header->gameWorldMp = builder->getIW4OfApi()->read<Game::GameWorldMp>(Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP, _name);
|
||||
}
|
||||
}
|
||||
|
@ -1,150 +1,11 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IGfxImage.hpp"
|
||||
|
||||
#define IW4X_IMG_VERSION "0"
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IGfxImage::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Game::GfxImage* image = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_IMAGE, name.data()).image;
|
||||
if (image && name[0] != '*') return;
|
||||
|
||||
image = builder->getAllocator()->allocate<Game::GfxImage>();
|
||||
if (!image)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate GfxImage structure!");
|
||||
return;
|
||||
}
|
||||
|
||||
image->name = builder->getAllocator()->duplicateString(name);
|
||||
image->semantic = Game::TextureSemantic::TS_COLOR_MAP;
|
||||
|
||||
const char* tempName = image->name;
|
||||
if (tempName[0] == '*') tempName++;
|
||||
|
||||
Components::FileSystem::File imageFile(std::format("images/{}.iw4xImage", tempName));
|
||||
if (imageFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), imageFile.getBuffer());
|
||||
|
||||
__int64 magic = reader.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xImg" IW4X_IMG_VERSION, 8))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading image '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
image->mapType = reader.read<char>();
|
||||
image->semantic = reader.read<Game::TextureSemantic>();
|
||||
image->category = reader.read<char>();
|
||||
|
||||
int dataLength = reader.read<int>();
|
||||
image->cardMemory.platform[0] = dataLength;
|
||||
image->cardMemory.platform[1] = dataLength;
|
||||
|
||||
Game::GfxImageLoadDefIW3 loadDef;
|
||||
image->texture.loadDef = reinterpret_cast<Game::GfxImageLoadDef*>(reader.readArray<char>(dataLength + 16));
|
||||
std::memcpy(&loadDef, image->texture.loadDef, sizeof(loadDef));
|
||||
|
||||
image->texture.loadDef->levelCount = loadDef.levelCount;
|
||||
image->texture.loadDef->flags = loadDef.flags;
|
||||
image->texture.loadDef->format = loadDef.format;
|
||||
image->texture.loadDef->resourceSize = loadDef.resourceSize;
|
||||
ZeroMemory(image->texture.loadDef->pad, 3);
|
||||
|
||||
if (image->texture.loadDef->resourceSize != dataLength)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Resource size doesn't match the data length ({})!\n", name);
|
||||
}
|
||||
|
||||
image->width = loadDef.dimensions[0];
|
||||
image->height = loadDef.dimensions[1];
|
||||
image->depth = loadDef.dimensions[2];
|
||||
|
||||
image->delayLoadPixels = true;
|
||||
|
||||
header->image = image;
|
||||
}
|
||||
else if (name[0] != '*')
|
||||
{
|
||||
char nameBuffer[MAX_PATH] = { 0 };
|
||||
Components::Materials::FormatImagePath(nameBuffer, sizeof(nameBuffer), 0, 0, name.data());
|
||||
Components::FileSystem::File iwi(nameBuffer);
|
||||
|
||||
if (!iwi.exists())
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Loading image '{}' failed!", iwi.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
auto iwiBuffer = iwi.getBuffer();
|
||||
|
||||
const Game::GfxImageFileHeader* iwiHeader = reinterpret_cast<const Game::GfxImageFileHeader*>(iwiBuffer.data());
|
||||
|
||||
if (std::memcmp(iwiHeader->tag, "IWi", 3) && iwiHeader->version == 8)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Image is not a valid IWi!");
|
||||
return;
|
||||
}
|
||||
|
||||
image->mapType = Game::MAPTYPE_2D;
|
||||
image->cardMemory.platform[0] = iwiHeader->fileSizeForPicmip[0] - 32;
|
||||
image->cardMemory.platform[1] = iwiHeader->fileSizeForPicmip[0] - 32;
|
||||
|
||||
image->texture.loadDef = builder->getAllocator()->allocate<Game::GfxImageLoadDef>();
|
||||
if (!image->texture.loadDef)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Failed to allocate GfxImageLoadDef structure!");
|
||||
return;
|
||||
}
|
||||
|
||||
image->texture.loadDef->flags = iwiHeader->flags;
|
||||
image->texture.loadDef->levelCount = 0;
|
||||
|
||||
image->width = iwiHeader->dimensions[0];
|
||||
image->height = iwiHeader->dimensions[1];
|
||||
image->depth = iwiHeader->dimensions[2];
|
||||
|
||||
switch (iwiHeader->format)
|
||||
{
|
||||
case Game::IMG_FORMAT_BITMAP_RGBA:
|
||||
{
|
||||
image->texture.loadDef->format = 21;
|
||||
break;
|
||||
}
|
||||
|
||||
case Game::IMG_FORMAT_BITMAP_RGB:
|
||||
{
|
||||
image->texture.loadDef->format = 20;
|
||||
break;
|
||||
}
|
||||
|
||||
case Game::IMG_FORMAT_DXT1:
|
||||
{
|
||||
image->texture.loadDef->format = 0x31545844;
|
||||
break;
|
||||
}
|
||||
|
||||
case Game::IMG_FORMAT_DXT3:
|
||||
{
|
||||
image->texture.loadDef->format = 0x33545844;
|
||||
break;
|
||||
}
|
||||
|
||||
case Game::IMG_FORMAT_DXT5:
|
||||
{
|
||||
image->texture.loadDef->format = 0x35545844;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
header->image = image;
|
||||
}
|
||||
header->image = builder->getIW4OfApi()->read<Game::GfxImage>(Game::XAssetType::ASSET_TYPE_IMAGE, name);
|
||||
}
|
||||
|
||||
void IGfxImage::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -1,44 +1,11 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IGfxLightDef.hpp"
|
||||
|
||||
#define IW4X_LIGHT_VERSION "0"
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IGfxLightDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File mapFile(std::format("lights/{}.iw4xLight", name));
|
||||
|
||||
if (mapFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer());
|
||||
|
||||
char* magic = reader.readArray<char>(7);
|
||||
if (std::memcmp(magic, "IW4xLit", 7))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading light '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
std::string version;
|
||||
version.push_back(reader.read<char>());
|
||||
if (version != IW4X_LIGHT_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading light '{}' failed, expected version is {}, but it was {}!", name, IW4X_LIGHT_VERSION, version);
|
||||
}
|
||||
|
||||
Game::GfxLightDef* asset = reader.readObject<Game::GfxLightDef>();
|
||||
header->lightDef = asset;
|
||||
|
||||
if (asset->name)
|
||||
{
|
||||
asset->name = reader.readCString();
|
||||
}
|
||||
|
||||
if (asset->attenuation.image)
|
||||
{
|
||||
asset->attenuation.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image;
|
||||
}
|
||||
}
|
||||
header->lightDef = builder->getIW4OfApi()->read<Game::GfxLightDef>(Game::XAssetType::ASSET_TYPE_LIGHT_DEF, name);
|
||||
}
|
||||
|
||||
void IGfxLightDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -1,450 +1,16 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IGfxWorld.hpp"
|
||||
|
||||
#define IW4X_GFXMAP_VERSION 1
|
||||
|
||||
// The xmodel vehicle_small_hatch_green_destructible_mp causes EXTREME lag
|
||||
// when placed in the world, for reasons unknown.
|
||||
//
|
||||
// Something happens with the SModelSurfIterator which makes it load garbage
|
||||
// as an XSurface in the middle of otherwise valid surfaces. This bug is very
|
||||
// easy to reproduce with an empty map and just this car in the middle
|
||||
//
|
||||
// As of know we do not know why the iterator corruption occurs or what causes
|
||||
// it. It doesn't seem linked to the SModel, nor to the materials or techsets,
|
||||
// nor to the sortkeys, nor to the tilemode, boneinfo, and so on. So for now
|
||||
// and to make it work for majority of users, we just swap the car. (no, using
|
||||
// the identical car from iw4's favela_escape doesn't work either!)
|
||||
//
|
||||
// Two other models have this problem: ch_apartment_9story_noentry_02 and
|
||||
// ch_apartment_5story_noentry_01
|
||||
// But these exist in mp_vacant in slightly different versions, and can be
|
||||
// swapped safely by deleting the two .iw4XModel files and requiring mp_vacant
|
||||
// or a minimal zone containing just these two models.
|
||||
//
|
||||
#define SWAP_GREEN_VEHICLE_XMODEL 1
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IGfxWorld::loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
|
||||
{
|
||||
if (asset->sortedSurfIndex)
|
||||
{
|
||||
asset->sortedSurfIndex = reader->readArray<unsigned short>(asset->staticSurfaceCount + asset->staticSurfaceCountNoDecal);
|
||||
}
|
||||
|
||||
if (asset->smodelInsts)
|
||||
{
|
||||
asset->smodelInsts = reader->readArray<Game::GfxStaticModelInst>(asset->smodelCount);
|
||||
}
|
||||
|
||||
if (asset->surfaces)
|
||||
{
|
||||
asset->surfaces = reader->readArray<Game::GfxSurface>(world->surfaceCount);
|
||||
|
||||
for (unsigned int i = 0; i < world->surfaceCount; ++i)
|
||||
{
|
||||
Game::GfxSurface* surface = &asset->surfaces[i];
|
||||
|
||||
if (surface->material)
|
||||
{
|
||||
world->dpvs.surfaces[i].material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString().data(), builder).material;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->surfacesBounds)
|
||||
{
|
||||
asset->surfacesBounds = reader->readArray<Game::GfxSurfaceBounds>(world->surfaceCount);
|
||||
}
|
||||
|
||||
if (asset->smodelDrawInsts)
|
||||
{
|
||||
asset->smodelDrawInsts = reader->readArray<Game::GfxStaticModelDrawInst>(asset->smodelCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->smodelCount; ++i)
|
||||
{
|
||||
Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i];
|
||||
|
||||
if (model->model)
|
||||
{
|
||||
auto name = reader->readString();
|
||||
|
||||
model->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, name.data(), builder).model;
|
||||
|
||||
assert(model->model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IGfxWorld::loadGfxWorldDraw(Game::GfxWorldDraw* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
|
||||
{
|
||||
if (asset->reflectionProbes)
|
||||
{
|
||||
asset->reflectionProbes = reader->readArray<Game::GfxImage*>(asset->reflectionProbeCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i)
|
||||
{
|
||||
asset->reflectionProbes[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image;
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->reflectionProbeOrigins)
|
||||
{
|
||||
asset->reflectionProbeOrigins = reader->readArray<Game::GfxReflectionProbe>(asset->reflectionProbeCount);
|
||||
}
|
||||
|
||||
if (asset->lightmaps)
|
||||
{
|
||||
asset->lightmaps = reader->readArray<Game::GfxLightmapArray>(asset->lightmapCount);
|
||||
|
||||
for (int i = 0; i < asset->lightmapCount; ++i)
|
||||
{
|
||||
Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i];
|
||||
|
||||
if (lightmapArray->primary)
|
||||
{
|
||||
lightmapArray->primary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image;
|
||||
}
|
||||
|
||||
if (lightmapArray->secondary)
|
||||
{
|
||||
lightmapArray->secondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->lightmapOverridePrimary)
|
||||
{
|
||||
asset->lightmapOverridePrimary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image;
|
||||
}
|
||||
|
||||
if (asset->lightmapOverrideSecondary)
|
||||
{
|
||||
asset->lightmapOverrideSecondary = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader->readString().data(), builder).image;
|
||||
}
|
||||
|
||||
// saveGfxWorldVertexData
|
||||
{
|
||||
if (asset->vd.vertices)
|
||||
{
|
||||
asset->vd.vertices = reader->readArray<Game::GfxWorldVertex>(asset->vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
// saveGfxWorldVertexLayerData
|
||||
{
|
||||
if (asset->vld.data)
|
||||
{
|
||||
// no align for char
|
||||
asset->vld.data = reader->readArray<char>(asset->vertexLayerDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->indices)
|
||||
{
|
||||
asset->indices = reader->readArray<unsigned short>(asset->indexCount);
|
||||
}
|
||||
}
|
||||
|
||||
void IGfxWorld::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
std::string name = _name;
|
||||
Utils::String::Replace(name, "maps/mp/", "");
|
||||
Utils::String::Replace(name, ".d3dbsp", "");
|
||||
|
||||
Components::FileSystem::File mapFile(std::format("gfxworld/{}.iw4xGfxWorld", name));
|
||||
|
||||
if (mapFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), mapFile.getBuffer());
|
||||
|
||||
__int64 magic = reader.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xGfxW", 8))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
int version = reader.read<int>();
|
||||
if (version != IW4X_GFXMAP_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading gfxworld '{}' failed, expected version is {}, but it was {}!", name, IW4X_GFXMAP_VERSION, version);
|
||||
}
|
||||
|
||||
Game::GfxWorld* asset = reader.readObject<Game::GfxWorld>();
|
||||
header->gfxWorld = asset;
|
||||
|
||||
if (asset->name)
|
||||
{
|
||||
asset->name = reader.readCString();
|
||||
}
|
||||
|
||||
if (asset->baseName)
|
||||
{
|
||||
asset->baseName = reader.readCString();
|
||||
}
|
||||
|
||||
if (asset->skies)
|
||||
{
|
||||
asset->skies = reader.readArray<Game::GfxSky>(asset->skyCount);
|
||||
|
||||
for (int i = 0; i < asset->skyCount; ++i)
|
||||
{
|
||||
Game::GfxSky* sky = &asset->skies[i];
|
||||
|
||||
if (sky->skyStartSurfs)
|
||||
{
|
||||
sky->skyStartSurfs = reader.readArray<int>(sky->skySurfCount);
|
||||
}
|
||||
|
||||
if (sky->skyImage)
|
||||
{
|
||||
sky->skyImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GfxWorldDpvsPlanes
|
||||
{
|
||||
if (asset->dpvsPlanes.planes)
|
||||
{
|
||||
void* oldPtr = asset->dpvsPlanes.planes;
|
||||
asset->dpvsPlanes.planes = reader.readArray<Game::cplane_s>(asset->planeCount);
|
||||
|
||||
if (builder->getAllocator()->isPointerMapped(oldPtr))
|
||||
{
|
||||
asset->dpvsPlanes.planes = builder->getAllocator()->getPointer<Game::cplane_s>(oldPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder->getAllocator()->mapPointer(oldPtr, asset->dpvsPlanes.planes);
|
||||
Components::Logger::Print("GfxWorld dpvs planes not mapped. This shouldn't happen. Make sure to load the ClipMap first!\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->dpvsPlanes.nodes)
|
||||
{
|
||||
asset->dpvsPlanes.nodes = reader.readArray<unsigned short>(asset->nodeCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int cellCount = asset->dpvsPlanes.cellCount;
|
||||
|
||||
if (asset->aabbTreeCounts)
|
||||
{
|
||||
asset->aabbTreeCounts = reader.readArray<Game::GfxCellTreeCount>(cellCount);
|
||||
}
|
||||
|
||||
if (asset->aabbTrees)
|
||||
{
|
||||
asset->aabbTrees = reader.readArray<Game::GfxCellTree>(cellCount);
|
||||
|
||||
for (int i = 0; i < cellCount; ++i)
|
||||
{
|
||||
Game::GfxCellTree* cellTree = &asset->aabbTrees[i];
|
||||
|
||||
if (cellTree->aabbTree)
|
||||
{
|
||||
cellTree->aabbTree = reader.readArray<Game::GfxAabbTree>(asset->aabbTreeCounts[i].aabbTreeCount);
|
||||
|
||||
for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j)
|
||||
{
|
||||
Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j];
|
||||
|
||||
if (aabbTree->smodelIndexes)
|
||||
{
|
||||
unsigned short* oldPointer = aabbTree->smodelIndexes;
|
||||
if(builder->getAllocator()->isPointerMapped(oldPointer))
|
||||
{
|
||||
// We still have to read it
|
||||
reader.readArray<unsigned short>(aabbTree->smodelIndexCount);
|
||||
|
||||
aabbTree->smodelIndexes = builder->getAllocator()->getPointer<unsigned short>(oldPointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
aabbTree->smodelIndexes = reader.readArray<unsigned short>(aabbTree->smodelIndexCount);
|
||||
|
||||
for (unsigned short k = 0; k < aabbTree->smodelIndexCount; ++k)
|
||||
{
|
||||
builder->getAllocator()->mapPointer(&oldPointer[k], &aabbTree->smodelIndexes[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->cells)
|
||||
{
|
||||
asset->cells = reader.readArray<Game::GfxCell>(cellCount);
|
||||
|
||||
for (int i = 0; i < cellCount; ++i)
|
||||
{
|
||||
Game::GfxCell* cell = &asset->cells[i];
|
||||
|
||||
if (cell->portals)
|
||||
{
|
||||
cell->portals = reader.readArray<Game::GfxPortal>(cell->portalCount);
|
||||
|
||||
for (int j = 0; j < cell->portalCount; ++j)
|
||||
{
|
||||
Game::GfxPortal* portal = &cell->portals[j];
|
||||
|
||||
if (portal->vertices)
|
||||
{
|
||||
portal->vertices = reader.readArray<Game::vec3_t>(portal->vertexCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cell->reflectionProbes)
|
||||
{
|
||||
cell->reflectionProbes = reader.readArray<char>(cell->reflectionProbeCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->loadGfxWorldDraw(&asset->draw, builder, &reader);
|
||||
|
||||
// GfxLightGrid
|
||||
{
|
||||
if (asset->lightGrid.rowDataStart)
|
||||
{
|
||||
asset->lightGrid.rowDataStart = reader.readArray<unsigned short>((asset->lightGrid.maxs[asset->lightGrid.rowAxis] - asset->lightGrid.mins[asset->lightGrid.rowAxis]) + 1);
|
||||
}
|
||||
|
||||
if (asset->lightGrid.rawRowData)
|
||||
{
|
||||
asset->lightGrid.rawRowData = reader.readArray<char>(asset->lightGrid.rawRowDataSize);
|
||||
}
|
||||
|
||||
if (asset->lightGrid.entries)
|
||||
{
|
||||
asset->lightGrid.entries = reader.readArray<Game::GfxLightGridEntry>(asset->lightGrid.entryCount);
|
||||
}
|
||||
|
||||
if (asset->lightGrid.colors)
|
||||
{
|
||||
asset->lightGrid.colors = reader.readArray<Game::GfxLightGridColors>(asset->lightGrid.colorCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->models)
|
||||
{
|
||||
asset->models = reader.readArray<Game::GfxBrushModel>(asset->modelCount);
|
||||
}
|
||||
|
||||
if (asset->materialMemory)
|
||||
{
|
||||
asset->materialMemory = reader.readArray<Game::MaterialMemory>(asset->materialMemoryCount);
|
||||
|
||||
for (int i = 0; i < asset->materialMemoryCount; ++i)
|
||||
{
|
||||
Game::MaterialMemory* materialMemory = &asset->materialMemory[i];
|
||||
|
||||
if (materialMemory->material)
|
||||
{
|
||||
materialMemory->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->sun.spriteMaterial)
|
||||
{
|
||||
asset->sun.spriteMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material;
|
||||
}
|
||||
|
||||
if (asset->sun.flareMaterial)
|
||||
{
|
||||
asset->sun.flareMaterial = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString().data(), builder).material;
|
||||
}
|
||||
|
||||
if (asset->outdoorImage)
|
||||
{
|
||||
asset->outdoorImage = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image;
|
||||
}
|
||||
|
||||
if (asset->primaryLightCount > 0)
|
||||
{
|
||||
Utils::Stream::ClearPointer(&asset->primaryLightEntityShadowVis);
|
||||
}
|
||||
|
||||
if (asset->dpvsDyn.dynEntClientCount[0] > 0)
|
||||
{
|
||||
Utils::Stream::ClearPointer(&asset->sceneDynModel);
|
||||
Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[0]);
|
||||
Utils::Stream::ClearPointer(&asset->nonSunPrimaryLightForModelDynEnt);
|
||||
}
|
||||
|
||||
if (asset->dpvsDyn.dynEntClientCount[1] > 0)
|
||||
{
|
||||
Utils::Stream::ClearPointer(&asset->sceneDynBrush);
|
||||
Utils::Stream::ClearPointer(&asset->primaryLightDynEntShadowVis[1]);
|
||||
}
|
||||
|
||||
if (asset->shadowGeom)
|
||||
{
|
||||
asset->shadowGeom = reader.readArray<Game::GfxShadowGeometry>(asset->primaryLightCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
|
||||
{
|
||||
Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i];
|
||||
|
||||
if (shadowGeometry->sortedSurfIndex)
|
||||
{
|
||||
shadowGeometry->sortedSurfIndex = reader.readArray<unsigned short>(shadowGeometry->surfaceCount);
|
||||
}
|
||||
|
||||
if (shadowGeometry->smodelIndex)
|
||||
{
|
||||
shadowGeometry->smodelIndex = reader.readArray<unsigned short>(shadowGeometry->smodelCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->lightRegion)
|
||||
{
|
||||
asset->lightRegion = reader.readArray<Game::GfxLightRegion>(asset->primaryLightCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
|
||||
{
|
||||
Game::GfxLightRegion* lightRegion = &asset->lightRegion[i];
|
||||
|
||||
if (lightRegion->hulls)
|
||||
{
|
||||
lightRegion->hulls = reader.readArray<Game::GfxLightRegionHull>(lightRegion->hullCount);
|
||||
|
||||
for (unsigned int j = 0; j < lightRegion->hullCount; ++j)
|
||||
{
|
||||
Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j];
|
||||
|
||||
if (lightRegionHull->axis)
|
||||
{
|
||||
lightRegionHull->axis = reader.readArray<Game::GfxLightRegionAxis>(lightRegionHull->axisCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->loadGfxWorldDpvsStatic(asset, &asset->dpvs, builder, &reader);
|
||||
|
||||
// Obsolete, IW3 has no support for that
|
||||
if (asset->heroOnlyLights)
|
||||
{
|
||||
asset->heroOnlyLights = reader.readArray<Game::GfxHeroOnlyLight>(asset->heroOnlyLightCount);
|
||||
}
|
||||
}
|
||||
header->gfxWorld = builder->getIW4OfApi()->read<Game::GfxWorld>(Game::XAssetType::ASSET_TYPE_GFXWORLD, _name);
|
||||
}
|
||||
|
||||
void IGfxWorld::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Game::GfxWorld* asset = header.gfxWorld;
|
||||
|
||||
auto* asset = header.gfxWorld;
|
||||
if (asset->draw.reflectionProbes)
|
||||
{
|
||||
for (unsigned int i = 0; i < asset->draw.reflectionProbeCount; ++i)
|
||||
@ -455,7 +21,7 @@ namespace Assets
|
||||
|
||||
if (asset->draw.lightmaps)
|
||||
{
|
||||
for (int i = 0; i < asset->draw.lightmapCount; ++i)
|
||||
for (auto i = 0; i < asset->draw.lightmapCount; ++i)
|
||||
{
|
||||
if (asset->draw.lightmaps[i].primary)
|
||||
{
|
||||
@ -599,7 +165,7 @@ namespace Assets
|
||||
{
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
|
||||
Game::GfxImage** imageDest = buffer->dest<Game::GfxImage*>();
|
||||
auto** imageDest = buffer->dest<Game::GfxImage*>();
|
||||
buffer->saveArray(asset->reflectionProbes, asset->reflectionProbeCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->reflectionProbeCount; ++i)
|
||||
@ -648,13 +214,13 @@ namespace Assets
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
|
||||
Game::GfxLightmapArray* lightmapArrayDestTable = buffer->dest<Game::GfxLightmapArray>();
|
||||
auto* lightmapArrayDestTable = buffer->dest<Game::GfxLightmapArray>();
|
||||
buffer->saveArray(asset->lightmaps, asset->lightmapCount);
|
||||
|
||||
for (int i = 0; i < asset->lightmapCount; ++i)
|
||||
{
|
||||
Game::GfxLightmapArray* lightmapArrayDest = &lightmapArrayDestTable[i];
|
||||
Game::GfxLightmapArray* lightmapArray = &asset->lightmaps[i];
|
||||
auto* lightmapArrayDest = &lightmapArrayDestTable[i];
|
||||
auto* lightmapArray = &asset->lightmaps[i];
|
||||
|
||||
if (lightmapArray->primary)
|
||||
{
|
||||
@ -854,13 +420,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxSurface");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxSurface* destSurfaceTable = buffer->dest<Game::GfxSurface>();
|
||||
auto* destSurfaceTable = buffer->dest<Game::GfxSurface>();
|
||||
buffer->saveArray(asset->surfaces, world->surfaceCount);
|
||||
|
||||
for (unsigned int i = 0; i < world->surfaceCount; ++i)
|
||||
{
|
||||
Game::GfxSurface* surface = &asset->surfaces[i];
|
||||
Game::GfxSurface* destSurface = &destSurfaceTable[i];
|
||||
auto* surface = &asset->surfaces[i];
|
||||
auto* destSurface = &destSurfaceTable[i];
|
||||
|
||||
if (surface->material)
|
||||
{
|
||||
@ -890,13 +456,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxStaticModelDrawInst");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxStaticModelDrawInst* destModelTable = buffer->dest<Game::GfxStaticModelDrawInst>();
|
||||
auto* destModelTable = buffer->dest<Game::GfxStaticModelDrawInst>();
|
||||
buffer->saveArray(asset->smodelDrawInsts, asset->smodelCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->smodelCount; ++i)
|
||||
{
|
||||
Game::GfxStaticModelDrawInst* model = &asset->smodelDrawInsts[i];
|
||||
Game::GfxStaticModelDrawInst* destModel = &destModelTable[i];
|
||||
auto* model = &asset->smodelDrawInsts[i];
|
||||
auto* destModel = &destModelTable[i];
|
||||
|
||||
if (model->model)
|
||||
{
|
||||
@ -986,8 +552,8 @@ namespace Assets
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
SaveLogEnter("GfxWorld");
|
||||
|
||||
Game::GfxWorld* asset = header.gfxWorld;
|
||||
Game::GfxWorld* dest = buffer->dest<Game::GfxWorld>();
|
||||
auto* asset = header.gfxWorld;
|
||||
auto* dest = buffer->dest<Game::GfxWorld>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
@ -1012,13 +578,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxSky");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxSky* destSkyTable = buffer->dest<Game::GfxSky>();
|
||||
auto* destSkyTable = buffer->dest<Game::GfxSky>();
|
||||
buffer->saveArray(asset->skies, asset->skyCount);
|
||||
|
||||
for (int i = 0; i < asset->skyCount; ++i)
|
||||
{
|
||||
Game::GfxSky* destSky = &destSkyTable[i];
|
||||
Game::GfxSky* sky = &asset->skies[i];
|
||||
auto* destSky = &destSkyTable[i];
|
||||
auto* sky = &asset->skies[i];
|
||||
|
||||
if (sky->skyStartSurfs)
|
||||
{
|
||||
@ -1059,13 +625,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxCellTree");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_128);
|
||||
Game::GfxCellTree* destCellTreeTable = buffer->dest<Game::GfxCellTree>();
|
||||
auto* destCellTreeTable = buffer->dest<Game::GfxCellTree>();
|
||||
buffer->saveArray(asset->aabbTrees, cellCount);
|
||||
|
||||
for (int i = 0; i < cellCount; ++i)
|
||||
{
|
||||
Game::GfxCellTree* destCellTree = &destCellTreeTable[i];
|
||||
Game::GfxCellTree* cellTree = &asset->aabbTrees[i];
|
||||
auto* destCellTree = &destCellTreeTable[i];
|
||||
auto* cellTree = &asset->aabbTrees[i];
|
||||
|
||||
if (cellTree->aabbTree)
|
||||
{
|
||||
@ -1073,7 +639,7 @@ namespace Assets
|
||||
SaveLogEnter("GfxAabbTree");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxAabbTree* destAabbTreeTable = buffer->dest<Game::GfxAabbTree>();
|
||||
auto* destAabbTreeTable = buffer->dest<Game::GfxAabbTree>();
|
||||
buffer->saveArray(cellTree->aabbTree, asset->aabbTreeCounts[i].aabbTreeCount);
|
||||
|
||||
// ok this one is based on some assumptions because the actual count is this
|
||||
@ -1083,8 +649,8 @@ namespace Assets
|
||||
|
||||
for (int j = 0; j < asset->aabbTreeCounts[i].aabbTreeCount; ++j)
|
||||
{
|
||||
Game::GfxAabbTree* destAabbTree = &destAabbTreeTable[j];
|
||||
Game::GfxAabbTree* aabbTree = &cellTree->aabbTree[j];
|
||||
auto* destAabbTree = &destAabbTreeTable[j];
|
||||
auto* aabbTree = &cellTree->aabbTree[j];
|
||||
|
||||
if (aabbTree->smodelIndexes)
|
||||
{
|
||||
@ -1122,13 +688,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxCell");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxCell* destCellTable = buffer->dest<Game::GfxCell>();
|
||||
auto* destCellTable = buffer->dest<Game::GfxCell>();
|
||||
buffer->saveArray(asset->cells, cellCount);
|
||||
|
||||
for (int i = 0; i < cellCount; ++i)
|
||||
{
|
||||
Game::GfxCell* destCell = &destCellTable[i];
|
||||
Game::GfxCell* cell = &asset->cells[i];
|
||||
auto* destCell = &destCellTable[i];
|
||||
auto* cell = &asset->cells[i];
|
||||
|
||||
if (cell->portals)
|
||||
{
|
||||
@ -1136,13 +702,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxPortal");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxPortal* destPortalTable = buffer->dest<Game::GfxPortal>();
|
||||
auto* destPortalTable = buffer->dest<Game::GfxPortal>();
|
||||
buffer->saveArray(cell->portals, cell->portalCount);
|
||||
|
||||
for (int j = 0; j < cell->portalCount; ++j)
|
||||
{
|
||||
Game::GfxPortal* destPortal = &destPortalTable[j];
|
||||
Game::GfxPortal* portal = &cell->portals[j];
|
||||
auto* destPortal = &destPortalTable[j];
|
||||
auto* portal = &cell->portals[j];
|
||||
|
||||
if (portal->vertices)
|
||||
{
|
||||
@ -1189,13 +755,13 @@ namespace Assets
|
||||
SaveLogEnter("MaterialMemory");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::MaterialMemory* destMaterialMemoryTable = buffer->dest<Game::MaterialMemory>();
|
||||
auto* destMaterialMemoryTable = buffer->dest<Game::MaterialMemory>();
|
||||
buffer->saveArray(asset->materialMemory, asset->materialMemoryCount);
|
||||
|
||||
for (int i = 0; i < asset->materialMemoryCount; ++i)
|
||||
{
|
||||
Game::MaterialMemory* destMaterialMemory = &destMaterialMemoryTable[i];
|
||||
Game::MaterialMemory* materialMemory = &asset->materialMemory[i];
|
||||
auto* destMaterialMemory = &destMaterialMemoryTable[i];
|
||||
auto* materialMemory = &asset->materialMemory[i];
|
||||
|
||||
if (materialMemory->material)
|
||||
{
|
||||
@ -1284,13 +850,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxShadowGeometry");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxShadowGeometry* destShadowGeometryTable = buffer->dest<Game::GfxShadowGeometry>();
|
||||
auto* destShadowGeometryTable = buffer->dest<Game::GfxShadowGeometry>();
|
||||
buffer->saveArray(asset->shadowGeom, asset->primaryLightCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
|
||||
{
|
||||
Game::GfxShadowGeometry* destShadowGeometry = &destShadowGeometryTable[i];
|
||||
Game::GfxShadowGeometry* shadowGeometry = &asset->shadowGeom[i];
|
||||
auto* destShadowGeometry = &destShadowGeometryTable[i];
|
||||
auto* shadowGeometry = &asset->shadowGeom[i];
|
||||
|
||||
if (shadowGeometry->sortedSurfIndex)
|
||||
{
|
||||
@ -1317,13 +883,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxLightRegion");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxLightRegion* destLightRegionTable = buffer->dest<Game::GfxLightRegion>();
|
||||
auto* destLightRegionTable = buffer->dest<Game::GfxLightRegion>();
|
||||
buffer->saveArray(asset->lightRegion, asset->primaryLightCount);
|
||||
|
||||
for (unsigned int i = 0; i < asset->primaryLightCount; ++i)
|
||||
{
|
||||
Game::GfxLightRegion* destLightRegion = &destLightRegionTable[i];
|
||||
Game::GfxLightRegion* lightRegion = &asset->lightRegion[i];
|
||||
auto* destLightRegion = &destLightRegionTable[i];
|
||||
auto* lightRegion = &asset->lightRegion[i];
|
||||
|
||||
if (lightRegion->hulls)
|
||||
{
|
||||
@ -1331,13 +897,13 @@ namespace Assets
|
||||
SaveLogEnter("GfxLightRegionHull");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
Game::GfxLightRegionHull* destLightRegionHullTable = buffer->dest<Game::GfxLightRegionHull>();
|
||||
auto* destLightRegionHullTable = buffer->dest<Game::GfxLightRegionHull>();
|
||||
buffer->saveArray(lightRegion->hulls, lightRegion->hullCount);
|
||||
|
||||
for (unsigned int j = 0; j < lightRegion->hullCount; ++j)
|
||||
{
|
||||
Game::GfxLightRegionHull* destLightRegionHull = &destLightRegionHullTable[j];
|
||||
Game::GfxLightRegionHull* lightRegionHull = &lightRegion->hulls[j];
|
||||
auto* destLightRegionHull = &destLightRegionHullTable[j];
|
||||
auto* lightRegionHull = &lightRegion->hulls[j];
|
||||
|
||||
if (lightRegionHull->axis)
|
||||
{
|
||||
@ -1373,7 +939,6 @@ namespace Assets
|
||||
Utils::Stream::ClearPointer(&dest->heroOnlyLights);
|
||||
}
|
||||
|
||||
//buffer->setPointerAssertion(false);
|
||||
buffer->popBlock();
|
||||
SaveLogExit();
|
||||
}
|
||||
|
@ -18,8 +18,5 @@ namespace Assets
|
||||
void savesunflare_t(Game::sunflare_t* asset, Game::sunflare_t* dest, Components::ZoneBuilder::Zone* builder);
|
||||
void saveGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Game::GfxWorldDpvsStatic* dest, int planeCount, Components::ZoneBuilder::Zone* builder);
|
||||
void saveGfxWorldDpvsDynamic(Game::GfxWorldDpvsDynamic* asset, Game::GfxWorldDpvsDynamic* dest, int cellCount, Components::ZoneBuilder::Zone* builder);
|
||||
|
||||
void loadGfxWorldDraw(Game::GfxWorldDraw* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
|
||||
void loadGfxWorldDpvsStatic(Game::GfxWorld* world, Game::GfxWorldDpvsStatic* asset, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
|
||||
};
|
||||
}
|
||||
|
@ -5,106 +5,16 @@ namespace Assets
|
||||
{
|
||||
void ILoadedSound::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File soundFile(std::format("loaded_sound/{}", name));
|
||||
if (!soundFile.exists())
|
||||
{
|
||||
header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd;
|
||||
return;
|
||||
}
|
||||
|
||||
Game::LoadedSound* sound = builder->getAllocator()->allocate<Game::LoadedSound>();
|
||||
if (!sound)
|
||||
{
|
||||
Components::Logger::Print("Error allocating memory for sound structure!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Game::LoadedSound* reference = nullptr;
|
||||
if (!reference) reference = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_LOADED_SOUND, "weapons/c4_detpack/c4_drop_dirt1.wav").loadSnd;
|
||||
|
||||
std::memcpy(sound, reference, sizeof(Game::LoadedSound));
|
||||
sound->sound.data = nullptr;
|
||||
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), soundFile.getBuffer());
|
||||
|
||||
unsigned int chunkIDBuffer = reader.read<unsigned int>();
|
||||
if (chunkIDBuffer != 0x46464952) // RIFF
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int chunkSize = reader.read<unsigned int>();
|
||||
|
||||
unsigned int format = reader.read<unsigned int>();
|
||||
if (format != 0x45564157) // WAVE
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, header is invalid!", name);
|
||||
return;
|
||||
}
|
||||
|
||||
while (!sound->sound.data && !reader.end())
|
||||
{
|
||||
chunkIDBuffer = reader.read<unsigned int>();
|
||||
chunkSize = reader.read<unsigned int>();
|
||||
|
||||
switch (chunkIDBuffer)
|
||||
{
|
||||
case 0x20746D66: // fmt
|
||||
if (chunkSize >= 16)
|
||||
{
|
||||
sound->sound.info.format = reader.read<short>();
|
||||
if (sound->sound.info.format != 1 && sound->sound.info.format != 17)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, invalid format!", name);
|
||||
return;
|
||||
}
|
||||
|
||||
sound->sound.info.channels = reader.read<short>();
|
||||
sound->sound.info.rate = reader.read<int>();
|
||||
sound->sound.info.samples = reader.read<int>();
|
||||
sound->sound.info.block_size = reader.read<short>();
|
||||
sound->sound.info.bits = reader.read<short>();
|
||||
|
||||
// skip any extra parameters
|
||||
if (chunkSize > 16)
|
||||
{
|
||||
reader.seekRelative(chunkSize - 16);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x61746164: // data
|
||||
sound->sound.info.data_len = chunkSize;
|
||||
sound->sound.data = reader.readArray<char>(chunkSize);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (chunkSize > 0)
|
||||
{
|
||||
reader.seekRelative(chunkSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sound->sound.info.data_ptr)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading sound '{}' failed, invalid format!", name);
|
||||
return;
|
||||
}
|
||||
|
||||
sound->name = builder->getAllocator()->duplicateString(name.data());
|
||||
header->loadSnd = sound;
|
||||
header->loadSnd = builder->getIW4OfApi()->read<Game::LoadedSound>(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, name);
|
||||
}
|
||||
|
||||
void ILoadedSound::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
AssertSize(Game::LoadedSound, 44);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::LoadedSound* asset = header.loadSnd;
|
||||
Game::LoadedSound* dest = buffer->dest<Game::LoadedSound>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.loadSnd;
|
||||
auto* dest = buffer->dest<Game::LoadedSound>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
|
@ -5,55 +5,7 @@ namespace Assets
|
||||
{
|
||||
void IMapEnts::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
std::string name = _name;
|
||||
Utils::String::Replace(name, "maps/", "");
|
||||
Utils::String::Replace(name, "mp/", "");
|
||||
Utils::String::Replace(name, ".d3dbsp", "");
|
||||
|
||||
Components::FileSystem::File ents(std::format("mapents/{}.ents", name));
|
||||
if (ents.exists())
|
||||
{
|
||||
Game::MapEnts* entites = builder->getAllocator()->allocate<Game::MapEnts>();
|
||||
Game::MapEnts* orgEnts = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).mapEnts;
|
||||
|
||||
// TODO: Get rid of that
|
||||
if (!orgEnts)
|
||||
{
|
||||
orgEnts = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, "maps/iw4_credits.d3dbsp").mapEnts;
|
||||
|
||||
if (!orgEnts)
|
||||
{
|
||||
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_MAP_ENTS, [](Game::XAssetHeader header, void* mapEnts)
|
||||
{
|
||||
if (!*reinterpret_cast<void**>(mapEnts))
|
||||
{
|
||||
*reinterpret_cast<Game::MapEnts**>(mapEnts) = header.mapEnts;
|
||||
}
|
||||
}, &orgEnts, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (orgEnts)
|
||||
{
|
||||
std::memcpy(entites, orgEnts, sizeof Game::MapEnts);
|
||||
}
|
||||
else
|
||||
{
|
||||
entites->stageCount = 1;
|
||||
entites->stages = builder->getAllocator()->allocate<Game::Stage>();
|
||||
entites->stages[0].name = "stage 0";
|
||||
entites->stages[0].triggerIndex = 0x400;
|
||||
entites->stages[0].sunPrimaryLightIndex = 0x1;
|
||||
}
|
||||
|
||||
std::string entityString = ents.getBuffer();
|
||||
|
||||
entites->name = builder->getAllocator()->duplicateString(std::format("maps/mp/{}.d3dbsp", name));
|
||||
entites->entityString = builder->getAllocator()->duplicateString(entityString);
|
||||
entites->numEntityChars = entityString.size() + 1;
|
||||
|
||||
header->mapEnts = entites;
|
||||
}
|
||||
header->mapEnts = builder->getIW4OfApi()->read<Game::MapEnts>(Game::XAssetType::ASSET_TYPE_MAP_ENTS, _name);
|
||||
}
|
||||
|
||||
void IMapEnts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -1,728 +1,18 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IMaterial.hpp"
|
||||
|
||||
#define IW4X_MAT_BIN_VERSION "1"
|
||||
#define IW4X_MAT_JSON_VERSION 1
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
const std::unordered_map<std::string, std::string> techSetCorrespondance =
|
||||
{
|
||||
{"effect", "effect_blend"},
|
||||
{"effect", "effect_blend"},
|
||||
{"effect_nofog", "effect_blend_nofog"},
|
||||
{"effect_zfeather", "effect_zfeather_blend"},
|
||||
{"effect_zfeather_falloff", "effect_zfeather_falloff_add"},
|
||||
{"effect_zfeather_nofog", "effect_zfeather_add_nofog"},
|
||||
|
||||
{"wc_unlit_add", "wc_unlit_add_lin"},
|
||||
{"wc_unlit_distfalloff", "wc_unlit_distfalloff_replace"},
|
||||
{"wc_unlit_multiply", "wc_unlit_multiply_lin"},
|
||||
{"wc_unlit_falloff_add", "wc_unlit_falloff_add_lin"},
|
||||
{"wc_unlit", "wc_unlit_replace_lin"},
|
||||
{"wc_unlit_alphatest", "wc_unlit_blend_lin"},
|
||||
{"wc_unlit_blend", "wc_unlit_blend_lin"},
|
||||
{"wc_unlit_replace", "wc_unlit_replace_lin"},
|
||||
{"wc_unlit_nofog", "wc_unlit_replace_lin_nofog_nocast" },
|
||||
|
||||
{"mc_unlit_replace", "mc_unlit_replace_lin"},
|
||||
{"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"},
|
||||
{"mc_unlit", "mc_unlit_replace_lin_nocast"},
|
||||
{"mc_unlit_alphatest", "mc_unlit_blend_lin"},
|
||||
{"mc_effect_nofog", "mc_effect_blend_nofog"},
|
||||
{"mc_effect_falloff_add_nofog", "mc_effect_falloff_add_nofog_eyeoffset"},
|
||||
};
|
||||
|
||||
void IMaterial::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
if (!header->data) this->loadJson(header, name, builder); // Check if we want to load a material from disk
|
||||
if (!header->data) this->loadBinary(header, name, builder); // Check if we want to load a material from disk (binary format)
|
||||
if (!header->data) this->loadFromDisk(header, name, builder); // Check if we want to load a material from disk
|
||||
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
|
||||
assert(header->data);
|
||||
}
|
||||
|
||||
|
||||
void IMaterial::loadJson(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
|
||||
void IMaterial::loadFromDisk(Game::XAssetHeader* header, const std::string& name, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File materialInfo(std::format("materials/{}.iw4x.json", name));
|
||||
|
||||
if (!materialInfo.exists()) return;
|
||||
|
||||
Game::Material* asset = builder->getAllocator()->allocate<Game::Material>();
|
||||
|
||||
|
||||
nlohmann::json materialJson;
|
||||
try
|
||||
{
|
||||
materialJson = nlohmann::json::parse(materialInfo.getBuffer());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Components::Logger::Print("Invalid material json for {} (broken json {})\n", name, e.what());
|
||||
}
|
||||
|
||||
if (!materialJson.is_object())
|
||||
{
|
||||
Components::Logger::Print("Invalid material json for {} (Is it zonebuilder format?)\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (materialJson["version"].get<int>() != IW4X_MAT_JSON_VERSION)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid material json version for {}, expected {} and got {}\n", name, IW4X_MAT_JSON_VERSION, materialJson["version"].get<std::string>());
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
asset->info.name = builder->getAllocator()->duplicateString(materialJson["name"].get<std::string>());
|
||||
asset->info.gameFlags = static_cast<char>(Utils::Json::ReadFlags(materialJson["gameFlags"].get<std::string>(), sizeof(char)));
|
||||
|
||||
asset->info.sortKey = materialJson["sortKey"].get<char>();
|
||||
// * We do techset later * //
|
||||
asset->info.textureAtlasRowCount = materialJson["textureAtlasRowCount"].get<unsigned char>();
|
||||
asset->info.textureAtlasColumnCount = materialJson["textureAtlasColumnCount"].get<unsigned char>();
|
||||
asset->info.surfaceTypeBits = static_cast<unsigned int>(Utils::Json::ReadFlags(materialJson["surfaceTypeBits"].get<std::string>(), sizeof(int)));
|
||||
asset->info.hashIndex = materialJson["hashIndex"].get<unsigned short>();
|
||||
asset->cameraRegion = materialJson["cameraRegion"].get<char>();
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid material json for {} (broken json {})\n", name, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (materialJson["gfxDrawSurface"].is_object())
|
||||
{
|
||||
asset->info.drawSurf.fields.customIndex = materialJson["gfxDrawSurface"]["customIndex"].get<long long>();
|
||||
asset->info.drawSurf.fields.hasGfxEntIndex = materialJson["gfxDrawSurface"]["hasGfxEntIndex"].get<long long>();
|
||||
asset->info.drawSurf.fields.materialSortedIndex = materialJson["gfxDrawSurface"]["materialSortedIndex"].get<long long>();
|
||||
asset->info.drawSurf.fields.objectId = materialJson["gfxDrawSurface"]["objectId"].get<long long>();
|
||||
asset->info.drawSurf.fields.prepass = materialJson["gfxDrawSurface"]["prepass"].get<long long>();
|
||||
asset->info.drawSurf.fields.primarySortKey = materialJson["gfxDrawSurface"]["primarySortKey"].get<long long>();
|
||||
asset->info.drawSurf.fields.reflectionProbeIndex = materialJson["gfxDrawSurface"]["reflectionProbeIndex"].get<long long>();
|
||||
asset->info.drawSurf.fields.sceneLightIndex = materialJson["gfxDrawSurface"]["sceneLightIndex"].get<long long>();
|
||||
asset->info.drawSurf.fields.surfType = materialJson["gfxDrawSurface"]["surfType"].get<long long>();
|
||||
asset->info.drawSurf.fields.unused = materialJson["gfxDrawSurface"]["unused"].get<long long>();
|
||||
asset->info.drawSurf.fields.useHeroLighting = materialJson["gfxDrawSurface"]["useHeroLighting"].get<long long>();
|
||||
}
|
||||
|
||||
asset->stateFlags = static_cast<char>(Utils::Json::ReadFlags(materialJson["stateFlags"].get<std::string>(), sizeof(char)));
|
||||
|
||||
if (materialJson["textureTable"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t textureTable = materialJson["textureTable"];
|
||||
asset->textureCount = static_cast<unsigned char>(textureTable.size());
|
||||
asset->textureTable = builder->getAllocator()->allocateArray<Game::MaterialTextureDef>(asset->textureCount);
|
||||
|
||||
for (size_t i = 0; i < textureTable.size(); i++)
|
||||
{
|
||||
auto& textureJson = textureTable[i];
|
||||
if (textureJson.is_object())
|
||||
{
|
||||
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
|
||||
textureDef->semantic = textureJson["semantic"].get<Game::TextureSemantic>();
|
||||
textureDef->samplerState = textureJson["samplerState"].get<char>();
|
||||
textureDef->nameStart = textureJson["nameStart"].get<char>();
|
||||
textureDef->nameEnd = textureJson["nameEnd"].get<char>();
|
||||
textureDef->nameHash = textureJson["nameHash"].get<unsigned int>();
|
||||
|
||||
if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
|
||||
{
|
||||
Game::water_t* water = builder->getAllocator()->allocate<Game::water_t>();
|
||||
|
||||
if (textureJson["water"].is_object())
|
||||
{
|
||||
auto& waterJson = textureJson["water"];
|
||||
|
||||
if (waterJson["image"].is_string())
|
||||
{
|
||||
auto imageName = waterJson["image"].get<std::string>();
|
||||
|
||||
water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, imageName.data(), builder).image;
|
||||
}
|
||||
|
||||
water->amplitude = waterJson["amplitude"].get<float>();
|
||||
water->M = waterJson["M"].get<int>();
|
||||
water->N = waterJson["N"].get<int>();
|
||||
water->Lx = waterJson["Lx"].get<float>();
|
||||
water->Lz = waterJson["Lz"].get<float>();
|
||||
water->gravity = waterJson["gravity"].get<float>();
|
||||
water->windvel = waterJson["windvel"].get<float>();
|
||||
|
||||
auto winddir = waterJson["winddir"].get<std::vector<float>>();
|
||||
if (winddir.size() == 2)
|
||||
{
|
||||
std::copy(winddir.begin(), winddir.end(), water->winddir);
|
||||
}
|
||||
|
||||
auto codeConstant = waterJson["codeConstant"].get<std::vector<float>>();
|
||||
|
||||
if (codeConstant.size() == 4)
|
||||
{
|
||||
std::copy(codeConstant.begin(), codeConstant.end(), water->codeConstant);
|
||||
}
|
||||
|
||||
/// H0
|
||||
[[maybe_unused]] auto idealSize = water->M * water->N * sizeof(Game::complex_s);
|
||||
auto h064 = waterJson["H0"].get<std::string>();
|
||||
auto predictedSize = static_cast<unsigned long>(std::ceilf((h064.size() / 4.f) * 3.f));
|
||||
assert(predictedSize >= idealSize);
|
||||
|
||||
auto h0 = reinterpret_cast<Game::complex_s*>(builder->getAllocator()->allocate(predictedSize));
|
||||
|
||||
[[maybe_unused]] auto h0Result = base64_decode(
|
||||
h064.data(),
|
||||
h064.size(),
|
||||
reinterpret_cast<unsigned char*>(h0),
|
||||
&predictedSize
|
||||
);
|
||||
|
||||
assert(h0Result == CRYPT_OK);
|
||||
water->H0 = h0;
|
||||
|
||||
/// WTerm
|
||||
auto wTerm64 = waterJson["wTerm"].get<std::string>();
|
||||
auto predictedWTermSize = static_cast<unsigned long>(std::ceilf((wTerm64.size() / 4.f) * 3.f));
|
||||
|
||||
auto wTerm = reinterpret_cast<float*>(builder->getAllocator()->allocate(predictedWTermSize));
|
||||
|
||||
[[maybe_unused]] auto wTermResult = base64_decode(
|
||||
wTerm64.data(),
|
||||
wTerm64.size(),
|
||||
reinterpret_cast<unsigned char*>(wTerm),
|
||||
&predictedWTermSize
|
||||
);
|
||||
|
||||
assert(wTermResult == CRYPT_OK);
|
||||
water->wTerm = wTerm;
|
||||
}
|
||||
|
||||
textureDef->u.water = water;
|
||||
}
|
||||
else
|
||||
{
|
||||
textureDef->u.image = nullptr;
|
||||
if (textureJson["image"].is_string())
|
||||
{
|
||||
textureDef->u.image = Components::AssetHandler::FindAssetForZone
|
||||
(
|
||||
Game::XAssetType::ASSET_TYPE_IMAGE,
|
||||
textureJson["image"].get<std::string>(),
|
||||
builder
|
||||
).image;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Statebits
|
||||
if (materialJson["stateBitsEntry"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t stateBitsEntry = materialJson["stateBitsEntry"];
|
||||
|
||||
for (size_t i = 0; i < std::min(stateBitsEntry.size(), 48u); i++)
|
||||
{
|
||||
asset->stateBitsEntry[i] = stateBitsEntry[i].get<char>();
|
||||
}
|
||||
}
|
||||
|
||||
if (materialJson["stateBitsTable"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t array = materialJson["stateBitsTable"];
|
||||
asset->stateBitsCount = static_cast<unsigned char>(array.size());
|
||||
|
||||
asset->stateBitsTable = builder->getAllocator()->allocateArray<Game::GfxStateBits>(array.size());
|
||||
|
||||
size_t statebitTableIndex = 0;
|
||||
for (auto& jsonStateBitEntry : array)
|
||||
{
|
||||
auto stateBit = &asset->stateBitsTable[statebitTableIndex++];
|
||||
|
||||
unsigned int loadbits0 = 0;
|
||||
unsigned int loadbits1 = 0;
|
||||
|
||||
#define READ_INT_LB_FROM_JSON(x) unsigned int x = jsonStateBitEntry[#x].get<unsigned int>()
|
||||
#define READ_BOOL_LB_FROM_JSON(x) bool x = jsonStateBitEntry[#x].get<bool>()
|
||||
|
||||
READ_INT_LB_FROM_JSON(srcBlendRgb);
|
||||
READ_INT_LB_FROM_JSON(dstBlendRgb);
|
||||
READ_INT_LB_FROM_JSON(blendOpRgb);
|
||||
READ_INT_LB_FROM_JSON(srcBlendAlpha);
|
||||
READ_INT_LB_FROM_JSON(dstBlendAlpha);
|
||||
READ_INT_LB_FROM_JSON(blendOpAlpha);
|
||||
READ_INT_LB_FROM_JSON(depthTest);
|
||||
READ_INT_LB_FROM_JSON(polygonOffset);
|
||||
|
||||
const auto alphaTest = jsonStateBitEntry["alphaTest"].get<std::string>();
|
||||
const auto cullFace = jsonStateBitEntry["cullFace"].get<std::string>();
|
||||
|
||||
READ_BOOL_LB_FROM_JSON(colorWriteRgb);
|
||||
READ_BOOL_LB_FROM_JSON(colorWriteAlpha);
|
||||
READ_BOOL_LB_FROM_JSON(polymodeLine);
|
||||
|
||||
READ_BOOL_LB_FROM_JSON(gammaWrite);
|
||||
READ_BOOL_LB_FROM_JSON(depthWrite);
|
||||
READ_BOOL_LB_FROM_JSON(stencilFrontEnabled);
|
||||
READ_BOOL_LB_FROM_JSON(stencilBackEnabled);
|
||||
|
||||
READ_INT_LB_FROM_JSON(stencilFrontPass);
|
||||
READ_INT_LB_FROM_JSON(stencilFrontFail);
|
||||
READ_INT_LB_FROM_JSON(stencilFrontZFail);
|
||||
READ_INT_LB_FROM_JSON(stencilFrontFunc);
|
||||
READ_INT_LB_FROM_JSON(stencilBackPass);
|
||||
READ_INT_LB_FROM_JSON(stencilBackFail);
|
||||
READ_INT_LB_FROM_JSON(stencilBackZFail);
|
||||
READ_INT_LB_FROM_JSON(stencilBackFunc);
|
||||
|
||||
loadbits0 |= srcBlendRgb << Game::GFXS0_SRCBLEND_RGB_SHIFT;
|
||||
loadbits0 |= dstBlendRgb << Game::GFXS0_DSTBLEND_RGB_SHIFT;
|
||||
loadbits0 |= blendOpRgb << Game::GFXS0_BLENDOP_RGB_SHIFT;
|
||||
loadbits0 |= srcBlendAlpha << Game::GFXS0_SRCBLEND_ALPHA_SHIFT;
|
||||
loadbits0 |= dstBlendAlpha << Game::GFXS0_DSTBLEND_ALPHA_SHIFT;
|
||||
loadbits0 |= blendOpAlpha << Game::GFXS0_BLENDOP_ALPHA_SHIFT;
|
||||
|
||||
if (depthTest == -1)
|
||||
{
|
||||
loadbits1 |= Game::GFXS1_DEPTHTEST_DISABLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadbits1 |= depthTest << Game::GFXS1_DEPTHTEST_SHIFT;
|
||||
}
|
||||
|
||||
loadbits1 |= polygonOffset << Game::GFXS1_POLYGON_OFFSET_SHIFT;
|
||||
|
||||
if (alphaTest == "disable")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_ATEST_DISABLE;
|
||||
}
|
||||
else if (alphaTest == ">0")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_ATEST_GT_0;
|
||||
}
|
||||
else if (alphaTest == "<128")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_ATEST_LT_128;
|
||||
}
|
||||
else if (alphaTest == ">=128")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_ATEST_GE_128;
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid alphatest loadbit0 '{}' in material {}\n", alphaTest, name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cullFace == "none")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_CULL_NONE;
|
||||
}
|
||||
else if (cullFace == "back")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_CULL_BACK;
|
||||
}
|
||||
else if (cullFace == "front")
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_CULL_FRONT;
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Invalid cullFace loadbit0 '{}' in material {}\n", cullFace, name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gammaWrite)
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_GAMMAWRITE;
|
||||
}
|
||||
|
||||
if (colorWriteAlpha)
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_COLORWRITE_ALPHA;
|
||||
}
|
||||
|
||||
if (colorWriteRgb)
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_COLORWRITE_RGB;
|
||||
}
|
||||
|
||||
if (polymodeLine)
|
||||
{
|
||||
loadbits0 |= Game::GFXS0_POLYMODE_LINE;
|
||||
}
|
||||
|
||||
if (depthWrite)
|
||||
{
|
||||
loadbits1 |= Game::GFXS1_DEPTHWRITE;
|
||||
}
|
||||
if (stencilFrontEnabled)
|
||||
{
|
||||
loadbits1 |= Game::GFXS1_STENCIL_FRONT_ENABLE;
|
||||
}
|
||||
if (stencilBackEnabled)
|
||||
{
|
||||
loadbits1 |= Game::GFXS1_STENCIL_BACK_ENABLE;
|
||||
}
|
||||
|
||||
loadbits1 |= stencilFrontPass << Game::GFXS1_STENCIL_FRONT_PASS_SHIFT;
|
||||
loadbits1 |= stencilFrontFail << Game::GFXS1_STENCIL_FRONT_FAIL_SHIFT;
|
||||
loadbits1 |= stencilFrontZFail << Game::GFXS1_STENCIL_FRONT_ZFAIL_SHIFT;
|
||||
loadbits1 |= stencilFrontFunc << Game::GFXS1_STENCIL_FRONT_FUNC_SHIFT;
|
||||
loadbits1 |= stencilBackPass << Game::GFXS1_STENCIL_BACK_PASS_SHIFT;
|
||||
loadbits1 |= stencilBackFail << Game::GFXS1_STENCIL_BACK_FAIL_SHIFT;
|
||||
loadbits1 |= stencilBackZFail << Game::GFXS1_STENCIL_BACK_ZFAIL_SHIFT;
|
||||
loadbits1 |= stencilBackFunc << Game::GFXS1_STENCIL_BACK_FUNC_SHIFT;
|
||||
|
||||
stateBit->loadBits[0] = loadbits0;
|
||||
stateBit->loadBits[1] = loadbits1;
|
||||
}
|
||||
|
||||
// Constant table
|
||||
if (materialJson["constantTable"].is_array())
|
||||
{
|
||||
|
||||
nlohmann::json::array_t constants = materialJson["constantTable"];
|
||||
asset->constantCount = static_cast<char>(constants.size());
|
||||
auto table = builder->getAllocator()->allocateArray<Game::MaterialConstantDef>(asset->constantCount);
|
||||
|
||||
for (size_t constantIndex = 0; constantIndex < asset->constantCount; constantIndex++)
|
||||
{
|
||||
auto& constant = constants[constantIndex];
|
||||
auto entry = &table[constantIndex];
|
||||
|
||||
auto litVec = constant["literal"].get<std::vector<float>>();
|
||||
std::copy(litVec.begin(), litVec.end(), entry->literal);
|
||||
|
||||
auto constantName = constant["name"].get<std::string>();
|
||||
std::copy(constantName.begin(), constantName.end(), entry->name);
|
||||
|
||||
entry->nameHash = constant["nameHash"].get<unsigned int>();
|
||||
}
|
||||
|
||||
asset->constantTable = table;
|
||||
}
|
||||
|
||||
if (materialJson["techniqueSet"].is_string())
|
||||
{
|
||||
const std::string techsetName = materialJson["techniqueSet"].get<std::string>();
|
||||
asset->techniqueSet = findWorkingTechset(techsetName, asset, builder);
|
||||
|
||||
if (asset->techniqueSet == nullptr)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
header->material = asset;
|
||||
}
|
||||
}
|
||||
|
||||
Game::MaterialTechniqueSet* IMaterial::findWorkingTechset(std::string techsetName, [[maybe_unused]] Game::Material* material, Components::ZoneBuilder::Zone* builder) const
|
||||
{
|
||||
Game::MaterialTechniqueSet* techset;
|
||||
|
||||
// Pass 1: Identical techset (1:1)
|
||||
techset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName.data(), builder).techniqueSet;
|
||||
if (techset != nullptr)
|
||||
{
|
||||
return techset;
|
||||
}
|
||||
|
||||
// We do no more cause we use CoD4 techset and they should always be present
|
||||
// If one day we want to go back to mw2 fallback we can add extra steps here!
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void IMaterial::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
static const char* techsetSuffix[] =
|
||||
{
|
||||
"_lin",
|
||||
"_add_lin",
|
||||
"_replace",
|
||||
"_eyeoffset",
|
||||
|
||||
"_blend",
|
||||
"_blend_nofog",
|
||||
"_add",
|
||||
"_nofog",
|
||||
"_nocast",
|
||||
|
||||
"_add_lin_nofog",
|
||||
};
|
||||
|
||||
Components::FileSystem::File materialFile(std::format("materials/{}.iw4xMaterial", name));
|
||||
if (!materialFile.exists()) return;
|
||||
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer());
|
||||
|
||||
char* magic = reader.readArray<char>(7);
|
||||
if (std::memcmp(magic, "IW4xMat", 7) != 0)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
std::string version;
|
||||
version.push_back(reader.read<char>());
|
||||
if (version != IW4X_MAT_BIN_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, expected version is {}, but it was {}!", name, IW4X_MAT_BIN_VERSION, version);
|
||||
}
|
||||
|
||||
auto* asset = reader.readObject<Game::Material>();
|
||||
|
||||
if (asset->info.name)
|
||||
{
|
||||
asset->info.name = reader.readCString();
|
||||
}
|
||||
|
||||
if (asset->techniqueSet)
|
||||
{
|
||||
std::string techsetName = reader.readString();
|
||||
if (!techsetName.empty() && techsetName.front() == ',') techsetName.erase(techsetName.begin());
|
||||
asset->techniqueSet = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName.data(), builder).techniqueSet;
|
||||
|
||||
if (!asset->techniqueSet)
|
||||
{
|
||||
// Workaround for effect techsets having _nofog suffix
|
||||
std::string suffix;
|
||||
if (Utils::String::StartsWith(techsetName, "effect_") && Utils::String::EndsWith(techsetName, "_nofog"))
|
||||
{
|
||||
suffix = "_nofog";
|
||||
Utils::String::Replace(techsetName, suffix, "");
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(techsetSuffix); ++i)
|
||||
{
|
||||
Game::MaterialTechniqueSet* techsetPtr = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, (techsetName + techsetSuffix[i] + suffix).data(), builder).techniqueSet;
|
||||
|
||||
if (techsetPtr)
|
||||
{
|
||||
asset->techniqueSet = techsetPtr;
|
||||
|
||||
if (asset->techniqueSet->name[0] == ',') continue; // Try to find a better one
|
||||
Components::Logger::Print("Techset '{}' has been mapped to '{}'\n", techsetName, asset->techniqueSet->name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::Print("Techset {} exists with the same name in iw4, and was mapped 1:1 with {}\n", techsetName, asset->techniqueSet->name);
|
||||
}
|
||||
|
||||
if (!asset->techniqueSet)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Missing techset: '{}' not found", techsetName);
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->textureTable)
|
||||
{
|
||||
asset->textureTable = reader.readArray<Game::MaterialTextureDef>(asset->textureCount);
|
||||
|
||||
for (char i = 0; i < asset->textureCount; ++i)
|
||||
{
|
||||
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
|
||||
|
||||
if (textureDef->semantic == Game::TextureSemantic::TS_WATER_MAP)
|
||||
{
|
||||
if (textureDef->u.water)
|
||||
{
|
||||
Game::water_t* water = reader.readObject<Game::water_t>();
|
||||
textureDef->u.water = water;
|
||||
|
||||
// Save_water_t
|
||||
if (water->H0)
|
||||
{
|
||||
water->H0 = reader.readArray<Game::complex_s>(water->M * water->N);
|
||||
}
|
||||
|
||||
if (water->wTerm)
|
||||
{
|
||||
water->wTerm = reader.readArray<float>(water->M * water->N);
|
||||
}
|
||||
|
||||
if (water->image)
|
||||
{
|
||||
water->image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (textureDef->u.image)
|
||||
{
|
||||
textureDef->u.image = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_IMAGE, reader.readString().data(), builder).image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->constantTable)
|
||||
{
|
||||
asset->constantTable = reader.readArray<Game::MaterialConstantDef>(asset->constantCount);
|
||||
}
|
||||
|
||||
if (asset->stateBitsTable)
|
||||
{
|
||||
asset->stateBitsTable = reader.readArray<Game::GfxStateBits>(asset->stateBitsCount);
|
||||
}
|
||||
|
||||
header->material = asset;
|
||||
|
||||
static thread_local bool replacementFound;
|
||||
replacementFound = false;
|
||||
|
||||
// Find correct sortkey by comparing techsets
|
||||
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset](Game::XAssetEntry* entry)
|
||||
{
|
||||
if (!replacementFound)
|
||||
{
|
||||
Game::XAssetHeader header = entry->asset.header;
|
||||
|
||||
const char* name = asset->techniqueSet->name;
|
||||
if (name[0] == ',') ++name;
|
||||
|
||||
if (std::string(name) == header.material->techniqueSet->name)
|
||||
{
|
||||
asset->info.sortKey = header.material->info.sortKey;
|
||||
|
||||
// This is temp, as nobody has time to fix materials
|
||||
asset->stateBitsCount = header.material->stateBitsCount;
|
||||
asset->stateBitsTable = header.material->stateBitsTable;
|
||||
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, ARRAYSIZE(asset->stateBitsEntry));
|
||||
asset->constantCount = header.material->constantCount;
|
||||
asset->constantTable = header.material->constantTable;
|
||||
Components::Logger::Print("For {}, copied constants & statebits from {}\n", asset->info.name, header.material->info.name);
|
||||
replacementFound = true;
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
if (!replacementFound)
|
||||
{
|
||||
auto techsetMatches = [](Game::Material* m1, Game::Material* m2)
|
||||
{
|
||||
Game::MaterialTechniqueSet* t1 = m1->techniqueSet;
|
||||
Game::MaterialTechniqueSet* t2 = m2->techniqueSet;
|
||||
if (!t1 || !t2) return false;
|
||||
if (t1->remappedTechniqueSet && t2->remappedTechniqueSet && std::string(t1->remappedTechniqueSet->name) == t2->remappedTechniqueSet->name) return true;
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(t1->techniques); ++i)
|
||||
{
|
||||
if (!t1->techniques[i] && !t2->techniques[i]) continue;;
|
||||
if (!t1->techniques[i] || !t2->techniques[i]) return false;
|
||||
|
||||
// Apparently, this is really not that important
|
||||
//if (t1->techniques[i]->flags != t2->techniques[i]->flags) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, techsetMatches](Game::XAssetEntry* entry)
|
||||
{
|
||||
if (!replacementFound)
|
||||
{
|
||||
Game::XAssetHeader header = entry->asset.header;
|
||||
|
||||
if (techsetMatches(header.material, asset))
|
||||
{
|
||||
Components::Logger::Print("Material {} with techset {} has been mapped to {}\n", asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name);
|
||||
asset->info.sortKey = header.material->info.sortKey;
|
||||
replacementFound = true;
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
if (!replacementFound && asset->techniqueSet)
|
||||
{
|
||||
Components::Logger::Print("No replacement found for material {} with techset {}\n", asset->info.name, asset->techniqueSet->name);
|
||||
std::string techName = asset->techniqueSet->name;
|
||||
if (const auto itr = techSetCorrespondance.find(techName); itr != techSetCorrespondance.end())
|
||||
{
|
||||
auto& iw4TechSetName = itr->second;
|
||||
auto* iw4TechSet = Game::DB_FindXAssetEntry(Game::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data());
|
||||
|
||||
if (iw4TechSet)
|
||||
{
|
||||
Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, iw4TechSet](Game::XAssetEntry* entry)
|
||||
{
|
||||
if (!replacementFound)
|
||||
{
|
||||
Game::XAssetHeader header = entry->asset.header;
|
||||
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
|
||||
asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
|
||||
|
||||
// Yeah this has a tendency to fuck up a LOT of transparent materials
|
||||
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet && std::string(header.material->info.name).find("icon") != std::string::npos)
|
||||
{
|
||||
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
|
||||
asset->info.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
|
||||
|
||||
asset->info.sortKey = header.material->info.sortKey;
|
||||
asset->techniqueSet = iw4TechSet->asset.header.techniqueSet;
|
||||
|
||||
// this is terrible!
|
||||
asset->stateBitsCount = header.material->stateBitsCount;
|
||||
asset->stateBitsTable = header.material->stateBitsTable;
|
||||
std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48);
|
||||
asset->constantCount = header.material->constantCount;
|
||||
asset->constantTable = header.material->constantTable;
|
||||
|
||||
replacementFound = true;
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
if (!replacementFound)
|
||||
{
|
||||
Components::Logger::Print("Could not find any loaded material with techset {} (in replacement of {}), so I cannot set the sortkey for material {}\n", iw4TechSetName, asset->techniqueSet->name, asset->info.name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::Print("Could not find any loaded techset with iw4 name {} for iw3 techset {}\n", iw4TechSetName, asset->techniqueSet->name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::Print("Could not match iw3 techset {} with any of the techsets I know! This is a critical error, there's a good chance the map will not be playable.\n", techName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader.end())
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Material data left!");
|
||||
}
|
||||
|
||||
/*char baseIndex = 0;
|
||||
for (char i = 0; i < asset->stateBitsCount; ++i)
|
||||
{
|
||||
auto stateBits = asset->stateBitsTable[i];
|
||||
if (stateBits.loadBits[0] == 0x18128812 &&
|
||||
stateBits.loadBits[1] == 0xD) // Seems to be like a default stateBit causing a 'generic' initialization
|
||||
{
|
||||
baseIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 48; ++i)
|
||||
{
|
||||
if (!asset->techniqueSet->techniques[i] && asset->stateBitsEntry[i] != -1)
|
||||
{
|
||||
asset->stateBitsEntry[i] = -1;
|
||||
}
|
||||
|
||||
if (asset->techniqueSet->techniques[i] && asset->stateBitsEntry[i] == -1)
|
||||
{
|
||||
asset->stateBitsEntry[i] = baseIndex;
|
||||
}
|
||||
}*/
|
||||
header->material = builder->getIW4OfApi()->read<Game::Material>(Game::XAssetType::ASSET_TYPE_MATERIAL, name);
|
||||
}
|
||||
|
||||
void IMaterial::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
|
||||
|
@ -10,11 +10,7 @@ namespace Assets
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||
void loadJson(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
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);
|
||||
|
||||
private:
|
||||
Game::MaterialTechniqueSet* findWorkingTechset(const std::string techsetName, Game::Material* material, Components::ZoneBuilder::Zone* builder) const;
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IMaterialPixelShader.hpp"
|
||||
|
||||
#define GFX_RENDERER_SHADER_SM3 0
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
|
||||
@ -19,21 +17,7 @@ namespace Assets
|
||||
|
||||
void IMaterialPixelShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File psFile(std::format("ps/{}.cso", name));
|
||||
if (!psFile.exists()) return;
|
||||
|
||||
auto buff = psFile.getBuffer();
|
||||
auto programSize = buff.size() / 4;
|
||||
Game::MaterialPixelShader* asset = builder->getAllocator()->allocate<Game::MaterialPixelShader>();
|
||||
|
||||
asset->name = builder->getAllocator()->duplicateString(name);
|
||||
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
|
||||
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
|
||||
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
|
||||
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
|
||||
|
||||
|
||||
header->pixelShader = asset;
|
||||
header->pixelShader = builder->getIW4OfApi()->read<Game::MaterialPixelShader>(Game::XAssetType::ASSET_TYPE_PIXELSHADER, name);
|
||||
}
|
||||
|
||||
void IMaterialPixelShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IMaterialTechniqueSet.hpp"
|
||||
|
||||
#include <Utils/Json.hpp>
|
||||
|
||||
#define IW4X_TECHSET_VERSION 1
|
||||
|
||||
namespace Assets
|
||||
@ -9,6 +11,11 @@ namespace Assets
|
||||
{
|
||||
if (!header->data) this->loadFromDisk(header, name, builder); // Check if we need to import a new one into the game
|
||||
if (!header->data) this->loadNative(header, name, builder); // Check if there is a native one
|
||||
|
||||
if (!header->data)
|
||||
{
|
||||
AssertUnreachable;
|
||||
}
|
||||
}
|
||||
|
||||
void IMaterialTechniqueSet::loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/)
|
||||
@ -16,200 +23,36 @@ namespace Assets
|
||||
header->techniqueSet = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).techniqueSet;
|
||||
}
|
||||
|
||||
void IMaterialTechniqueSet::loadTechniqueFromDisk(Game::MaterialTechnique** tech, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
AssertSize(Game::MaterialPass, 20);
|
||||
header->techniqueSet = builder->getIW4OfApi()->read<Game::MaterialTechniqueSet>(Game::ASSET_TYPE_TECHNIQUE_SET, name);
|
||||
|
||||
Components::FileSystem::File techFile(std::format("techniques/{}.iw4x.json", name));
|
||||
if (!techFile.exists())
|
||||
auto ptr = header->techniqueSet;
|
||||
if (ptr)
|
||||
{
|
||||
*tech = nullptr;
|
||||
|
||||
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Missing technique '{}'\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json technique;
|
||||
|
||||
try
|
||||
{
|
||||
technique = nlohmann::json::parse(techFile.getBuffer());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, file is messed up! {}", name, e.what());
|
||||
}
|
||||
|
||||
int version = technique["version"].get<int>();
|
||||
|
||||
if (version != IW4X_TECHSET_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL,
|
||||
"Reading technique '{}' failed, expected version is {}, but it was {}!", name, IW4X_TECHSET_VERSION, version);
|
||||
}
|
||||
|
||||
unsigned short flags = static_cast<unsigned short>(Utils::Json::ReadFlags(technique["flags"].get<std::string>(), sizeof(short)));
|
||||
|
||||
if (technique["passArray"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t passArray = technique["passArray"];
|
||||
|
||||
Game::MaterialTechnique* asset = (Game::MaterialTechnique*)builder->getAllocator()->allocateArray<unsigned char>(sizeof(Game::MaterialTechnique) + (sizeof(Game::MaterialPass) * (passArray.size() - 1)));
|
||||
|
||||
asset->name = builder->getAllocator()->duplicateString(name);
|
||||
asset->flags = flags;
|
||||
asset->passCount = static_cast<unsigned short>(passArray.size());
|
||||
|
||||
Game::MaterialPass* passes = builder->getAllocator()->allocateArray<Game::MaterialPass>(asset->passCount);
|
||||
std::memcpy(asset->passArray, passes, sizeof(Game::MaterialPass) * asset->passCount);
|
||||
|
||||
for (unsigned short i = 0; i < asset->passCount; i++)
|
||||
while (ptr->remappedTechniqueSet && ptr->remappedTechniqueSet != ptr)
|
||||
{
|
||||
Game::MaterialPass* pass = &asset->passArray[i];
|
||||
auto jsonPass = passArray[i];
|
||||
ptr = ptr->remappedTechniqueSet;
|
||||
builder->loadAsset(Game::ASSET_TYPE_TECHNIQUE_SET, ptr, false);
|
||||
|
||||
if (jsonPass["vertexDeclaration"].is_string())
|
||||
for (size_t i = 0; i < Game::TECHNIQUE_COUNT; i++)
|
||||
{
|
||||
auto declName = jsonPass["vertexDeclaration"].get<std::string>();
|
||||
pass->vertexDecl = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXDECL, declName, builder).vertexDecl;
|
||||
}
|
||||
|
||||
if (jsonPass["vertexShader"].is_string())
|
||||
{
|
||||
auto vsName = jsonPass["vertexShader"].get<std::string>();
|
||||
pass->vertexShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, vsName, builder).vertexShader;
|
||||
}
|
||||
|
||||
if (jsonPass["pixelShader"].is_string())
|
||||
{
|
||||
auto psName = jsonPass["pixelShader"].get<std::string>();
|
||||
pass->pixelShader = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PIXELSHADER, psName, builder).pixelShader;
|
||||
}
|
||||
|
||||
pass->perPrimArgCount = jsonPass["perPrimArgCount"].get<char>();
|
||||
pass->perObjArgCount = jsonPass["perObjArgCount"].get<char>();
|
||||
pass->stableArgCount = jsonPass["stableArgCount"].get<char>();
|
||||
pass->customSamplerFlags = jsonPass["customSamplerFlags"].get<char>();
|
||||
|
||||
|
||||
if (jsonPass["arguments"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t jsonAguments = jsonPass["arguments"];
|
||||
|
||||
pass->args = builder->getAllocator()->allocateArray<Game::MaterialShaderArgument>(jsonAguments.size());
|
||||
|
||||
for (size_t j = 0; j < jsonAguments.size(); j++)
|
||||
const auto technique = ptr->techniques[i];
|
||||
if (technique)
|
||||
{
|
||||
auto jsonArgument = jsonAguments[j];
|
||||
Game::MaterialShaderArgument* argument = &pass->args[j];
|
||||
|
||||
argument->type = jsonArgument["type"].get<Game::MaterialShaderArgumentType>();
|
||||
argument->dest = jsonArgument["dest"].get<unsigned short>();
|
||||
|
||||
if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_VERTEX_CONST ||
|
||||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_LITERAL_PIXEL_CONST)
|
||||
for (size_t j = 0; j < technique->passCount; j++)
|
||||
{
|
||||
argument->u.literalConst = builder->getAllocator()->allocateArray<float>(4);
|
||||
|
||||
auto literals = jsonArgument["literals"].get<std::vector<float>>();
|
||||
std::copy(literals.begin(), literals.end(), argument->u.literalConst);
|
||||
}
|
||||
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_VERTEX_CONST ||
|
||||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_CONST)
|
||||
{
|
||||
if (jsonArgument["codeConst"].is_object())
|
||||
{
|
||||
auto codeConst = jsonArgument["codeConst"];
|
||||
|
||||
argument->u.codeConst.index = codeConst["index"].get<unsigned short>();
|
||||
argument->u.codeConst.firstRow = codeConst["firstRow"].get<unsigned char>();
|
||||
argument->u.codeConst.rowCount = codeConst["rowCount"].get<unsigned char>();
|
||||
}
|
||||
}
|
||||
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_SAMPLER ||
|
||||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_VERTEX_CONST ||
|
||||
argument->type == Game::MaterialShaderArgumentType::MTL_ARG_MATERIAL_PIXEL_CONST)
|
||||
{
|
||||
argument->u.nameHash = jsonArgument["nameHash"].get<unsigned int>();
|
||||
}
|
||||
else if (argument->type == Game::MaterialShaderArgumentType::MTL_ARG_CODE_PIXEL_SAMPLER)
|
||||
{
|
||||
argument->u.codeSampler = jsonArgument["codeSampler"].get<unsigned int>();
|
||||
const auto pass = &technique->passArray[j];
|
||||
builder->loadAsset(Game::ASSET_TYPE_VERTEXDECL, pass->vertexDecl, true);
|
||||
builder->loadAsset(Game::ASSET_TYPE_PIXELSHADER, pass->pixelShader, true);
|
||||
builder->loadAsset(Game::ASSET_TYPE_VERTEXSHADER, pass->vertexShader, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*tech = asset;
|
||||
}
|
||||
}
|
||||
|
||||
void IMaterialTechniqueSet::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File tsFile(std::format("techsets/{}.iw4x.json", name));
|
||||
if (!tsFile.exists()) return;
|
||||
|
||||
nlohmann::json techset;
|
||||
|
||||
try
|
||||
{
|
||||
techset = nlohmann::json::parse(tsFile.getBuffer());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, file is messed up! {}", name, e.what());
|
||||
}
|
||||
|
||||
auto version = techset["version"].get<int>();
|
||||
if (version != IW4X_TECHSET_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, expected version is {}, but it was {}!",
|
||||
name, IW4X_TECHSET_VERSION, version);
|
||||
}
|
||||
|
||||
Game::MaterialTechniqueSet* asset = builder->getAllocator()->allocate<Game::MaterialTechniqueSet>();
|
||||
|
||||
if (asset == nullptr)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading techset '{}' failed, allocation failed!", name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (techset["name"].is_string())
|
||||
{
|
||||
asset->name = builder->getAllocator()->duplicateString(techset["name"].get<std::string>());
|
||||
}
|
||||
|
||||
asset->hasBeenUploaded = techset["hasBeenUploaded"].get<bool>();
|
||||
asset->worldVertFormat = techset["worldVertFormat"].get<char>();
|
||||
|
||||
|
||||
if (techset["remappedTechniqueSet"].is_string())
|
||||
{
|
||||
auto remapped = techset["remappedTechniqueSet"].get<std::string>();
|
||||
|
||||
if (remapped != asset->name)
|
||||
{
|
||||
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, remapped, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (techset["techniques"].is_object())
|
||||
{
|
||||
for (int i = 0; i < Game::TECHNIQUE_COUNT; i++)
|
||||
{
|
||||
auto technique = techset["techniques"].at(std::to_string(i));
|
||||
|
||||
if (technique.is_string())
|
||||
{
|
||||
this->loadTechniqueFromDisk(&asset->techniques[i], technique.get<std::string>(), builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header->techniqueSet = asset;
|
||||
}
|
||||
|
||||
void IMaterialTechniqueSet::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Game::MaterialTechniqueSet* asset = header.techniqueSet;
|
||||
|
@ -18,32 +18,7 @@ namespace Assets
|
||||
|
||||
void IMaterialVertexDeclaration::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File declFile(std::format("decl/{}.iw4xDECL", name));
|
||||
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(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
auto version = reader.read<char>();
|
||||
if (version != IW4X_TECHSET_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading vertex declaration '{}' failed, expected version is {}, but it was {:d}!",
|
||||
name, IW4X_TECHSET_VERSION, version);
|
||||
}
|
||||
|
||||
Game::MaterialVertexDeclaration* asset = reader.readObject<Game::MaterialVertexDeclaration>();
|
||||
|
||||
if (asset->name)
|
||||
{
|
||||
asset->name = reader.readCString();
|
||||
}
|
||||
|
||||
header->vertexDecl = asset;
|
||||
header->vertexDecl = builder->getIW4OfApi()->read<Game::MaterialVertexDeclaration>(Game::XAssetType::ASSET_TYPE_VERTEXDECL, name);
|
||||
}
|
||||
|
||||
void IMaterialVertexDeclaration::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -18,20 +18,7 @@ namespace Assets
|
||||
|
||||
void IMaterialVertexShader::loadBinary(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File vsFile(std::format("vs/{}.cso", name));
|
||||
if (!vsFile.exists()) return;
|
||||
|
||||
auto buff = vsFile.getBuffer();
|
||||
auto programSize = buff.size() / 4;
|
||||
Game::MaterialVertexShader* asset = builder->getAllocator()->allocate<Game::MaterialVertexShader>();
|
||||
|
||||
asset->name = builder->getAllocator()->duplicateString(name);
|
||||
asset->prog.loadDef.loadForRenderer = GFX_RENDERER_SHADER_SM3;
|
||||
asset->prog.loadDef.programSize = static_cast<unsigned short>(programSize);
|
||||
asset->prog.loadDef.program = builder->getAllocator()->allocateArray<unsigned int>(programSize);
|
||||
memcpy_s(asset->prog.loadDef.program, buff.size(), buff.data(), buff.size());
|
||||
|
||||
header->vertexShader = asset;
|
||||
header->vertexShader = builder->getIW4OfApi()->read<Game::MaterialVertexShader>(Game::XAssetType::ASSET_TYPE_VERTEXSHADER, name);
|
||||
}
|
||||
|
||||
void IMaterialVertexShader::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -7,9 +7,9 @@ namespace Assets
|
||||
{
|
||||
AssertSize(Game::PhysPreset, 44);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::PhysPreset* asset = header.physPreset;
|
||||
Game::PhysPreset* dest = buffer->dest<Game::PhysPreset>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.physPreset;
|
||||
auto* dest = buffer->dest<Game::PhysPreset>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
@ -28,4 +28,14 @@ namespace Assets
|
||||
|
||||
buffer->popBlock();
|
||||
}
|
||||
|
||||
void IPhysPreset::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
loadFromDisk(header, name, builder);
|
||||
}
|
||||
|
||||
void IPhysPreset::loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
header->physPreset = builder->getIW4OfApi()->read<Game::PhysPreset>(Game::XAssetType::ASSET_TYPE_PHYSPRESET, name);
|
||||
}
|
||||
}
|
||||
|
@ -8,5 +8,7 @@ namespace Assets
|
||||
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_PHYSPRESET; }
|
||||
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||
void loadFromDisk(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
};
|
||||
}
|
||||
|
@ -1,42 +1,13 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IRawFile.hpp"
|
||||
|
||||
#include <Utils/Compression.hpp>
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IRawFile::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File rawFile(name);
|
||||
|
||||
if (!rawFile.exists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* asset = builder->getAllocator()->allocate<Game::RawFile>();
|
||||
if (!asset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
asset->name = builder->getAllocator()->duplicateString(name);
|
||||
asset->len = static_cast<int>(rawFile.getBuffer().size());
|
||||
|
||||
const auto compressedData = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
|
||||
// Only save the compressed buffer if we gained space
|
||||
if (compressedData.size() < rawFile.getBuffer().size())
|
||||
{
|
||||
asset->buffer = builder->getAllocator()->allocateArray<char>(compressedData.size());
|
||||
std::memcpy(const_cast<char*>(asset->buffer), compressedData.data(), compressedData.size());
|
||||
asset->compressedLen = static_cast<int>(compressedData.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
asset->buffer = builder->getAllocator()->allocateArray<char>(rawFile.getBuffer().size() + 1);
|
||||
std::memcpy(const_cast<char*>(asset->buffer), rawFile.getBuffer().data(), rawFile.getBuffer().size());
|
||||
asset->compressedLen = 0;
|
||||
}
|
||||
|
||||
header->rawfile = asset;
|
||||
header->rawfile = builder->getIW4OfApi()->read<Game::RawFile>(Game::XAssetType::ASSET_TYPE_RAWFILE, name);
|
||||
}
|
||||
|
||||
void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -7,9 +7,9 @@ namespace Assets
|
||||
{
|
||||
AssertSize(Game::SndCurve, 136);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::SndCurve* asset = header.sndCurve;
|
||||
Game::SndCurve* dest = buffer->dest<Game::SndCurve>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.sndCurve;
|
||||
auto* dest = buffer->dest<Game::SndCurve>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
@ -22,4 +22,14 @@ namespace Assets
|
||||
|
||||
buffer->popBlock();
|
||||
}
|
||||
|
||||
void ISndCurve::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
header->sndCurve = builder->getIW4OfApi()->read<Game::SndCurve>(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name);
|
||||
|
||||
if (!header->sndCurve)
|
||||
{
|
||||
header->sndCurve = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name.data()).sndCurve;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,5 +8,6 @@ namespace Assets
|
||||
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_SOUND_CURVE; }
|
||||
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||
};
|
||||
}
|
||||
|
@ -1,108 +1,11 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IXAnimParts.hpp"
|
||||
|
||||
#define IW4X_ANIM_VERSION 1
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IXAnimParts::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File animFile(std::format("xanim/{}.iw4xAnim", name));
|
||||
|
||||
if (animFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), animFile.getBuffer());
|
||||
|
||||
__int64 magic = reader.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xAnim", 8))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
int version = reader.read<int>();
|
||||
if (version != IW4X_ANIM_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, expected version is {}, but it was {}!", name, IW4X_ANIM_VERSION, version);
|
||||
}
|
||||
|
||||
Game::XAnimParts* xanim = reader.readArray<Game::XAnimParts>();
|
||||
|
||||
if (xanim)
|
||||
{
|
||||
if (xanim->name)
|
||||
{
|
||||
xanim->name = reader.readCString();
|
||||
}
|
||||
|
||||
if (xanim->names)
|
||||
{
|
||||
xanim->names = builder->getAllocator()->allocateArray<unsigned short>(xanim->boneCount[Game::PART_TYPE_ALL]);
|
||||
for (int i = 0; i < xanim->boneCount[Game::PART_TYPE_ALL]; ++i)
|
||||
{
|
||||
xanim->names[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (xanim->notify)
|
||||
{
|
||||
xanim->notify = reader.readArray<Game::XAnimNotifyInfo>(xanim->notifyCount);
|
||||
|
||||
for (int i = 0; i < xanim->notifyCount; ++i)
|
||||
{
|
||||
xanim->notify[i].name = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (xanim->dataByte)
|
||||
{
|
||||
xanim->dataByte = reader.readArray<char>(xanim->dataByteCount);
|
||||
}
|
||||
|
||||
if (xanim->dataShort)
|
||||
{
|
||||
xanim->dataShort = reader.readArray<short>(xanim->dataShortCount);
|
||||
}
|
||||
|
||||
if (xanim->dataInt)
|
||||
{
|
||||
xanim->dataInt = reader.readArray<int>(xanim->dataIntCount);
|
||||
}
|
||||
|
||||
if (xanim->randomDataByte)
|
||||
{
|
||||
xanim->randomDataByte = reader.readArray<char>(xanim->randomDataByteCount);
|
||||
}
|
||||
|
||||
if (xanim->randomDataShort)
|
||||
{
|
||||
xanim->randomDataShort = reader.readArray<short>(xanim->randomDataShortCount);
|
||||
}
|
||||
|
||||
if (xanim->randomDataInt)
|
||||
{
|
||||
xanim->randomDataInt = reader.readArray<int>(xanim->randomDataIntCount);
|
||||
}
|
||||
|
||||
if (xanim->indices.data)
|
||||
{
|
||||
if (xanim->numframes < 256)
|
||||
{
|
||||
xanim->indices._1 = reader.readArray<char>(xanim->indexCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
xanim->indices._2 = reader.readArray<unsigned short>(xanim->indexCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader.end())
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading animation '{}' failed, remaining raw data found!", name);
|
||||
}
|
||||
|
||||
header->parts = xanim;
|
||||
}
|
||||
}
|
||||
header->parts = builder->getIW4OfApi()->read<Game::XAnimParts>(Game::XAssetType::ASSET_TYPE_XANIMPARTS, name);
|
||||
}
|
||||
|
||||
void IXAnimParts::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -1,298 +1,30 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IXModel.hpp"
|
||||
|
||||
#define IW4X_MODEL_VERSION 8
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IXModel::loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader)
|
||||
{
|
||||
if (entry->nodes)
|
||||
{
|
||||
entry->nodes = reader->readArrayOnce<Game::XSurfaceCollisionNode>(entry->nodeCount);
|
||||
}
|
||||
|
||||
if (entry->leafs)
|
||||
{
|
||||
entry->leafs = reader->readArrayOnce<Game::XSurfaceCollisionLeaf>(entry->leafCount);
|
||||
}
|
||||
}
|
||||
|
||||
void IXModel::loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, [[maybe_unused]] Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
if (surf->vertInfo.vertsBlend)
|
||||
{
|
||||
surf->vertInfo.vertsBlend = reader->readArrayOnce<unsigned short>(surf->vertInfo.vertCount[0] + (surf->vertInfo.vertCount[1] * 3) + (surf->vertInfo.vertCount[2] * 5) + (surf->vertInfo.vertCount[3] * 7));
|
||||
}
|
||||
|
||||
// Access vertex block
|
||||
if (surf->verts0)
|
||||
{
|
||||
surf->verts0 = reader->readArrayOnce<Game::GfxPackedVertex>(surf->vertCount);
|
||||
}
|
||||
|
||||
// Save_XRigidVertListArray
|
||||
if (surf->vertList)
|
||||
{
|
||||
surf->vertList = reader->readArrayOnce<Game::XRigidVertList>(surf->vertListCount);
|
||||
|
||||
for (unsigned int i = 0; i < surf->vertListCount; ++i)
|
||||
{
|
||||
Game::XRigidVertList* rigidVertList = &surf->vertList[i];
|
||||
|
||||
if (rigidVertList->collisionTree)
|
||||
{
|
||||
rigidVertList->collisionTree = reader->readObject<Game::XSurfaceCollisionTree>();
|
||||
this->loadXSurfaceCollisionTree(rigidVertList->collisionTree, reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Access index block
|
||||
if (surf->triIndices)
|
||||
{
|
||||
surf->triIndices = reader->readArrayOnce<unsigned short>(surf->triCount * 3);
|
||||
}
|
||||
}
|
||||
|
||||
void IXModel::loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
if (asset->name)
|
||||
{
|
||||
asset->name = reader->readCString();
|
||||
}
|
||||
|
||||
if (asset->surfs)
|
||||
{
|
||||
asset->surfs = reader->readArrayOnce<Game::XSurface>(asset->numsurfs);
|
||||
|
||||
for (int i = 0; i < asset->numsurfs; ++i)
|
||||
{
|
||||
this->loadXSurface(&asset->surfs[i], reader, builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File modelFile(std::format("xmodel/{}.iw4xModel", name));
|
||||
header->model = builder->getIW4OfApi()->read<Game::XModel>(Game::XAssetType::ASSET_TYPE_XMODEL, name);
|
||||
|
||||
if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::PreferDiskAssetsDvar.get<bool>() || !modelFile.exists()))
|
||||
if (header->model)
|
||||
{
|
||||
header->model = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).model;
|
||||
if (header->model) return;
|
||||
}
|
||||
|
||||
|
||||
if (modelFile.exists())
|
||||
{
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), modelFile.getBuffer());
|
||||
|
||||
__int64 magic = reader.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xModl", 8))
|
||||
// ???
|
||||
if (header->model->physCollmap)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, header is invalid!", name);
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { header->model->physCollmap });
|
||||
}
|
||||
|
||||
int version = reader.read<int>();
|
||||
if (version != IW4X_MODEL_VERSION)
|
||||
if (header->model->physPreset)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, expected version is {}, but it was {}!", name, IW4X_MODEL_VERSION, version);
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { header->model->physPreset });
|
||||
}
|
||||
|
||||
Game::XModel* asset = reader.readObject<Game::XModel>();
|
||||
|
||||
if (asset->name)
|
||||
for (size_t i = 0; i < header->model->numLods; i++)
|
||||
{
|
||||
asset->name = reader.readCString();
|
||||
const auto& info = header->model->lodInfo[i];
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { info.modelSurfs });
|
||||
}
|
||||
|
||||
if (asset->boneNames)
|
||||
{
|
||||
asset->boneNames = builder->getAllocator()->allocateArray<unsigned short>(asset->numBones);
|
||||
|
||||
for (char i = 0; i < asset->numBones; ++i)
|
||||
{
|
||||
asset->boneNames[i] = static_cast<std::uint16_t>(Game::SL_GetString(reader.readCString(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->parentList)
|
||||
{
|
||||
asset->parentList = reader.readArrayOnce<unsigned char>(asset->numBones - asset->numRootBones);
|
||||
}
|
||||
|
||||
if (asset->quats)
|
||||
{
|
||||
asset->quats = reader.readArrayOnce<short>((asset->numBones - asset->numRootBones) * 4);
|
||||
}
|
||||
|
||||
if (asset->trans)
|
||||
{
|
||||
asset->trans = reader.readArrayOnce<float>((asset->numBones - asset->numRootBones) * 3);
|
||||
}
|
||||
|
||||
if (asset->partClassification)
|
||||
{
|
||||
asset->partClassification = reader.readArrayOnce<unsigned char>(asset->numBones);
|
||||
}
|
||||
|
||||
if (asset->baseMat)
|
||||
{
|
||||
asset->baseMat = reader.readArrayOnce<Game::DObjAnimMat>(asset->numBones);
|
||||
}
|
||||
|
||||
if (asset->materialHandles)
|
||||
{
|
||||
asset->materialHandles = reader.readArray<Game::Material*>(asset->numsurfs);
|
||||
|
||||
for (unsigned char i = 0; i < asset->numsurfs; ++i)
|
||||
{
|
||||
if (asset->materialHandles[i])
|
||||
{
|
||||
asset->materialHandles[i] = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader.readString(), builder).material;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save_XModelLodInfoArray
|
||||
{
|
||||
for (unsigned int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (asset->lodInfo[i].modelSurfs)
|
||||
{
|
||||
asset->lodInfo[i].modelSurfs = reader.readObject<Game::XModelSurfs>();
|
||||
this->loadXModelSurfs(asset->lodInfo[i].modelSurfs, &reader, builder);
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_XMODEL_SURFS, { asset->lodInfo[i].modelSurfs });
|
||||
|
||||
asset->lodInfo[i].surfs = asset->lodInfo[i].modelSurfs->surfs;
|
||||
|
||||
// Zero that for now, it breaks the models.
|
||||
// TODO: Figure out how that can be converted
|
||||
asset->lodInfo[i].smcBaseIndexPlusOne = 0;
|
||||
asset->lodInfo[i].smcSubIndexMask = 0;
|
||||
asset->lodInfo[i].smcBucket = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save_XModelCollSurfArray
|
||||
if (asset->collSurfs)
|
||||
{
|
||||
asset->collSurfs = reader.readArray<Game::XModelCollSurf_s>(asset->numCollSurfs);
|
||||
|
||||
for (int i = 0; i < asset->numCollSurfs; ++i)
|
||||
{
|
||||
Game::XModelCollSurf_s* collSurf = &asset->collSurfs[i];
|
||||
|
||||
if (collSurf->collTris)
|
||||
{
|
||||
collSurf->collTris = reader.readArray<Game::XModelCollTri_s>(collSurf->numCollTris);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->boneInfo)
|
||||
{
|
||||
asset->boneInfo = reader.readArray<Game::XBoneInfo>(asset->numBones);
|
||||
}
|
||||
|
||||
if (asset->physPreset)
|
||||
{
|
||||
asset->physPreset = reader.readObject<Game::PhysPreset>();
|
||||
|
||||
if (asset->physPreset->name)
|
||||
{
|
||||
asset->physPreset->name = reader.readCString();
|
||||
}
|
||||
|
||||
if (asset->physPreset->sndAliasPrefix)
|
||||
{
|
||||
asset->physPreset->sndAliasPrefix = reader.readCString();
|
||||
}
|
||||
|
||||
// This is an experiment, ak74 fails though
|
||||
if (asset->name == "weapon_ak74u"s)
|
||||
{
|
||||
asset->physPreset = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
Game::PhysPreset* preset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, asset->physPreset->name, builder).physPreset;
|
||||
if (preset)
|
||||
{
|
||||
asset->physPreset = preset;
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSPRESET, { asset->physPreset });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->physCollmap)
|
||||
{
|
||||
Game::PhysCollmap* collmap = reader.readObject<Game::PhysCollmap>();
|
||||
asset->physCollmap = collmap;
|
||||
|
||||
if (collmap->name)
|
||||
{
|
||||
collmap->name = reader.readCString();
|
||||
}
|
||||
|
||||
if (collmap->geoms)
|
||||
{
|
||||
collmap->geoms = reader.readArray<Game::PhysGeomInfo>(collmap->count);
|
||||
|
||||
for (unsigned int i = 0; i < collmap->count; ++i)
|
||||
{
|
||||
Game::PhysGeomInfo* geom = &collmap->geoms[i];
|
||||
|
||||
if (geom->brushWrapper)
|
||||
{
|
||||
Game::BrushWrapper* brush = reader.readObject<Game::BrushWrapper>();
|
||||
geom->brushWrapper = brush;
|
||||
{
|
||||
if (brush->brush.sides)
|
||||
{
|
||||
brush->brush.sides = reader.readArray<Game::cbrushside_t>(brush->brush.numsides);
|
||||
for (unsigned short j = 0; j < brush->brush.numsides; ++j)
|
||||
{
|
||||
Game::cbrushside_t* side = &brush->brush.sides[j];
|
||||
|
||||
// TODO: Add pointer support
|
||||
if (side->plane)
|
||||
{
|
||||
side->plane = reader.readObject<Game::cplane_s>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (brush->brush.baseAdjacentSide)
|
||||
{
|
||||
brush->brush.baseAdjacentSide = reader.readArray<char>(brush->totalEdgeCount);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add pointer support
|
||||
if (brush->planes)
|
||||
{
|
||||
brush->planes = reader.readArray<Game::cplane_s>(brush->brush.numsides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::XAssetType::ASSET_TYPE_PHYSCOLLMAP, { asset->physCollmap });
|
||||
// asset->physCollmap = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader.end())
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading model '{}' failed, remaining raw data found!", name);
|
||||
}
|
||||
|
||||
header->model = asset;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,5 @@ namespace Assets
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||
|
||||
private:
|
||||
std::map<void*, void*> triIndicies;
|
||||
void loadXModelSurfs(Game::XModelSurfs* asset, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder);
|
||||
void loadXSurface(Game::XSurface* surf, Utils::Stream::Reader* reader, Components::ZoneBuilder::Zone* builder);
|
||||
void loadXSurfaceCollisionTree(Game::XSurfaceCollisionTree* entry, Utils::Stream::Reader* reader);
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "IclipMap_t.hpp"
|
||||
|
||||
#define IW4X_CLIPMAP_VERSION 2
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void IclipMap_t::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
@ -37,7 +35,7 @@ namespace Assets
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
|
||||
// not sure if this is needed but both brushside and brushedge need it and it can't hurt
|
||||
for (int i = 0; i < asset->planeCount; ++i)
|
||||
for (size_t i = 0; i < asset->planeCount; ++i)
|
||||
{
|
||||
builder->storePointer(&asset->planes[i]);
|
||||
buffer->save(&asset->planes[i]);
|
||||
@ -170,6 +168,11 @@ namespace Assets
|
||||
Utils::Stream::ClearPointer(&nodes[i].plane);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertUnreachable;
|
||||
Components::Logger::Error(Game::ERR_FATAL, "node {} of clipmap {} has no plane!", i, asset->name);
|
||||
}
|
||||
}
|
||||
|
||||
Utils::Stream::ClearPointer(&dest->nodes);
|
||||
@ -192,7 +195,13 @@ namespace Assets
|
||||
SaveLogEnter("cLeafBrush_t");
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_2);
|
||||
buffer->saveArray(asset->leafbrushes, asset->numLeafBrushes);
|
||||
|
||||
for (size_t i = 0; i < asset->numLeafBrushes; i++)
|
||||
{
|
||||
builder->storePointer(&asset->leafbrushes[i]);
|
||||
buffer->saveObject(asset->leafbrushes[i]);
|
||||
}
|
||||
|
||||
Utils::Stream::ClearPointer(&dest->leafbrushes);
|
||||
|
||||
SaveLogExit();
|
||||
@ -278,7 +287,7 @@ namespace Assets
|
||||
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
|
||||
for (int i = 0; i < asset->borderCount; ++i)
|
||||
for (size_t i = 0; i < asset->borderCount; ++i)
|
||||
{
|
||||
builder->storePointer(&asset->borders[i]);
|
||||
buffer->save(&asset->borders[i]);
|
||||
@ -564,381 +573,13 @@ namespace Assets
|
||||
}
|
||||
}
|
||||
}
|
||||
builder->loadAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, asset);
|
||||
|
||||
builder->loadAsset(Game::XAssetType::ASSET_TYPE_MAP_ENTS, asset->mapEnts);
|
||||
}
|
||||
|
||||
void IclipMap_t::load(Game::XAssetHeader* header, const std::string& _name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
std::string name = _name;
|
||||
Utils::String::Replace(name, "maps/mp/", "");
|
||||
Utils::String::Replace(name, ".d3dbsp", "");
|
||||
|
||||
Components::FileSystem::File clipFile(std::format("clipmap/{}.iw4xClipMap", name));
|
||||
if (!clipFile.exists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Game::clipMap_t* clipMap = builder->getAllocator()->allocate<Game::clipMap_t>();
|
||||
if (!clipMap)
|
||||
{
|
||||
Components::Logger::Print("Error allocating memory for clipMap_t structure!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Game::clipMap_t* orgClipMap = nullptr;
|
||||
Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, [](Game::XAssetHeader header, void* clipMap)
|
||||
{
|
||||
if (!*reinterpret_cast<void**>(clipMap))
|
||||
{
|
||||
*reinterpret_cast<Game::clipMap_t**>(clipMap) = header.clipMap;
|
||||
}
|
||||
}, &orgClipMap, false);
|
||||
|
||||
if (orgClipMap) std::memcpy(clipMap, orgClipMap, sizeof Game::clipMap_t);
|
||||
|
||||
Utils::Stream::Reader reader(builder->getAllocator(), clipFile.getBuffer());
|
||||
|
||||
__int64 magic = reader.read<__int64>();
|
||||
if (std::memcmp(&magic, "IW4xClip", 8))
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading clipMap_t '{}' failed, header is invalid!", name);
|
||||
}
|
||||
|
||||
int version = reader.read<int>();
|
||||
if (version > IW4X_CLIPMAP_VERSION)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Reading clipmap '{}' failed, expected version is {}, but it was {}!", name, IW4X_CLIPMAP_VERSION, version);
|
||||
}
|
||||
|
||||
clipMap->name = reader.readCString();
|
||||
|
||||
clipMap->planeCount = reader.read<int>();
|
||||
clipMap->numStaticModels = reader.read<int>();
|
||||
clipMap->numMaterials = reader.read<int>();
|
||||
clipMap->numBrushSides = reader.read<int>();
|
||||
clipMap->numBrushEdges = reader.read<int>();
|
||||
clipMap->numNodes = reader.read<int>();
|
||||
clipMap->numLeafs = reader.read<int>();
|
||||
clipMap->leafbrushNodesCount = reader.read<int>();
|
||||
clipMap->numLeafBrushes = reader.read<int>();
|
||||
clipMap->numLeafSurfaces = reader.read<int>();
|
||||
clipMap->vertCount = reader.read<int>();
|
||||
clipMap->triCount = reader.read<int>();
|
||||
clipMap->borderCount = reader.read<int>();
|
||||
clipMap->partitionCount = reader.read<int>();
|
||||
clipMap->aabbTreeCount = reader.read<int>();
|
||||
clipMap->numSubModels = reader.read<int>();
|
||||
clipMap->numBrushes = reader.read<short>();
|
||||
clipMap->dynEntCount[0] = reader.read<unsigned __int16>();
|
||||
clipMap->dynEntCount[1] = reader.read<unsigned __int16>();
|
||||
|
||||
if (clipMap->planeCount)
|
||||
{
|
||||
void* oldPtr = reader.read<void*>();
|
||||
clipMap->planes = reader.readArray<Game::cplane_s>(clipMap->planeCount);
|
||||
|
||||
if (builder->getAllocator()->isPointerMapped(oldPtr))
|
||||
{
|
||||
clipMap->planes = builder->getAllocator()->getPointer<Game::cplane_s>(oldPtr);
|
||||
Components::Logger::Print("ClipMap dpvs planes already mapped. This shouldn't happen. Make sure to load the ClipMap before the GfxWorld!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder->getAllocator()->mapPointer(oldPtr, clipMap->planes);
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->numStaticModels)
|
||||
{
|
||||
clipMap->staticModelList = builder->getAllocator()->allocateArray<Game::cStaticModel_s>(clipMap->numStaticModels);
|
||||
for (unsigned int i = 0; i < clipMap->numStaticModels; ++i)
|
||||
{
|
||||
std::string modelName = reader.readString();
|
||||
if (modelName != "NONE"s)
|
||||
{
|
||||
clipMap->staticModelList[i].xmodel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, modelName, builder).model;
|
||||
}
|
||||
float* buf = reader.readArray<float>(18);
|
||||
memcpy(&clipMap->staticModelList[i].origin, buf, sizeof(float) * 18);
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->numMaterials)
|
||||
{
|
||||
clipMap->materials = builder->getAllocator()->allocateArray<Game::ClipMaterial>(clipMap->numMaterials);
|
||||
for (unsigned int j = 0; j < clipMap->numMaterials; ++j)
|
||||
{
|
||||
clipMap->materials[j].name = reader.readArray<char>(64);
|
||||
clipMap->materials[j].surfaceFlags = reader.read<int>();
|
||||
clipMap->materials[j].contents = reader.read<int>();
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->numBrushSides)
|
||||
{
|
||||
clipMap->brushsides = builder->getAllocator()->allocateArray<Game::cbrushside_t>(clipMap->numBrushSides);
|
||||
for (unsigned int i = 0; i < clipMap->numBrushSides; ++i)
|
||||
{
|
||||
int planeIndex = reader.read<int>();
|
||||
if (planeIndex < 0 || planeIndex >= clipMap->planeCount)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "invalid plane index");
|
||||
return;
|
||||
}
|
||||
clipMap->brushsides[i].plane = &clipMap->planes[planeIndex];
|
||||
clipMap->brushsides[i].materialNum = static_cast<unsigned short>(reader.read<int>()); // materialNum
|
||||
clipMap->brushsides[i].firstAdjacentSideOffset = static_cast<char>(reader.read<short>()); // firstAdjacentSide
|
||||
clipMap->brushsides[i].edgeCount = reader.read<char>(); // edgeCount
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->numBrushEdges)
|
||||
{
|
||||
clipMap->brushEdges = reader.readArray<char>(clipMap->numBrushEdges);
|
||||
}
|
||||
|
||||
if (clipMap->numNodes)
|
||||
{
|
||||
clipMap->nodes = builder->getAllocator()->allocateArray<Game::cNode_t>(clipMap->numNodes);
|
||||
for (unsigned int i = 0; i < clipMap->numNodes; ++i)
|
||||
{
|
||||
int planeIndex = reader.read<int>();
|
||||
if (planeIndex < 0 || planeIndex >= clipMap->planeCount)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "invalid plane index\n");
|
||||
return;
|
||||
}
|
||||
clipMap->nodes[i].plane = &clipMap->planes[planeIndex];
|
||||
clipMap->nodes[i].children[0] = reader.read<short>();
|
||||
clipMap->nodes[i].children[1] = reader.read<short>();
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->numLeafs)
|
||||
{
|
||||
clipMap->leafs = reader.readArray<Game::cLeaf_t>(clipMap->numLeafs);
|
||||
}
|
||||
|
||||
if (clipMap->leafbrushNodesCount)
|
||||
{
|
||||
clipMap->leafbrushNodes = builder->getAllocator()->allocateArray<Game::cLeafBrushNode_s>(clipMap->leafbrushNodesCount);
|
||||
for (unsigned int i = 0; i < clipMap->leafbrushNodesCount; ++i)
|
||||
{
|
||||
clipMap->leafbrushNodes[i] = reader.read<Game::cLeafBrushNode_s>();
|
||||
|
||||
if (clipMap->leafbrushNodes[i].leafBrushCount > 0)
|
||||
{
|
||||
clipMap->leafbrushNodes[i].data.leaf.brushes = reader.readArray<unsigned short>(clipMap->leafbrushNodes[i].leafBrushCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->numLeafBrushes)
|
||||
{
|
||||
clipMap->leafbrushes = reader.readArray<unsigned short>(clipMap->numLeafBrushes);
|
||||
}
|
||||
|
||||
if (clipMap->numLeafSurfaces)
|
||||
{
|
||||
clipMap->leafsurfaces = reader.readArray<unsigned int>(clipMap->numLeafSurfaces);
|
||||
}
|
||||
|
||||
if (clipMap->vertCount)
|
||||
{
|
||||
clipMap->verts = reader.readArray<Game::vec3_t>(clipMap->vertCount);
|
||||
}
|
||||
|
||||
if (clipMap->triCount)
|
||||
{
|
||||
clipMap->triIndices = reader.readArray<unsigned short>(clipMap->triCount * 3);
|
||||
clipMap->triEdgeIsWalkable = reader.readArray<char>(4 * ((3 * clipMap->triCount + 31) >> 5));
|
||||
}
|
||||
|
||||
if (clipMap->borderCount)
|
||||
{
|
||||
clipMap->borders = reader.readArray<Game::CollisionBorder>(clipMap->borderCount);
|
||||
}
|
||||
|
||||
if (clipMap->partitionCount)
|
||||
{
|
||||
clipMap->partitions = builder->getAllocator()->allocateArray<Game::CollisionPartition>(clipMap->partitionCount);
|
||||
for (int i = 0; i < clipMap->partitionCount; ++i)
|
||||
{
|
||||
clipMap->partitions[i].triCount = reader.read<char>();
|
||||
clipMap->partitions[i].borderCount = reader.read<char>();
|
||||
clipMap->partitions[i].firstTri = reader.read<int>();
|
||||
|
||||
if (clipMap->partitions[i].borderCount > 0)
|
||||
{
|
||||
int index = reader.read<int>();
|
||||
if (index < 0 || index > clipMap->borderCount)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "invalid border index\n");
|
||||
return;
|
||||
}
|
||||
clipMap->partitions[i].borders = &clipMap->borders[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clipMap->aabbTreeCount)
|
||||
{
|
||||
clipMap->aabbTrees = reader.readArray<Game::CollisionAabbTree>(clipMap->aabbTreeCount);
|
||||
}
|
||||
|
||||
if (clipMap->numSubModels)
|
||||
{
|
||||
clipMap->cmodels = reader.readArray<Game::cmodel_t>(clipMap->numSubModels);
|
||||
}
|
||||
|
||||
if (clipMap->numBrushes)
|
||||
{
|
||||
clipMap->brushes = builder->getAllocator()->allocateArray<Game::cbrush_t>(clipMap->numBrushes);
|
||||
memset(clipMap->brushes, 0, sizeof(Game::cbrush_t) * clipMap->numBrushes);
|
||||
for (int i = 0; i < clipMap->numBrushes; ++i)
|
||||
{
|
||||
clipMap->brushes[i].numsides = reader.read<unsigned int>() & 0xFFFF; // todo: check for overflow here
|
||||
if (clipMap->brushes[i].numsides > 0)
|
||||
{
|
||||
auto index = reader.read<unsigned int>();
|
||||
if (index < 0 || index > clipMap->numBrushSides)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "invalid side index\n");
|
||||
return;
|
||||
}
|
||||
clipMap->brushes[i].sides = &clipMap->brushsides[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
clipMap->brushes[i].sides = nullptr;
|
||||
}
|
||||
|
||||
auto index = reader.read<unsigned int>();
|
||||
if (index > clipMap->numBrushEdges)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "invalid edge index\n");
|
||||
return;
|
||||
}
|
||||
clipMap->brushes[i].baseAdjacentSide = &clipMap->brushEdges[index];
|
||||
|
||||
char* tmp = reader.readArray<char>(12);
|
||||
memcpy(&clipMap->brushes[i].axialMaterialNum, tmp, 12);
|
||||
|
||||
//todo check for overflow
|
||||
for (int r = 0; r < 2; ++r)
|
||||
{
|
||||
for (int c = 0; c < 3; ++c)
|
||||
{
|
||||
clipMap->brushes[i].firstAdjacentSideOffsets[r][c] = reader.read<short>() & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
tmp = reader.readArray<char>(6);
|
||||
memcpy(&clipMap->brushes[i].edgeCount, tmp, 6);
|
||||
}
|
||||
|
||||
clipMap->brushBounds = reader.readArray<Game::Bounds>(clipMap->numBrushes);
|
||||
clipMap->brushContents = reader.readArray<int>(clipMap->numBrushes);
|
||||
}
|
||||
|
||||
for (int x = 0; x < 2; ++x)
|
||||
{
|
||||
if (clipMap->dynEntCount[x])
|
||||
{
|
||||
clipMap->dynEntDefList[x] = builder->getAllocator()->allocateArray<Game::DynEntityDef>(clipMap->dynEntCount[x]);
|
||||
for (int i = 0; i < clipMap->dynEntCount[x]; ++i)
|
||||
{
|
||||
clipMap->dynEntDefList[x][i].type = reader.read<Game::DynEntityType>();
|
||||
clipMap->dynEntDefList[x][i].pose = reader.read<Game::GfxPlacement>();
|
||||
std::string tempName = reader.readString();
|
||||
if (tempName != "NONE"s)
|
||||
{
|
||||
clipMap->dynEntDefList[x][i].xModel = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, tempName, builder).model;
|
||||
}
|
||||
|
||||
clipMap->dynEntDefList[x][i].brushModel = reader.read<short>();
|
||||
clipMap->dynEntDefList[x][i].physicsBrushModel = reader.read<short>();
|
||||
|
||||
tempName = reader.readString();
|
||||
if (tempName != "NONE"s)
|
||||
{
|
||||
clipMap->dynEntDefList[x][i].destroyFx = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, tempName, builder).fx;
|
||||
}
|
||||
|
||||
tempName = reader.readString();
|
||||
if (tempName != "NONE"s)
|
||||
{
|
||||
clipMap->dynEntDefList[x][i].physPreset = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_PHYSPRESET, tempName, builder).physPreset;
|
||||
}
|
||||
|
||||
clipMap->dynEntDefList[x][i].health = reader.read<int>();
|
||||
clipMap->dynEntDefList[x][i].mass = reader.read<Game::PhysMass>();
|
||||
clipMap->dynEntDefList[x][i].contents = reader.read<int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clipMap->smodelNodeCount = reader.read<unsigned short>();
|
||||
clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
|
||||
|
||||
clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, std::format("maps/mp/{}.d3dbsp", name), builder).mapEnts;
|
||||
|
||||
// add triggers to mapEnts
|
||||
if (version >= 2) {
|
||||
if (clipMap->numSubModels > 0) {
|
||||
clipMap->mapEnts->trigger.count = clipMap->numSubModels;
|
||||
clipMap->mapEnts->trigger.hullCount = clipMap->numSubModels;
|
||||
|
||||
Game::TriggerHull* hulls = builder->getAllocator()->allocateArray<Game::TriggerHull>(clipMap->mapEnts->trigger.hullCount);
|
||||
Game::TriggerModel* models = builder->getAllocator()->allocateArray<Game::TriggerModel>(clipMap->mapEnts->trigger.count);
|
||||
|
||||
for (unsigned int i = 0; i < clipMap->numSubModels; ++i)
|
||||
{
|
||||
models[i] = reader.read<Game::TriggerModel>();
|
||||
hulls[i] = reader.read<Game::TriggerHull>();
|
||||
}
|
||||
|
||||
size_t slabCount = reader.read<size_t>();
|
||||
clipMap->mapEnts->trigger.slabCount = slabCount;
|
||||
Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray<Game::TriggerSlab>(clipMap->mapEnts->trigger.slabCount);
|
||||
for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) {
|
||||
slabs[i] = reader.read<Game::TriggerSlab>();
|
||||
}
|
||||
|
||||
|
||||
clipMap->mapEnts->trigger.models = &models[0];
|
||||
clipMap->mapEnts->trigger.hulls = &hulls[0];
|
||||
clipMap->mapEnts->trigger.slabs = &slabs[0];
|
||||
}
|
||||
}
|
||||
|
||||
clipMap->checksum = reader.read<int>();
|
||||
|
||||
// This mustn't be null and has to have at least 1 'valid' entry.
|
||||
if (!clipMap->smodelNodeCount || !clipMap->smodelNodes)
|
||||
{
|
||||
clipMap->smodelNodeCount = 1;
|
||||
clipMap->smodelNodes = builder->getAllocator()->allocateArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
|
||||
|
||||
clipMap->smodelNodes[0].bounds.halfSize[0] = -131072.000f;
|
||||
clipMap->smodelNodes[0].bounds.halfSize[1] = -131072.000f;
|
||||
clipMap->smodelNodes[0].bounds.halfSize[2] = -131072.000f;
|
||||
}
|
||||
|
||||
// These mustn't be null, but they don't need to be valid.
|
||||
for (int i = 0; i < 2 && clipMap->dynEntCount[i]; ++i)
|
||||
{
|
||||
Utils::Stream::ClearPointer(&clipMap->dynEntPoseList[i]);
|
||||
Utils::Stream::ClearPointer(&clipMap->dynEntClientList[i]);
|
||||
Utils::Stream::ClearPointer(&clipMap->dynEntCollList[i]);
|
||||
}
|
||||
|
||||
if (!reader.end())
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Clipmap data left!");
|
||||
}
|
||||
|
||||
header->clipMap = clipMap;
|
||||
header->clipMap = builder->getIW4OfApi()->read<Game::clipMap_t>(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, _name);
|
||||
assert(header->data);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace Assets
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||
static void dump(Game::XAssetHeader /*header*/);
|
||||
|
||||
private:
|
||||
class SModelQuadtree
|
||||
|
@ -1,355 +1,18 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Isnd_alias_list_t.hpp"
|
||||
|
||||
#include <Utils/Json.hpp>
|
||||
|
||||
namespace Assets
|
||||
{
|
||||
void Isnd_alias_list_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
Components::FileSystem::File aliasFile(std::format("sounds/{}.json", name));
|
||||
if (!aliasFile.exists())
|
||||
header->sound = builder->getIW4OfApi()->read<Game::snd_alias_list_t>(Game::XAssetType::ASSET_TYPE_SOUND, name);
|
||||
|
||||
if (!header->sound)
|
||||
{
|
||||
header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound;
|
||||
return;
|
||||
header->sound = Components::AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, name.data()).sound;
|
||||
}
|
||||
|
||||
auto* aliasList = builder->getAllocator()->allocate<Game::snd_alias_list_t>();
|
||||
if (!aliasList)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to allocate memory for sound alias structure!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json infoData;
|
||||
try
|
||||
{
|
||||
infoData = nlohmann::json::parse(aliasFile.getBuffer());
|
||||
}
|
||||
catch (const nlohmann::json::parse_error& ex)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json aliasesContainer = infoData["head"];
|
||||
nlohmann::json::array_t aliases = aliasesContainer;
|
||||
|
||||
aliasList->count = aliases.size();
|
||||
|
||||
// Allocate
|
||||
aliasList->head = builder->getAllocator()->allocateArray<Game::snd_alias_t>(aliasList->count);
|
||||
if (!aliasList->head)
|
||||
{
|
||||
Components::Logger::Print("Error allocating memory for sound alias structure!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
aliasList->aliasName = builder->getAllocator()->duplicateString(name);
|
||||
|
||||
for (size_t i = 0; i < aliasList->count; i++)
|
||||
{
|
||||
nlohmann::json head = aliasesContainer[i];
|
||||
|
||||
if (!infoData.is_object())
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Failed to load sound {}!", name);
|
||||
return;
|
||||
}
|
||||
|
||||
aliasList->head->soundFile = builder->getAllocator()->allocate<Game::SoundFile>();
|
||||
if (!aliasList->head->soundFile)
|
||||
{
|
||||
Components::Logger::Print("Error allocating memory for sound alias structure!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Game::snd_alias_t* alias = aliasList->head;
|
||||
|
||||
// try and parse everything and if it fails then fail for the whole file
|
||||
auto type = head["type"];
|
||||
auto subtitle = head["subtitle"];
|
||||
auto secondaryAliasName = head["secondaryAliasName"];
|
||||
auto chainAliasName = head["chainAliasName"];
|
||||
auto soundFile = head["soundFile"];
|
||||
auto sequence = head["sequence"];
|
||||
auto volMin = head["volMin"];
|
||||
auto volMax = head["volMax"];
|
||||
auto pitchMin = head["pitchMin"];
|
||||
auto pitchMax = head["pitchMax"];
|
||||
auto distMin = head["distMin"];
|
||||
auto distMax = head["distMax"];
|
||||
auto flags = head["flags"];
|
||||
auto slavePercentage = head["slavePercentage"];
|
||||
auto probability = head["probability"];
|
||||
auto lfePercentage = head["lfePercentage"];
|
||||
auto centerPercentage = head["centerPercentage"];
|
||||
auto startDelay = head["startDelay"];
|
||||
auto volumeFalloffCurve = head["volumeFalloffCurve"];
|
||||
auto envelopMin = head["envelopMin"];
|
||||
auto envelopMax = head["envelopMax"];
|
||||
auto envelopPercentage = head["envelopPercentage"];
|
||||
auto speakerMap = head["speakerMap"];
|
||||
auto aliasName = head["aliasName"];
|
||||
|
||||
// Fix casing
|
||||
if (soundFile.is_null())
|
||||
{
|
||||
soundFile = head["soundfile"];
|
||||
|
||||
Components::Logger::Print("Fixed casing on {}\n", name);
|
||||
}
|
||||
|
||||
if (type.is_null() || soundFile.is_null())
|
||||
{
|
||||
Components::Logger::Print("Type is {}\n", type.dump());
|
||||
Components::Logger::Print("SoundFile is {}\n", soundFile.dump());
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Each alias must have at least a type and a soundFile\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
#define CHECK(x, type) (x.is_##type##() || x.is_null())
|
||||
|
||||
// TODO: actually support all of those properties
|
||||
if (!CHECK(type, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "type", Utils::Json::TypeToString(type.type()), type.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(subtitle, string))
|
||||
{
|
||||
Components::Logger::Print("{} is not string but {} ({})\n", "subtitle", Utils::Json::TypeToString(subtitle.type()), subtitle.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(aliasName, string))
|
||||
{
|
||||
Components::Logger::Print("{} is not string but {} ({})\n", "aliasName", Utils::Json::TypeToString(aliasName.type()), aliasName.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(secondaryAliasName, string))
|
||||
{
|
||||
Components::Logger::Print("{} is not string but {} ({})\n", "secondaryAliasName", Utils::Json::TypeToString(secondaryAliasName.type()), secondaryAliasName.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(chainAliasName, string))
|
||||
{
|
||||
Components::Logger::Print("{} is not string but {} ({})\n", "chainAliasName", Utils::Json::TypeToString(chainAliasName.type()), chainAliasName.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(soundFile, string))
|
||||
{
|
||||
Components::Logger::Print("{} is not string but {} ({})\n", "soundFile", Utils::Json::TypeToString(soundFile.type()), soundFile.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(sequence, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "sequence", Utils::Json::TypeToString(sequence.type()), sequence.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(volMin, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "volMin", Utils::Json::TypeToString(volMin.type()), volMin.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(volMax, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "volMax", Utils::Json::TypeToString(volMax.type()), volMax.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(pitchMin, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "pitchMin", Utils::Json::TypeToString(pitchMin.type()), pitchMin.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(pitchMax, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ()\n", "pitchMax", Utils::Json::TypeToString(pitchMax.type()), pitchMax.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(probability, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({}))\n", "probability", Utils::Json::TypeToString(probability.type()), probability.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(lfePercentage, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "lfePercentage", Utils::Json::TypeToString(lfePercentage.type()), lfePercentage.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(centerPercentage, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "centerPercentage", Utils::Json::TypeToString(centerPercentage.type()), centerPercentage.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(startDelay, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "startDelay", Utils::Json::TypeToString(startDelay.type()), startDelay.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(volumeFalloffCurve, string))
|
||||
{
|
||||
Components::Logger::Print("{}s is not string but {} ({})\n", "volumeFalloffCurve", Utils::Json::TypeToString(volumeFalloffCurve.type()), volumeFalloffCurve.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(envelopMin, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "envelopMin", Utils::Json::TypeToString(envelopMin.type()), envelopMin.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(envelopMax, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "envelopMax", Utils::Json::TypeToString(envelopMax.type()), envelopMax.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(envelopPercentage, number))
|
||||
{
|
||||
Components::Logger::Print("{} is not number but {} ({})\n", "envelopPercentage", Utils::Json::TypeToString(envelopPercentage.type()), envelopPercentage.dump());
|
||||
}
|
||||
|
||||
if (!CHECK(speakerMap, object))
|
||||
{
|
||||
Components::Logger::Print("{} is not object but {} ({})\n", "speakerMap", Utils::Json::TypeToString(speakerMap.type()), speakerMap.dump());
|
||||
}
|
||||
|
||||
|
||||
if (CHECK(type, number) && CHECK(aliasName, string) && CHECK(subtitle, string) && CHECK(secondaryAliasName, string) && CHECK(chainAliasName, string) &&
|
||||
CHECK(soundFile, string) && CHECK(sequence, number) && CHECK(volMin, number) && CHECK(volMax, number) && CHECK(pitchMin, number) &&
|
||||
CHECK(pitchMax, number) && CHECK(distMin, number) && CHECK(distMax, number) && CHECK(flags, number) && CHECK(slavePercentage, number) &&
|
||||
CHECK(probability, number) && CHECK(lfePercentage, number) && CHECK(centerPercentage, number) && CHECK(startDelay, number) &&
|
||||
CHECK(volumeFalloffCurve, string) && CHECK(envelopMin, number) && CHECK(envelopMax, number) && CHECK(envelopPercentage, number) &&
|
||||
CHECK(speakerMap, object))
|
||||
{
|
||||
|
||||
alias->soundFile->exists = true;
|
||||
alias->aliasName = builder->getAllocator()->duplicateString(aliasName.get<std::string>());
|
||||
|
||||
if (subtitle.is_string())
|
||||
{
|
||||
alias->subtitle = builder->getAllocator()->duplicateString(subtitle.get<std::string>());
|
||||
}
|
||||
if (secondaryAliasName.is_string())
|
||||
{
|
||||
alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.get<std::string>());
|
||||
}
|
||||
if (chainAliasName.is_string())
|
||||
{
|
||||
alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.get<std::string>());
|
||||
}
|
||||
|
||||
alias->sequence = sequence.get<int>();
|
||||
alias->volMin = volMin.get<float>();
|
||||
alias->volMax = volMax.get<float>();
|
||||
alias->pitchMin = pitchMin.get<float>();
|
||||
alias->pitchMax = pitchMax.get<float>();
|
||||
alias->distMin = distMin.get<float>();
|
||||
alias->distMax = distMax.get<float>();
|
||||
alias->flags = flags.get<int>();
|
||||
alias->___u15.slavePercentage = slavePercentage.get<float>();
|
||||
alias->probability = probability.get<float>();
|
||||
alias->lfePercentage = lfePercentage.get<float>();
|
||||
alias->centerPercentage = centerPercentage.get<float>();
|
||||
alias->startDelay = startDelay.get<int>();
|
||||
alias->envelopMin = envelopMin.get<float>();
|
||||
alias->envelopMax = envelopMax.get<float>();
|
||||
alias->envelopPercentage = envelopPercentage.get<float>();
|
||||
|
||||
// Speaker map object
|
||||
if (!speakerMap.is_null())
|
||||
{
|
||||
alias->speakerMap = builder->getAllocator()->allocate<Game::SpeakerMap>();
|
||||
if (!alias->speakerMap)
|
||||
{
|
||||
Components::Logger::Print("Error allocating memory for speakermap in sound alias{}!\n", alias->aliasName);
|
||||
return;
|
||||
}
|
||||
|
||||
alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].get<std::string>());
|
||||
alias->speakerMap->isDefault = speakerMap["isDefault"].get<bool>();
|
||||
|
||||
if (speakerMap["channelMaps"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t channelMaps = speakerMap["channelMaps"];
|
||||
|
||||
assert(channelMaps.size() <= 4);
|
||||
|
||||
// channelMapIndex should never exceed 1
|
||||
for (size_t channelMapIndex = 0; channelMapIndex < 2; channelMapIndex++)
|
||||
{
|
||||
// subChannelIndex should never exceed 1
|
||||
for (size_t subChannelIndex = 0; subChannelIndex < 2; subChannelIndex++)
|
||||
{
|
||||
nlohmann::json channelMap = channelMaps[channelMapIndex * 2 + subChannelIndex]; // 0-3
|
||||
|
||||
nlohmann::json::array_t speakers = channelMap["speakers"];
|
||||
|
||||
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount = speakers.size();
|
||||
|
||||
for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++)
|
||||
{
|
||||
auto speaker = speakers[speakerIndex];
|
||||
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[0] = speaker["levels0"].get<float>();
|
||||
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = speaker["levels1"].get<float>();
|
||||
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = speaker["numLevels"].get<int>();
|
||||
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = speaker["speaker"].get<int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (volumeFalloffCurve.is_string())
|
||||
{
|
||||
auto fallOffCurve = volumeFalloffCurve.get<std::string>();
|
||||
|
||||
if (fallOffCurve.empty())
|
||||
{
|
||||
fallOffCurve = "$default";
|
||||
}
|
||||
|
||||
auto curve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, fallOffCurve, builder).sndCurve;
|
||||
alias->volumeFalloffCurve = curve;
|
||||
}
|
||||
|
||||
if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_LOADED) // Loaded
|
||||
{
|
||||
alias->soundFile->type = Game::SAT_LOADED;
|
||||
alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.get<std::string>(), builder).loadSnd;
|
||||
}
|
||||
else if (static_cast<Game::snd_alias_type_t>(type.get<int>()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed
|
||||
{
|
||||
alias->soundFile->type = Game::SAT_STREAMED;
|
||||
|
||||
std::string streamedFile = soundFile.get<std::string>();
|
||||
std::string directory = ""s;
|
||||
int split = streamedFile.find_last_of('/');
|
||||
|
||||
if (split >= 0)
|
||||
{
|
||||
directory = streamedFile.substr(0, split);
|
||||
streamedFile = streamedFile.substr(split+1);
|
||||
}
|
||||
|
||||
alias->soundFile->u.streamSnd.filename.info.raw.dir = builder->getAllocator()->duplicateString(directory.c_str());
|
||||
alias->soundFile->u.streamSnd.filename.info.raw.name = builder->getAllocator()->duplicateString(streamedFile.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}! Invalid sound type {}\n", name, type.get<std::string>());
|
||||
return;
|
||||
}
|
||||
|
||||
aliasList->head[i] = *alias;
|
||||
}
|
||||
else
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Failed to parse sound {}!\n", name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
header->sound = aliasList;
|
||||
|
||||
#undef CHECK
|
||||
|
||||
}
|
||||
|
||||
void Isnd_alias_list_t::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
@ -390,8 +53,16 @@ namespace Assets
|
||||
|
||||
if (asset->aliasName)
|
||||
{
|
||||
buffer->saveString(builder->getAssetName(this->getType(), asset->aliasName));
|
||||
Utils::Stream::ClearPointer(&dest->aliasName);
|
||||
if (builder->hasPointer(asset->aliasName))
|
||||
{
|
||||
dest->aliasName = builder->getPointer(asset->aliasName);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder->storePointer(asset->aliasName);
|
||||
buffer->saveString(asset->aliasName);
|
||||
Utils::Stream::ClearPointer(&dest->aliasName);
|
||||
}
|
||||
}
|
||||
|
||||
if (asset->head)
|
||||
@ -417,8 +88,16 @@ namespace Assets
|
||||
|
||||
if (alias->aliasName)
|
||||
{
|
||||
buffer->saveString(alias->aliasName);
|
||||
Utils::Stream::ClearPointer(&destAlias->aliasName);
|
||||
if (builder->hasPointer(alias->aliasName))
|
||||
{
|
||||
destAlias->aliasName = builder->getPointer(alias->aliasName);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder->storePointer(alias->aliasName);
|
||||
buffer->saveString(alias->aliasName);
|
||||
Utils::Stream::ClearPointer(&destAlias->aliasName);
|
||||
}
|
||||
}
|
||||
|
||||
if (alias->subtitle)
|
||||
@ -465,7 +144,7 @@ namespace Assets
|
||||
{
|
||||
if (alias->soundFile->type == Game::snd_alias_type_t::SAT_LOADED)
|
||||
{
|
||||
destSoundFile->u.loadSnd = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd;
|
||||
destSoundFile->u.loadSnd = builder->saveSubAsset(Game::ASSET_TYPE_LOADED_SOUND, alias->soundFile->u.loadSnd).loadSnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -492,7 +171,7 @@ namespace Assets
|
||||
|
||||
if (alias->volumeFalloffCurve)
|
||||
{
|
||||
destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve;
|
||||
destAlias->volumeFalloffCurve = builder->saveSubAsset(Game::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve).sndCurve;
|
||||
}
|
||||
|
||||
if (alias->speakerMap)
|
||||
|
@ -1,4 +1,8 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include <proto/auth.pb.h>
|
||||
|
||||
#include "Bans.hpp"
|
||||
|
||||
namespace Components
|
||||
@ -467,7 +471,7 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto level = static_cast<uint32_t>(atoi(params->get(1)));
|
||||
const auto level = std::strtoul(params->get(1), nullptr, 10);
|
||||
Auth::IncreaseSecurityLevel(level);
|
||||
}
|
||||
});
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
const char* Bans::BanListFile = "userraw/bans.json";
|
||||
|
||||
// Have only one instance of IW4x read/write the file
|
||||
std::unique_lock<Utils::NamedMutex> Bans::Lock()
|
||||
{
|
||||
@ -87,7 +89,7 @@ namespace Components
|
||||
|
||||
void Bans::SaveBans(const BanList* list)
|
||||
{
|
||||
assert(list != nullptr);
|
||||
assert(list);
|
||||
|
||||
const auto _ = Lock();
|
||||
|
||||
@ -105,7 +107,8 @@ namespace Components
|
||||
ipEntry.bytes[0] & 0xFF,
|
||||
ipEntry.bytes[1] & 0xFF,
|
||||
ipEntry.bytes[2] & 0xFF,
|
||||
ipEntry.bytes[3] & 0xFF));
|
||||
ipEntry.bytes[3] & 0xFF)
|
||||
);
|
||||
}
|
||||
|
||||
const nlohmann::json bans = nlohmann::json
|
||||
@ -114,18 +117,17 @@ namespace Components
|
||||
{ "id", idVector },
|
||||
};
|
||||
|
||||
FileSystem::FileWriter ("bans.json").write(bans.dump());
|
||||
Utils::IO::WriteFile(BanListFile, bans.dump());
|
||||
}
|
||||
|
||||
void Bans::LoadBans(BanList* list)
|
||||
{
|
||||
assert(list != nullptr);
|
||||
assert(list);
|
||||
|
||||
const auto _ = Lock();
|
||||
|
||||
FileSystem::File bans("bans.json");
|
||||
|
||||
if (!bans.exists())
|
||||
const auto bans = Utils::IO::ReadFile(BanListFile);
|
||||
if (bans.empty())
|
||||
{
|
||||
Logger::Debug("bans.json does not exist");
|
||||
return;
|
||||
@ -134,14 +136,20 @@ namespace Components
|
||||
nlohmann::json banData;
|
||||
try
|
||||
{
|
||||
banData = nlohmann::json::parse(bans.getBuffer());
|
||||
banData = nlohmann::json::parse(bans);
|
||||
}
|
||||
catch (const nlohmann::json::parse_error& ex)
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!banData.contains("id") || !banData.contains("ip"))
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "bans.json contains invalid data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& idList = banData["id"];
|
||||
const auto& ipList = banData["ip"];
|
||||
|
||||
@ -252,7 +260,6 @@ namespace Components
|
||||
}
|
||||
|
||||
const auto num = std::atoi(input);
|
||||
|
||||
if (num < 0 || num >= *Game::svs_clientCount)
|
||||
{
|
||||
Logger::Print("Bad client slot: {}\n", num);
|
||||
@ -266,8 +273,13 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (cl->bIsTestClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2);
|
||||
Bans::BanClient(&Game::svs_clients[num], reason);
|
||||
BanClient(&Game::svs_clients[num], reason);
|
||||
});
|
||||
|
||||
Command::Add("unbanClient", [](Command::Params* params)
|
||||
|
@ -9,8 +9,6 @@ namespace Components
|
||||
|
||||
Bans();
|
||||
|
||||
static std::unique_lock<Utils::NamedMutex> Lock();
|
||||
|
||||
static void BanClient(Game::client_t* cl, const std::string& reason);
|
||||
static void UnbanClient(SteamID id);
|
||||
static void UnbanClient(Game::netIP_t ip);
|
||||
@ -25,6 +23,10 @@ namespace Components
|
||||
std::vector<Game::netIP_t> ipList;
|
||||
};
|
||||
|
||||
static const char* BanListFile;
|
||||
|
||||
static std::unique_lock<Utils::NamedMutex> Lock();
|
||||
|
||||
static void LoadBans(BanList* list);
|
||||
static void SaveBans(const BanList* list);
|
||||
};
|
||||
|
155
src/Components/Modules/BotLib/lPrecomp.cpp
Normal file
155
src/Components/Modules/BotLib/lPrecomp.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "lPrecomp.hpp"
|
||||
|
||||
namespace Components::BotLib
|
||||
{
|
||||
// Two new directives! Refer to (https://en.cppreference.com/w/cpp/preprocessor/conditional)
|
||||
Game::directive_s lPrecomp::directives[] =
|
||||
{
|
||||
{"if", Game::PC_Directive_if},
|
||||
{"ifdef", Game::PC_Directive_ifdef},
|
||||
{"ifndef", Game::PC_Directive_ifndef},
|
||||
{"elifdef", lPrecomp::PC_Directive_elifdef},
|
||||
{"elifndef", lPrecomp::PC_Directive_elifndef},
|
||||
{"elif", Game::PC_Directive_elif},
|
||||
{"else", Game::PC_Directive_else},
|
||||
{"endif", Game::PC_Directive_endif},
|
||||
{"include", Game::PC_Directive_include},
|
||||
{"define", Game::PC_Directive_define},
|
||||
{"undef", Game::PC_Directive_undef},
|
||||
{"line", Game::PC_Directive_line},
|
||||
{"error", Game::PC_Directive_error},
|
||||
{"pragma", Game::PC_Directive_pragma},
|
||||
{"eval", Game::PC_Directive_eval},
|
||||
{"evalfloat", Game::PC_Directive_evalfloat},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
int lPrecomp::PC_Directive_elif_def(Game::source_s* source, int* value, int type)
|
||||
{
|
||||
Game::token_s token;
|
||||
int skip;
|
||||
|
||||
if (!Game::PC_ReadLine(source, &token, false))
|
||||
{
|
||||
Game::SourceError(source, "#elifdef without name");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token.type != TT_NAME)
|
||||
{
|
||||
Game::PC_UnreadSourceToken(source, &token);
|
||||
Game::SourceError(source, "expected name after #elifdef, found %s", token.string);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* d = Game::PC_FindHashedDefine(source->definehash, token.string);
|
||||
*value = skip = (type == INDENT_IFDEF) == (d == nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// #elifdef identifier is essentially equivalent to #elif defined identifier
|
||||
int lPrecomp::PC_Directive_elifdef(Game::source_s* source)
|
||||
{
|
||||
int type;
|
||||
int skip;
|
||||
|
||||
Game::PC_PopIndent(source, &type, &skip);
|
||||
if (!type || type == INDENT_ELSE)
|
||||
{
|
||||
Game::SourceError(source, "misplaced #elifdef");
|
||||
return false;
|
||||
}
|
||||
|
||||
int value;
|
||||
if (PC_Directive_elif_def(source, &value, INDENT_IFDEF))
|
||||
{
|
||||
if (skip == Game::SKIP_YES)
|
||||
{
|
||||
skip = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
skip = Game::SKIP_ALL_ELIFS;
|
||||
}
|
||||
|
||||
Game::PC_PushIndent(source, INDENT_ELIF, skip);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// #elifndef identifier is essentially equivalent to #elif !defined identifier
|
||||
int lPrecomp::PC_Directive_elifndef(Game::source_s* source)
|
||||
{
|
||||
int type;
|
||||
int skip;
|
||||
|
||||
Game::PC_PopIndent(source, &type, &skip);
|
||||
if (!type || type == INDENT_ELSE)
|
||||
{
|
||||
Game::SourceError(source, "misplaced #elifndef");
|
||||
return false;
|
||||
}
|
||||
|
||||
int value;
|
||||
if (PC_Directive_elif_def(source, &value, INDENT_IFNDEF))
|
||||
{
|
||||
if (skip == Game::SKIP_YES)
|
||||
{
|
||||
skip = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
skip = Game::SKIP_ALL_ELIFS;
|
||||
}
|
||||
|
||||
Game::PC_PushIndent(source, INDENT_ELIF, skip);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int lPrecomp::PC_ReadDirective(Game::source_s* source)
|
||||
{
|
||||
Game::token_s token;
|
||||
|
||||
// Read the directive name
|
||||
if (!Game::PC_ReadSourceToken(source, &token))
|
||||
{
|
||||
Game::SourceError(source, "found # without name");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Directive name must be on the same line
|
||||
if (token.linescrossed > 0)
|
||||
{
|
||||
Game::PC_UnreadSourceToken(source, &token);
|
||||
Game::SourceError(source, "found # at end of line");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it is a name
|
||||
if (token.type == TT_NAME)
|
||||
{
|
||||
// Find the precompiler directive
|
||||
for (auto i = 0; directives[i].name; ++i)
|
||||
{
|
||||
if (!std::strcmp(directives[i].name, token.string))
|
||||
{
|
||||
return directives[i].func(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Game::SourceError(source, "unknown precompiler directive %s", token.string);
|
||||
return false;
|
||||
}
|
||||
|
||||
lPrecomp::lPrecomp()
|
||||
{
|
||||
Utils::Hook(0x4ACD19, PC_ReadDirective, HOOK_CALL).install()->quick();
|
||||
}
|
||||
}
|
46
src/Components/Modules/BotLib/lPrecomp.hpp
Normal file
46
src/Components/Modules/BotLib/lPrecomp.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
namespace Components::BotLib
|
||||
{
|
||||
class lPrecomp : public Component
|
||||
{
|
||||
public:
|
||||
lPrecomp();
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
INDENT_IF = 1,
|
||||
INDENT_ELSE = 2,
|
||||
INDENT_ELIF = 4,
|
||||
INDENT_IFDEF = 8,
|
||||
INDENT_IFNDEF = 10,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
TT_STRING = 1,
|
||||
TT_LITERAL = 2,
|
||||
TT_NUMBER = 3,
|
||||
TT_NAME = 4,
|
||||
TT_PUNCTUATION = 5,
|
||||
TT_DECIMAL = 8,
|
||||
TT_HEX = 0x100,
|
||||
TT_OCTAL = 0x200,
|
||||
TT_BINARY = 0x400,
|
||||
TT_FLOAT = 0x800,
|
||||
TT_INTEGER = 0x1000,
|
||||
TT_LONG = 0x2000,
|
||||
TT_UNSIGNED = 0x4000,
|
||||
};
|
||||
|
||||
static Game::directive_s directives[];
|
||||
|
||||
static int PC_Directive_elif_def(Game::source_s* source, int* value, int type);
|
||||
|
||||
static int PC_Directive_elifdef(Game::source_s* source);
|
||||
static int PC_Directive_elifndef(Game::source_s* source);
|
||||
|
||||
static int PC_ReadDirective(Game::source_s* source);
|
||||
};
|
||||
}
|
@ -1,13 +1,20 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include "Bots.hpp"
|
||||
#include "ServerList.hpp"
|
||||
|
||||
#include "GSC/Script.hpp"
|
||||
|
||||
// From Quake-III
|
||||
#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX)
|
||||
#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1)))
|
||||
|
||||
namespace Components
|
||||
{
|
||||
std::vector<Bots::botData> Bots::BotNames;
|
||||
|
||||
Dvar::Var Bots::SVRandomBotNames;
|
||||
Dvar::Var Bots::SVClanName;
|
||||
|
||||
struct BotMovementInfo
|
||||
{
|
||||
@ -45,72 +52,112 @@ namespace Components
|
||||
{ "activate", Game::CMD_BUTTON_ACTIVATE },
|
||||
};
|
||||
|
||||
void Bots::RandomizeBotNames()
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::ranges::shuffle(BotNames, gen);
|
||||
}
|
||||
|
||||
void Bots::UpdateBotNames()
|
||||
{
|
||||
const auto masterPort = (*Game::com_masterPort)->current.integer;
|
||||
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
|
||||
|
||||
Game::netadr_t master;
|
||||
if (ServerList::GetMasterServer(masterServerName, masterPort, master))
|
||||
{
|
||||
Logger::Print("Getting bots...\n");
|
||||
Network::Send(master, "getbots");
|
||||
}
|
||||
}
|
||||
|
||||
void Bots::LoadBotNames()
|
||||
{
|
||||
FileSystem::File bots("bots.txt");
|
||||
|
||||
if (!bots.exists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = Utils::String::Split(bots.getBuffer(), '\n');
|
||||
|
||||
auto i = 0;
|
||||
for (auto& entry : data)
|
||||
{
|
||||
if (i >= 18)
|
||||
{
|
||||
// Only parse 18 names from the file
|
||||
break;
|
||||
}
|
||||
|
||||
// Take into account for CR line endings
|
||||
Utils::String::Replace(entry, "\r", "");
|
||||
// Remove whitespace
|
||||
Utils::String::Trim(entry);
|
||||
|
||||
if (entry.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string clanAbbrev;
|
||||
|
||||
// Check if there is a clan tag
|
||||
if (const auto pos = entry.find(','); pos != std::string::npos)
|
||||
{
|
||||
// Only start copying over from non-null characters (otherwise it can be "<=")
|
||||
if ((pos + 1) < entry.size())
|
||||
{
|
||||
clanAbbrev = entry.substr(pos + 1);
|
||||
}
|
||||
|
||||
entry = entry.substr(0, pos);
|
||||
}
|
||||
|
||||
BotNames.emplace_back(std::make_pair(entry, clanAbbrev));
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i)
|
||||
{
|
||||
RandomizeBotNames();
|
||||
}
|
||||
}
|
||||
|
||||
int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port)
|
||||
{
|
||||
static size_t botId = 0; // Loop over the BotNames vector
|
||||
static bool loadedNames = false; // Load file only once
|
||||
const char* botName;
|
||||
const char* clanName;
|
||||
std::string botName;
|
||||
std::string clanName;
|
||||
|
||||
if (BotNames.empty() && !loadedNames)
|
||||
if (!loadedNames)
|
||||
{
|
||||
FileSystem::File bots("bots.txt");
|
||||
loadedNames = true;
|
||||
|
||||
if (bots.exists())
|
||||
{
|
||||
auto data = Utils::String::Split(bots.getBuffer(), '\n');
|
||||
|
||||
if (SVRandomBotNames.get<bool>())
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::ranges::shuffle(data, gen);
|
||||
}
|
||||
|
||||
for (auto& entry : data)
|
||||
{
|
||||
// Take into account for CR line endings
|
||||
Utils::String::Replace(entry, "\r", "");
|
||||
// Remove whitespace
|
||||
Utils::String::Trim(entry);
|
||||
|
||||
if (!entry.empty())
|
||||
{
|
||||
std::string clanAbbrev;
|
||||
|
||||
// Check if there is a clan tag
|
||||
if (const auto pos = entry.find(','); pos != std::string::npos)
|
||||
{
|
||||
// Only start copying over from non-null characters (otherwise it can be "<=")
|
||||
if ((pos + 1) < entry.size())
|
||||
{
|
||||
clanAbbrev = entry.substr(pos + 1);
|
||||
}
|
||||
|
||||
entry = entry.substr(0, pos);
|
||||
}
|
||||
|
||||
BotNames.emplace_back(std::make_pair(entry, clanAbbrev));
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadBotNames();
|
||||
}
|
||||
|
||||
if (!BotNames.empty())
|
||||
{
|
||||
botId %= BotNames.size();
|
||||
const auto index = botId++;
|
||||
botName = BotNames[index].first.data();
|
||||
clanName = BotNames[index].second.data();
|
||||
botName = BotNames[index].first;
|
||||
clanName = BotNames[index].second;
|
||||
}
|
||||
else
|
||||
{
|
||||
botName = Utils::String::VA("bot%d", ++botId);
|
||||
clanName = "BOT";
|
||||
botName = std::format("bot{}", ++botId);
|
||||
clanName = "BOT"s;
|
||||
}
|
||||
|
||||
return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, clanName, protocol, checksum, statVer, statStuff, port);
|
||||
if (const auto svClanName = SVClanName.get<std::string>(); !svClanName.empty())
|
||||
{
|
||||
clanName = svClanName;
|
||||
}
|
||||
|
||||
return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port);
|
||||
}
|
||||
|
||||
void Bots::Spawn(unsigned int count)
|
||||
@ -120,8 +167,10 @@ namespace Components
|
||||
Scheduler::Once([]
|
||||
{
|
||||
auto* ent = Game::SV_AddTestClient();
|
||||
if (ent == nullptr)
|
||||
if (!ent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler::Once([ent]
|
||||
{
|
||||
@ -156,15 +205,15 @@ namespace Components
|
||||
|
||||
void Bots::AddMethods()
|
||||
{
|
||||
Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient();
|
||||
GSC::Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient();
|
||||
|
||||
Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
|
||||
GSC::Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
|
||||
{
|
||||
const auto* ent = Game::GetPlayerEntity(entref);
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
{
|
||||
Game::Scr_Error("^1BotStop: Can only call on a bot!\n");
|
||||
Game::Scr_Error("BotStop: Can only call on a bot!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,19 +222,19 @@ namespace Components
|
||||
g_botai[entref.entnum].active = true;
|
||||
});
|
||||
|
||||
Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: <bot> BotWeapon(<str>);
|
||||
GSC::Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: <bot> BotWeapon(<str>);
|
||||
{
|
||||
const auto* ent = Game::GetPlayerEntity(entref);
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
{
|
||||
Game::Scr_Error("^1BotWeapon: Can only call on a bot!\n");
|
||||
Game::Scr_Error("BotWeapon: Can only call on a bot!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* weapon = Game::Scr_GetString(0);
|
||||
|
||||
if (weapon == nullptr || weapon[0] == '\0')
|
||||
if (!weapon || !*weapon)
|
||||
{
|
||||
g_botai[entref.entnum].weapon = 1;
|
||||
return;
|
||||
@ -196,27 +245,27 @@ namespace Components
|
||||
g_botai[entref.entnum].active = true;
|
||||
});
|
||||
|
||||
Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: <bot> BotAction(<str action>);
|
||||
GSC::Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: <bot> BotAction(<str action>);
|
||||
{
|
||||
const auto* ent = Game::GetPlayerEntity(entref);
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
{
|
||||
Game::Scr_Error("^1BotAction: Can only call on a bot!\n");
|
||||
Game::Scr_Error("BotAction: Can only call on a bot!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* action = Game::Scr_GetString(0);
|
||||
|
||||
if (action == nullptr)
|
||||
if (!action)
|
||||
{
|
||||
Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!\n");
|
||||
Game::Scr_ParamError(0, "BotAction: Illegal parameter!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action[0] != '+' && action[0] != '-')
|
||||
{
|
||||
Game::Scr_ParamError(0, "^1BotAction: Sign for action must be '+' or '-'.\n");
|
||||
Game::Scr_ParamError(0, "BotAction: Sign for action must be '+' or '-'");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -234,16 +283,16 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
Game::Scr_ParamError(0, "^1BotAction: Unknown action.\n");
|
||||
Game::Scr_ParamError(0, "BotAction: Unknown action");
|
||||
});
|
||||
|
||||
Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: <bot> BotMovement(<int>, <int>);
|
||||
GSC::Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: <bot> BotMovement(<int>, <int>);
|
||||
{
|
||||
const auto* ent = Game::GetPlayerEntity(entref);
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
{
|
||||
Game::Scr_Error("^1BotMovement: Can only call on a bot!\n");
|
||||
Game::Scr_Error("BotMovement: Can only call on a bot!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -258,8 +307,10 @@ namespace Components
|
||||
|
||||
void Bots::BotAiAction(Game::client_t* cl)
|
||||
{
|
||||
if (cl->gentity == nullptr)
|
||||
if (!cl->gentity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto entnum = cl->gentity->s.number;
|
||||
|
||||
@ -280,10 +331,13 @@ namespace Components
|
||||
userCmd.rightmove = g_botai[entnum].right;
|
||||
userCmd.weapon = g_botai[entnum].weapon;
|
||||
|
||||
userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0]));
|
||||
userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1]));
|
||||
userCmd.angles[2] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[2] - cl->gentity->client->ps.delta_angles[2]));
|
||||
|
||||
Game::SV_ClientThink(cl, &userCmd);
|
||||
}
|
||||
|
||||
constexpr auto SV_BotUserMove = 0x626E50;
|
||||
__declspec(naked) void Bots::SV_BotUserMove_Hk()
|
||||
{
|
||||
__asm
|
||||
@ -345,7 +399,30 @@ namespace Components
|
||||
|
||||
Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick();
|
||||
|
||||
SVRandomBotNames = Dvar::Register<bool>("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names");
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
SVClanName = Dvar::Register<const char*>("sv_clanName", "", Game::DVAR_NONE, "The clan name for test clients");
|
||||
});
|
||||
|
||||
Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN);
|
||||
|
||||
Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data)
|
||||
{
|
||||
const auto masterPort = (*Game::com_masterPort)->current.integer;
|
||||
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
|
||||
|
||||
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));
|
||||
if (master == address)
|
||||
{
|
||||
auto botNames = Utils::String::Split(data, '\n');
|
||||
for (const auto& entry : botNames)
|
||||
{
|
||||
BotNames.emplace_back(std::make_pair(entry, "BOT"));
|
||||
}
|
||||
|
||||
RandomizeBotNames();
|
||||
}
|
||||
});
|
||||
|
||||
// Reset BotMovementInfo.active when client is dropped
|
||||
Events::OnClientDisconnect([](const int clientNum)
|
||||
@ -362,13 +439,19 @@ namespace Components
|
||||
|
||||
Command::Add("spawnBot", [](Command::Params* params)
|
||||
{
|
||||
auto count = 1u;
|
||||
if (!Dedicated::IsRunning())
|
||||
{
|
||||
Logger::Print("Server is not running.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t count = 1;
|
||||
|
||||
if (params->size() > 1)
|
||||
{
|
||||
if (params->get(1) == "all"s)
|
||||
{
|
||||
count = *Game::svs_clientCount;
|
||||
count = Game::MAX_CLIENTS;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -385,18 +468,9 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
count = std::min(static_cast<unsigned int>(*Game::svs_clientCount), count);
|
||||
count = std::min(Game::MAX_CLIENTS, count);
|
||||
|
||||
// Check if ingame and host
|
||||
if (!Game::SV_Loaded())
|
||||
{
|
||||
Toast::Show("cardicon_headshot", "^1Error", "You need to be host to spawn bots!", 3000);
|
||||
Logger::Print("You need to be host to spawn bots!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Toast::Show("cardicon_headshot", "^2Success", Utils::String::VA("Spawning %d %s...", count, (count == 1 ? "bot" : "bots")), 3000);
|
||||
Logger::Debug("Spawning {} {}", count, (count == 1 ? "bot" : "bots"));
|
||||
Logger::Print("Spawning {} {}", count, (count == 1 ? "bot" : "bots"));
|
||||
|
||||
Spawn(count);
|
||||
});
|
||||
|
@ -11,8 +11,11 @@ namespace Components
|
||||
using botData = std::pair< std::string, std::string>;
|
||||
static std::vector<botData> BotNames;
|
||||
|
||||
static Dvar::Var SVRandomBotNames;
|
||||
static Dvar::Var SVClanName;
|
||||
|
||||
static void RandomizeBotNames();
|
||||
static void UpdateBotNames();
|
||||
static void LoadBotNames();
|
||||
static int 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);
|
||||
|
@ -16,7 +16,7 @@ namespace Components
|
||||
{
|
||||
auto result = lookupResult;
|
||||
|
||||
const auto* username = Dvar::Var("name").get<const char*>();
|
||||
const auto* username = Dvar::Name.get<const char*>();
|
||||
if (std::strcmp(data->name, username) == 0)
|
||||
{
|
||||
result += 0xFE000000;
|
||||
@ -170,7 +170,7 @@ namespace Components
|
||||
playerTitle[0] = '\0';
|
||||
}
|
||||
|
||||
list.append(std::format("\\{}\\{}", std::to_string(i), playerTitle));
|
||||
list.append(std::format("\\{}\\{}", i, playerTitle));
|
||||
}
|
||||
|
||||
const auto* command = Utils::String::Format("{:c} customTitles \"{}\"", 21, list);
|
||||
@ -197,10 +197,10 @@ namespace Components
|
||||
|
||||
CardTitles::CardTitles()
|
||||
{
|
||||
Scheduler::Once([]
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
CustomTitle = Dvar::Register<const char*>("customTitle", "", Game::DVAR_USERINFO | Game::DVAR_ARCHIVE, "Custom card title");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
});
|
||||
|
||||
std::memset(&CustomTitles, 0, sizeof(char[Game::MAX_CLIENTS][18]));
|
||||
|
||||
|
@ -33,6 +33,13 @@ namespace Components
|
||||
Utils::Hook::Nop(0x43CA16, 9);
|
||||
Utils::Hook::Nop(0x505426, 9);
|
||||
|
||||
// Removed on IW5 MP (unprotected) but present on IW5 SP (protected) - CEG uninitialization / Steam Shutdown
|
||||
Utils::Hook::Set<std::uint8_t>(0x4F6370, 0xC3);
|
||||
|
||||
// Remove 'Steam Start' checking for DRM IPC
|
||||
Utils::Hook::Nop(0x451145, 5);
|
||||
Utils::Hook::Set<BYTE>(0x45114C, 0xEB);
|
||||
|
||||
// Disable some checks on certain game events
|
||||
Utils::Hook::Nop(0x43EC96, 9);
|
||||
Utils::Hook::Nop(0x4675C6, 9);
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "Voice.hpp"
|
||||
|
||||
#include "GSC/Script.hpp"
|
||||
#include "GSC/ScriptExtension.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
@ -11,16 +12,22 @@ namespace Components
|
||||
Dvar::Var Chat::sv_disableChat;
|
||||
Dvar::Var Chat::sv_sayName;
|
||||
|
||||
Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast<Game::dvar_t**>(0x7ED398);
|
||||
Game::dvar_t** Chat::cg_chatTime = reinterpret_cast<Game::dvar_t**>(0x9F5DE8);
|
||||
|
||||
bool Chat::SendChat;
|
||||
|
||||
Utils::Concurrency::Container<Chat::muteList> Chat::MutedList;
|
||||
const char* Chat::MutedListFile = "userraw/muted-users.json";
|
||||
|
||||
bool Chat::CanAddCallback = true;
|
||||
std::vector<Scripting::Function> Chat::SayCallbacks;
|
||||
|
||||
// Have only one instance of IW4x read/write the file
|
||||
std::unique_lock<Utils::NamedMutex> Chat::Lock()
|
||||
{
|
||||
static Utils::NamedMutex mutex{"iw4x-mute-list-lock"};
|
||||
std::unique_lock lock{mutex};
|
||||
return lock;
|
||||
}
|
||||
|
||||
const char* Chat::EvaluateSay(char* text, Game::gentity_t* player, int mode)
|
||||
{
|
||||
SendChat = true;
|
||||
@ -59,6 +66,19 @@ namespace Components
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65));
|
||||
}
|
||||
|
||||
if (sv_disableChat.get<bool>())
|
||||
{
|
||||
SendChat = false;
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
||||
}
|
||||
|
||||
// Message might be empty after processing the '/'
|
||||
if (text[msgIndex] == '\0')
|
||||
{
|
||||
SendChat = false;
|
||||
return text;
|
||||
}
|
||||
|
||||
for (const auto& callback : SayCallbacks)
|
||||
{
|
||||
if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode))
|
||||
@ -67,12 +87,6 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
if (sv_disableChat.get<bool>())
|
||||
{
|
||||
SendChat = false;
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
||||
}
|
||||
|
||||
TextRenderer::StripMaterialTextIcons(text, text, std::strlen(text) + 1);
|
||||
|
||||
Game::Scr_AddEntity(player);
|
||||
@ -167,9 +181,9 @@ namespace Components
|
||||
// Text can only be 150 characters maximum. This is bigger than the teamChatMsgs buffers with 160 characters
|
||||
// Therefore it is not needed to check for buffer lengths
|
||||
|
||||
const auto chatHeight = (*cg_chatHeight)->current.integer;
|
||||
const auto chatHeight = (*Game::cg_chatHeight)->current.integer;
|
||||
const auto chatWidth = static_cast<float>(cg_chatWidth.get<int>());
|
||||
const auto chatTime = (*cg_chatTime)->current.integer;
|
||||
const auto chatTime = (*Game::cg_chatTime)->current.integer;
|
||||
if (chatHeight <= 0 || static_cast<unsigned>(chatHeight) > std::extent_v<decltype(Game::cgs_t::teamChatMsgs)> || chatWidth <= 0 || chatTime <= 0)
|
||||
{
|
||||
Game::cgsArray[0].teamLastChatPos = 0;
|
||||
@ -179,7 +193,7 @@ namespace Components
|
||||
|
||||
TextRenderer::FontIconInfo fontIconInfo{};
|
||||
auto len = 0.0f;
|
||||
auto lastColor = static_cast<int>(TEXT_COLOR_DEFAULT);
|
||||
auto lastColor = static_cast<std::underlying_type_t<TextColor>>(TextColor::TEXT_COLOR_DEFAULT);
|
||||
char* lastSpace = nullptr;
|
||||
char* lastFontIcon = nullptr;
|
||||
char* p = Game::cgsArray[0].teamChatMsgs[Game::cgsArray[0].teamChatPos % chatHeight];
|
||||
@ -232,7 +246,9 @@ namespace Components
|
||||
|
||||
Game::cgsArray[0].teamChatPos++;
|
||||
if (Game::cgsArray[0].teamChatPos - Game::cgsArray[0].teamLastChatPos > chatHeight)
|
||||
{
|
||||
Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight;
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Chat::CG_AddToTeamChat_Stub()
|
||||
@ -255,7 +271,20 @@ namespace Components
|
||||
const auto clientNum = ent - Game::g_entities;
|
||||
const auto xuid = Game::svs_clients[clientNum].steamID;
|
||||
|
||||
const auto result = MutedList.access<bool>([&](muteList& clients)
|
||||
const auto result = MutedList.access<bool>([&](const muteList& clients)
|
||||
{
|
||||
return clients.contains(xuid);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Chat::IsMuted(const Game::client_t* cl)
|
||||
{
|
||||
const auto clientNum = cl - Game::svs_clients;
|
||||
const auto xuid = Game::svs_clients[clientNum].steamID;
|
||||
|
||||
const auto result = MutedList.access<bool>([&](const muteList& clients)
|
||||
{
|
||||
return clients.contains(xuid);
|
||||
});
|
||||
@ -269,6 +298,7 @@ namespace Components
|
||||
MutedList.access([&](muteList& clients)
|
||||
{
|
||||
clients.insert(xuid);
|
||||
SaveMutedList(clients);
|
||||
});
|
||||
|
||||
Logger::Print("{} was muted\n", client->name);
|
||||
@ -291,6 +321,67 @@ namespace Components
|
||||
clients.clear();
|
||||
else
|
||||
clients.erase(id);
|
||||
|
||||
SaveMutedList(clients);
|
||||
});
|
||||
}
|
||||
|
||||
void Chat::SaveMutedList(const muteList& list)
|
||||
{
|
||||
const auto _ = Lock();
|
||||
|
||||
const nlohmann::json mutedUsers = nlohmann::json
|
||||
{
|
||||
{ "SteamID", list },
|
||||
};
|
||||
|
||||
Utils::IO::WriteFile(MutedListFile, mutedUsers.dump());
|
||||
}
|
||||
|
||||
void Chat::LoadMutedList()
|
||||
{
|
||||
const auto _ = Lock();
|
||||
|
||||
const auto mutedUsers = Utils::IO::ReadFile(MutedListFile);
|
||||
if (mutedUsers.empty())
|
||||
{
|
||||
Logger::Debug("muted-users.json does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json mutedUsersData;
|
||||
try
|
||||
{
|
||||
mutedUsersData = nlohmann::json::parse(mutedUsers);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mutedUsersData.contains("SteamID"))
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "muted-users.json contains invalid data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& list = mutedUsersData["SteamID"];
|
||||
if (!list.is_array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MutedList.access([&](muteList& clients)
|
||||
{
|
||||
const nlohmann::json::array_t arr = list;
|
||||
for (auto& entry : arr)
|
||||
{
|
||||
if (entry.is_number_unsigned())
|
||||
{
|
||||
clients.insert(entry.get<std::uint64_t>());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -312,7 +403,7 @@ namespace Components
|
||||
}
|
||||
|
||||
const auto* client = Game::SV_GetPlayerByNum();
|
||||
if (client != nullptr)
|
||||
if (client && !client->bIsTestClient)
|
||||
{
|
||||
Voice::SV_MuteClient(client - Game::svs_clients);
|
||||
MuteClient(client);
|
||||
@ -335,8 +426,12 @@ namespace Components
|
||||
}
|
||||
|
||||
const auto* client = Game::SV_GetPlayerByNum();
|
||||
if (client->bIsTestClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (client != nullptr)
|
||||
if (client)
|
||||
{
|
||||
UnmuteClient(client);
|
||||
Voice::SV_UnmuteClient(client - Game::svs_clients);
|
||||
@ -466,42 +561,46 @@ namespace Components
|
||||
|
||||
int Chat::ChatCallback(Game::gentity_s* self, const char* codePos, const char* message, int mode)
|
||||
{
|
||||
const auto entityId = Game::Scr_GetEntityId(self - Game::g_entities, 0);
|
||||
constexpr auto paramcount = 2;
|
||||
|
||||
Scripting::StackIsolation _;
|
||||
Game::Scr_AddInt(mode);
|
||||
Game::Scr_AddString(message);
|
||||
|
||||
Game::VariableValue value;
|
||||
value.type = Game::VAR_OBJECT;
|
||||
value.u.uintValue = entityId;
|
||||
const auto objId = Game::Scr_GetEntityId(self - Game::g_entities, 0);
|
||||
Game::AddRefToObject(objId);
|
||||
const auto id = Game::VM_Execute_0(Game::AllocThread(objId), codePos, paramcount);
|
||||
|
||||
Game::AddRefToValue(value.type, value.u);
|
||||
const auto localId = Game::AllocThread(entityId);
|
||||
const auto result = GetCallbackReturn();
|
||||
|
||||
const auto result = Game::VM_Execute_0(localId, codePos, 2);
|
||||
Game::RemoveRefToObject(result);
|
||||
Game::RemoveRefToValue(Game::scrVmPub->top->type, Game::scrVmPub->top->u);
|
||||
|
||||
return GetCallbackReturn();
|
||||
Game::scrVmPub->top->type = Game::VAR_UNDEFINED;
|
||||
--Game::scrVmPub->top;
|
||||
--Game::scrVmPub->inparamcount;
|
||||
|
||||
Game::Scr_FreeThread(static_cast<std::uint16_t>(id));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Chat::AddScriptFunctions()
|
||||
{
|
||||
Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay(<function>)
|
||||
GSC::Script::AddFunction("OnPlayerSay", [] // gsc: OnPlayerSay(<function>)
|
||||
{
|
||||
if (Game::Scr_GetNumParam() != 1)
|
||||
{
|
||||
Game::Scr_Error("^1OnPlayerSay: Needs one function pointer!\n");
|
||||
Game::Scr_Error("OnPlayerSay: Needs one function pointer!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanAddCallback)
|
||||
{
|
||||
Game::Scr_Error("^1OnPlayerSay: Cannot add a callback in this context");
|
||||
Game::Scr_Error("OnPlayerSay: Cannot add a callback in this context");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* func = Script::GetCodePosForParam(0);
|
||||
const auto* func = GSC::ScriptExtension::GetCodePosForParam(0);
|
||||
SayCallbacks.emplace_back(func);
|
||||
});
|
||||
}
|
||||
@ -514,6 +613,8 @@ namespace Components
|
||||
sv_disableChat = Dvar::Register<bool>("sv_disableChat", false, Game::DVAR_NONE, "Disable chat messages from clients");
|
||||
Events::OnSVInit(AddChatCommands);
|
||||
|
||||
LoadMutedList();
|
||||
|
||||
// Intercept chat sending
|
||||
Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick();
|
||||
|
@ -8,23 +8,25 @@ namespace Components
|
||||
public:
|
||||
Chat();
|
||||
|
||||
static bool IsMuted(const Game::gentity_s* ent);
|
||||
static bool IsMuted(const Game::client_t* cl);
|
||||
|
||||
private:
|
||||
static Dvar::Var cg_chatWidth;
|
||||
static Dvar::Var sv_disableChat;
|
||||
static Dvar::Var sv_sayName;
|
||||
|
||||
// Game dvars
|
||||
static Game::dvar_t** cg_chatHeight;
|
||||
static Game::dvar_t** cg_chatTime;
|
||||
|
||||
static bool SendChat;
|
||||
|
||||
using muteList = std::unordered_set<std::uint64_t>;
|
||||
static Utils::Concurrency::Container<muteList> MutedList;
|
||||
static const char* MutedListFile;
|
||||
|
||||
static bool CanAddCallback; // ClientCommand & GSC thread are the same
|
||||
static std::vector<Scripting::Function> SayCallbacks;
|
||||
|
||||
static std::unique_lock<Utils::NamedMutex> Lock();
|
||||
|
||||
static const char* EvaluateSay(char* text, Game::gentity_t* player, int mode);
|
||||
|
||||
static void PreSayStub();
|
||||
@ -34,10 +36,12 @@ namespace Components
|
||||
static void CG_AddToTeamChat(const char* text);
|
||||
static void CG_AddToTeamChat_Stub();
|
||||
|
||||
static bool IsMuted(const Game::gentity_s* ent);
|
||||
static void MuteClient(const Game::client_t* client);
|
||||
static void UnmuteClient(const Game::client_t* client);
|
||||
static void UnmuteInternal(std::uint64_t id, bool everyone = false);
|
||||
static void SaveMutedList(const muteList& list);
|
||||
static void LoadMutedList();
|
||||
|
||||
static void AddChatCommands();
|
||||
|
||||
static int GetCallbackReturn();
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Game::dvar_t* ClanTags::ClanName;
|
||||
const Game::dvar_t* ClanTags::ClanName;
|
||||
|
||||
// bgs_t and clientState_s do not have this
|
||||
char ClanTags::ClientState[Game::MAX_CLIENTS][5];
|
||||
@ -28,7 +28,7 @@ namespace Components
|
||||
|
||||
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
|
||||
{
|
||||
list.append(std::format("\\{}\\{}", std::to_string(i), ClientState[i]));
|
||||
list.append(std::format("\\{}\\{}", i, ClientState[i]));
|
||||
}
|
||||
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} clanNames \"{}\"", 22, list));
|
||||
@ -118,7 +118,7 @@ namespace Components
|
||||
{
|
||||
AssertIn(clientNum, Game::MAX_CLIENTS);
|
||||
|
||||
auto* clanAbbrev = Game::Info_ValueForKey(s, "clanAbbrev");
|
||||
const auto* clanAbbrev = Game::Info_ValueForKey(s, "clanAbbrev");
|
||||
|
||||
if (clanAbbrev[0] == '\0')
|
||||
{
|
||||
@ -193,11 +193,11 @@ namespace Components
|
||||
|
||||
void __declspec(naked) ClanTags::PlayerCards_SetCachedPlayerData_Stub()
|
||||
{
|
||||
static DWORD func = 0x4D6F80; // I_strncpyz
|
||||
using namespace Game;
|
||||
|
||||
__asm
|
||||
{
|
||||
call func
|
||||
call I_strncpyz
|
||||
add esp, 0xC
|
||||
|
||||
mov byte ptr [esi + 0x3C], 0x0
|
||||
@ -235,8 +235,7 @@ namespace Components
|
||||
{
|
||||
Events::OnClientInit([]
|
||||
{
|
||||
ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE,
|
||||
"Your clan abbreviation");
|
||||
ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE, "Your clan abbreviation");
|
||||
});
|
||||
|
||||
std::memset(&ClientState, 0, sizeof(char[Game::MAX_CLIENTS][5]));
|
||||
|
@ -14,7 +14,7 @@ namespace Components
|
||||
static void CL_SanitizeClanName();
|
||||
|
||||
private:
|
||||
static Game::dvar_t* ClanName;
|
||||
static const Game::dvar_t* ClanName;
|
||||
|
||||
static const char* dvarNameList[];
|
||||
|
||||
|
@ -66,12 +66,12 @@ namespace Components
|
||||
if (!CheatsOk(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP;
|
||||
ent->client->flags ^= Game::PF_NOCLIP;
|
||||
|
||||
const auto entNum = ent->s.number;
|
||||
Logger::Debug("Noclip toggled for entity {}", entNum);
|
||||
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
|
||||
});
|
||||
|
||||
Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
@ -79,12 +79,12 @@ namespace Components
|
||||
if (!CheatsOk(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= Game::PLAYER_FLAG_UFO;
|
||||
ent->client->flags ^= Game::PF_UFO;
|
||||
|
||||
const auto entNum = ent->s.number;
|
||||
Logger::Debug("UFO toggled for entity {}", entNum);
|
||||
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
|
||||
});
|
||||
|
||||
Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
@ -363,7 +363,7 @@ namespace Components
|
||||
|
||||
void ClientCommand::AddScriptFunctions()
|
||||
{
|
||||
Script::AddFunction("DropAllBots", [] // gsc: DropAllBots();
|
||||
GSC::Script::AddFunction("DropAllBots", [] // gsc: DropAllBots();
|
||||
{
|
||||
Game::SV_DropAllBots();
|
||||
});
|
||||
|
@ -24,12 +24,12 @@ namespace Components
|
||||
assert(Game::cmd_args->nesting < Game::CMD_MAX_NESTING);
|
||||
}
|
||||
|
||||
int Command::ClientParams::size() const
|
||||
int Command::ClientParams::size() const noexcept
|
||||
{
|
||||
return Game::cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* Command::ClientParams::get(const int index) const
|
||||
const char* Command::ClientParams::get(const int index) const noexcept
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
@ -45,12 +45,12 @@ namespace Components
|
||||
assert(Game::sv_cmd_args->nesting < Game::CMD_MAX_NESTING);
|
||||
}
|
||||
|
||||
int Command::ServerParams::size() const
|
||||
int Command::ServerParams::size() const noexcept
|
||||
{
|
||||
return Game::sv_cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* Command::ServerParams::get(const int index) const
|
||||
const char* Command::ServerParams::get(const int index) const noexcept
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
@ -62,42 +62,40 @@ namespace Components
|
||||
|
||||
void Command::Add(const char* name, const std::function<void()>& callback)
|
||||
{
|
||||
Add(name, [callback]([[maybe_unused]] const Command::Params* params)
|
||||
Add(name, [callback]([[maybe_unused]] const Params* params)
|
||||
{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
void Command::Add(const char* name, const std::function<void(Command::Params*)>& callback)
|
||||
void Command::Add(const char* name, const std::function<void(Params*)>& callback)
|
||||
{
|
||||
const auto command = Utils::String::ToLower(name);
|
||||
|
||||
if (!Command::FunctionMap.contains(command))
|
||||
if (!FunctionMap.contains(command))
|
||||
{
|
||||
Command::AddRaw(name, Command::MainCallback);
|
||||
AddRaw(name, MainCallback);
|
||||
}
|
||||
|
||||
Command::FunctionMap.insert_or_assign(command, std::move(callback));
|
||||
FunctionMap.insert_or_assign(command, callback);
|
||||
}
|
||||
|
||||
void Command::AddSV(const char* name, const std::function<void(Command::Params*)>& callback)
|
||||
void Command::AddSV(const char* name, const std::function<void(Params*)>& callback)
|
||||
{
|
||||
if (Loader::IsPregame())
|
||||
{
|
||||
MessageBoxA(nullptr, "Registering server commands in pregame state is illegal!", nullptr, MB_ICONERROR);
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef _DEBUG
|
||||
__debugbreak();
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto command = Utils::String::ToLower(name);
|
||||
|
||||
if (!Command::FunctionMapSV.contains(command))
|
||||
if (!FunctionMapSV.contains(command))
|
||||
{
|
||||
Command::AddRawSV(name, Command::MainCallbackSV);
|
||||
AddRawSV(name, MainCallbackSV);
|
||||
}
|
||||
|
||||
FunctionMapSV.insert_or_assign(command, callback);
|
||||
@ -105,21 +103,23 @@ namespace Components
|
||||
|
||||
void Command::AddRaw(const char* name, void(*callback)(), bool key)
|
||||
{
|
||||
Game::Cmd_AddCommand(name, callback, Command::Allocate(), key);
|
||||
Game::Cmd_AddCommand(name, callback, Allocate(), key);
|
||||
}
|
||||
|
||||
void Command::AddRawSV(const char* name, void(*callback)())
|
||||
{
|
||||
Game::Cmd_AddServerCommand(name, callback, Command::Allocate());
|
||||
Game::Cmd_AddServerCommand(name, callback, Allocate());
|
||||
|
||||
// If the main command is registered as Cbuf_AddServerText, the command will be redirected to the SV handler
|
||||
Command::AddRaw(name, Game::Cbuf_AddServerText_f, false);
|
||||
AddRaw(name, Game::Cbuf_AddServerText_f, false);
|
||||
}
|
||||
|
||||
void Command::Execute(std::string command, bool sync)
|
||||
{
|
||||
command.append("\n"); // Make sure it's terminated
|
||||
|
||||
assert(command.size() < Game::MAX_CMD_LINE);
|
||||
|
||||
if (sync)
|
||||
{
|
||||
Game::Cmd_ExecuteSingleCommand(0, 0, command.data());
|
||||
@ -130,13 +130,13 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
Game::cmd_function_t* Command::Find(const std::string& command)
|
||||
Game::cmd_function_s* Command::Find(const std::string& command)
|
||||
{
|
||||
auto* cmdFunction = *Game::cmd_functions;
|
||||
|
||||
while (cmdFunction != nullptr)
|
||||
while (cmdFunction)
|
||||
{
|
||||
if (cmdFunction->name != nullptr && cmdFunction->name == command)
|
||||
if (cmdFunction->name && Utils::String::Compare(cmdFunction->name, command))
|
||||
{
|
||||
return cmdFunction;
|
||||
}
|
||||
@ -147,9 +147,9 @@ namespace Components
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Game::cmd_function_t* Command::Allocate()
|
||||
Game::cmd_function_s* Command::Allocate()
|
||||
{
|
||||
return Utils::Memory::GetAllocator()->allocate<Game::cmd_function_t>();
|
||||
return Utils::Memory::GetAllocator()->allocate<Game::cmd_function_s>();
|
||||
}
|
||||
|
||||
void Command::MainCallback()
|
||||
@ -173,4 +173,52 @@ namespace Components
|
||||
itr->second(¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Command::GetExceptions()
|
||||
{
|
||||
static const auto exceptions = []() -> std::vector<std::string>
|
||||
{
|
||||
std::vector<std::string> values =
|
||||
{
|
||||
"cmd",
|
||||
"exec",
|
||||
"map",
|
||||
};
|
||||
|
||||
if (Flags::HasFlag("disable-notifies"))
|
||||
{
|
||||
values.emplace_back("vstr");
|
||||
values.emplace_back("wait");
|
||||
}
|
||||
|
||||
return values;
|
||||
}();
|
||||
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
bool Command::CL_ShouldSendNotify_Hk(const char* cmd)
|
||||
{
|
||||
if (!cmd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& exceptions = GetExceptions();
|
||||
for (const auto& entry : exceptions)
|
||||
{
|
||||
if (Utils::String::Compare(cmd, entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Command::Command()
|
||||
{
|
||||
// Protect players from invasive servers
|
||||
Utils::Hook(0x434BD4, CL_ShouldSendNotify_Hk, HOOK_CALL).install()->quick(); // CL_CheckNotify
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ namespace Components
|
||||
class Command : public Component
|
||||
{
|
||||
public:
|
||||
static_assert(sizeof(Game::cmd_function_t) == 0x18);
|
||||
static_assert(sizeof(Game::cmd_function_s) == 0x18);
|
||||
|
||||
class Params
|
||||
{
|
||||
@ -13,8 +13,13 @@ namespace Components
|
||||
Params() = default;
|
||||
virtual ~Params() = default;
|
||||
|
||||
[[nodiscard]] virtual int size() const = 0;
|
||||
[[nodiscard]] virtual const char* get(int index) const = 0;
|
||||
Params(Params&&) = delete;
|
||||
Params(const Params&) = delete;
|
||||
Params& operator=(Params&&) = delete;
|
||||
Params& operator=(const Params&) = delete;
|
||||
|
||||
[[nodiscard]] virtual int size() const noexcept = 0;
|
||||
[[nodiscard]] virtual const char* get(int index) const noexcept = 0;
|
||||
[[nodiscard]] virtual std::string join(int index) const;
|
||||
|
||||
virtual const char* operator[](const int index)
|
||||
@ -28,8 +33,8 @@ namespace Components
|
||||
public:
|
||||
ClientParams();
|
||||
|
||||
[[nodiscard]] int size() const override;
|
||||
[[nodiscard]] const char* get(int index) const override;
|
||||
[[nodiscard]] int size() const noexcept override;
|
||||
[[nodiscard]] const char* get(int index) const noexcept override;
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
@ -40,16 +45,16 @@ namespace Components
|
||||
public:
|
||||
ServerParams();
|
||||
|
||||
[[nodiscard]] int size() const override;
|
||||
[[nodiscard]] const char* get(int index) const override;
|
||||
[[nodiscard]] int size() const noexcept override;
|
||||
[[nodiscard]] const char* get(int index) const noexcept override;
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
};
|
||||
|
||||
Command() = default;
|
||||
Command();
|
||||
|
||||
static Game::cmd_function_t* Allocate();
|
||||
static Game::cmd_function_s* Allocate();
|
||||
|
||||
static void Add(const char* name, const std::function<void()>& callback);
|
||||
static void Add(const char* name, const std::function<void(Command::Params*)>& callback);
|
||||
@ -57,7 +62,7 @@ namespace Components
|
||||
static void AddSV(const char* name, const std::function<void(Command::Params*)>& callback);
|
||||
static void Execute(std::string command, bool sync = true);
|
||||
|
||||
static Game::cmd_function_t* Find(const std::string& command);
|
||||
static Game::cmd_function_s* Find(const std::string& command);
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string, std::function<void(Command::Params*)>> FunctionMap;
|
||||
@ -67,5 +72,8 @@ namespace Components
|
||||
|
||||
static void MainCallback();
|
||||
static void MainCallbackSV();
|
||||
|
||||
static const std::vector<std::string>& GetExceptions();
|
||||
static bool CL_ShouldSendNotify_Hk(const char* cmd);
|
||||
};
|
||||
}
|
||||
|
@ -200,7 +200,8 @@ namespace Components
|
||||
{
|
||||
if (Used())
|
||||
{
|
||||
Command::Execute(std::format("connect {}", ConnectString), false);
|
||||
const auto* cmd = Utils::String::Format("connect {}", ConnectString);
|
||||
Command::Execute(cmd, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,7 +212,8 @@ namespace Components
|
||||
// IPC handler
|
||||
IPCPipe::On("connect", [](const std::string& data)
|
||||
{
|
||||
Command::Execute(std::format("connect {}", data), false);
|
||||
const auto* cmd = Utils::String::Format("connect {}", data);
|
||||
Command::Execute(cmd, false);
|
||||
});
|
||||
|
||||
// Invocation handler
|
||||
@ -227,7 +229,7 @@ namespace Components
|
||||
if (!Singleton::IsFirstInstance())
|
||||
{
|
||||
IPCPipe::Write("connect", ConnectString);
|
||||
ExitProcess(0);
|
||||
ExitProcess(EXIT_SUCCESS);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -237,7 +239,7 @@ namespace Components
|
||||
Scheduler::Once([]
|
||||
{
|
||||
Command::Execute("openmenu popup_reconnectingtoparty", false);
|
||||
}, Scheduler::Pipeline::CLIENT, 8s);
|
||||
}, Scheduler::Pipeline::MAIN, 8s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Console.hpp"
|
||||
|
||||
#include "Terminus_4.49.1.ttf.hpp"
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
#ifdef MOUSE_MOVED
|
||||
@ -32,14 +34,14 @@ namespace Components
|
||||
bool Console::SkipShutdown = false;
|
||||
|
||||
COLORREF Console::TextColor =
|
||||
#if DEBUG
|
||||
#if _DEBUG
|
||||
RGB(255, 200, 117);
|
||||
#else
|
||||
RGB(120, 237, 122);
|
||||
#endif
|
||||
|
||||
COLORREF Console::BackgroundColor =
|
||||
#if DEBUG
|
||||
#if _DEBUG
|
||||
RGB(35, 21, 0);
|
||||
#else
|
||||
RGB(25, 32, 25);
|
||||
@ -64,16 +66,16 @@ namespace Components
|
||||
const std::string mapname = (*Game::sv_mapname)->current.string;
|
||||
const auto hostname = TextRenderer::StripColors((*Game::sv_hostname)->current.string);
|
||||
|
||||
if (Console::HasConsole)
|
||||
if (HasConsole)
|
||||
{
|
||||
SetConsoleTitleA(hostname.data());
|
||||
|
||||
auto clientCount = 0;
|
||||
auto maxclientCount = *Game::svs_clientCount;
|
||||
auto maxClientCount = *Game::svs_clientCount;
|
||||
|
||||
if (maxclientCount)
|
||||
if (maxClientCount)
|
||||
{
|
||||
for (int i = 0; i < maxclientCount; ++i)
|
||||
for (auto i = 0; i < maxClientCount; ++i)
|
||||
{
|
||||
if (Game::svs_clients[i].header.state >= Game::CS_CONNECTED)
|
||||
{
|
||||
@ -83,18 +85,17 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
maxclientCount = Dvar::Var("party_maxplayers").get<int>();
|
||||
//maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame);
|
||||
clientCount = Game::PartyHost_CountMembers(reinterpret_cast<Game::PartyData*>(0x1081C00));
|
||||
maxClientCount = *Game::party_maxplayers ? (*Game::party_maxplayers)->current.integer : 18;
|
||||
clientCount = Game::PartyHost_CountMembers(Game::g_lobbyData);
|
||||
}
|
||||
|
||||
wclear(InfoWindow);
|
||||
wprintw(InfoWindow, "%s : %d/%d players : map %s", hostname.data(), clientCount, maxclientCount, (!mapname.empty()) ? mapname.data() : "none");
|
||||
wprintw(InfoWindow, "%s : %d/%d players : map %s", hostname.data(), clientCount, maxClientCount, (!mapname.empty()) ? mapname.data() : "none");
|
||||
wnoutrefresh(InfoWindow);
|
||||
}
|
||||
else if (IsWindow(Console::GetWindow()) != FALSE)
|
||||
else if (IsWindow(GetWindow()) != FALSE)
|
||||
{
|
||||
SetWindowTextA(Console::GetWindow(), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data()));
|
||||
SetWindowTextA(GetWindow(), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,35 +107,35 @@ namespace Components
|
||||
|
||||
void Console::RefreshOutput()
|
||||
{
|
||||
prefresh(OutputWindow, ((Console::OutputTop > 0) ? (Console::OutputTop - 1) : 0), 0, 1, 0, Console::Height - 2, Console::Width - 1);
|
||||
prefresh(OutputWindow, ((OutputTop > 0) ? (OutputTop - 1) : 0), 0, 1, 0, Height - 2, Width - 1);
|
||||
}
|
||||
|
||||
void Console::ScrollOutput(int amount)
|
||||
{
|
||||
Console::OutputTop += amount;
|
||||
OutputTop += amount;
|
||||
|
||||
if (Console::OutputTop > OUTPUT_MAX_TOP)
|
||||
if (OutputTop > OUTPUT_MAX_TOP)
|
||||
{
|
||||
Console::OutputTop = OUTPUT_MAX_TOP;
|
||||
OutputTop = OUTPUT_MAX_TOP;
|
||||
}
|
||||
else if (Console::OutputTop < 0)
|
||||
else if (OutputTop < 0)
|
||||
{
|
||||
Console::OutputTop = 0;
|
||||
OutputTop = 0;
|
||||
}
|
||||
|
||||
// make it only scroll the top if there's more than HEIGHT lines
|
||||
if (Console::OutBuffer >= 0)
|
||||
if (OutBuffer >= 0)
|
||||
{
|
||||
Console::OutBuffer += amount;
|
||||
OutBuffer += amount;
|
||||
|
||||
if (Console::OutBuffer >= Console::Height)
|
||||
if (OutBuffer >= Height)
|
||||
{
|
||||
Console::OutBuffer = -1;
|
||||
OutBuffer = -1;
|
||||
}
|
||||
|
||||
if (Console::OutputTop < Console::Height)
|
||||
if (OutputTop < Height)
|
||||
{
|
||||
Console::OutputTop = 0;
|
||||
OutputTop = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,17 +150,13 @@ namespace Components
|
||||
|
||||
if (getDpiForWindow)
|
||||
{
|
||||
dpi = getDpiForWindow(hWnd);
|
||||
dpi = static_cast<int>(getDpiForWindow(hWnd));
|
||||
}
|
||||
else if (getDpiForMonitor)
|
||||
{
|
||||
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
|
||||
UINT xdpi, ydpi;
|
||||
LRESULT success = getDpiForMonitor(hMonitor, 0, &xdpi, &ydpi);
|
||||
if (success == S_OK)
|
||||
{
|
||||
dpi = static_cast<int>(ydpi);
|
||||
}
|
||||
getDpiForMonitor(hMonitor, 0, &xdpi, &ydpi);
|
||||
|
||||
dpi = 96;
|
||||
}
|
||||
@ -167,33 +164,33 @@ namespace Components
|
||||
{
|
||||
HDC hDC = GetDC(hWnd);
|
||||
INT ydpi = GetDeviceCaps(hDC, LOGPIXELSY);
|
||||
ReleaseDC(NULL, hDC);
|
||||
ReleaseDC(nullptr, hDC);
|
||||
|
||||
dpi = ydpi;
|
||||
}
|
||||
|
||||
constexpr auto unawareDpi = 96.0f;
|
||||
return dpi / unawareDpi;
|
||||
return static_cast<float>(dpi) / unawareDpi;
|
||||
}
|
||||
|
||||
|
||||
const char* Console::Input()
|
||||
{
|
||||
if (!Console::HasConsole)
|
||||
if (!HasConsole)
|
||||
{
|
||||
Console::ShowPrompt();
|
||||
ShowPrompt();
|
||||
wrefresh(InputWindow);
|
||||
Console::HasConsole = true;
|
||||
HasConsole = true;
|
||||
}
|
||||
|
||||
int currentTime = static_cast<int>(GetTickCount64()); // Make our compiler happy
|
||||
if ((currentTime - Console::LastRefresh) > 250)
|
||||
auto currentTime = static_cast<int>(GetTickCount64()); // Make our compiler happy
|
||||
if ((currentTime - LastRefresh) > 250)
|
||||
{
|
||||
Console::RefreshOutput();
|
||||
Console::LastRefresh = currentTime;
|
||||
RefreshOutput();
|
||||
LastRefresh = currentTime;
|
||||
}
|
||||
|
||||
int c = wgetch(InputWindow);
|
||||
auto c = wgetch(InputWindow);
|
||||
|
||||
if (c == ERR)
|
||||
{
|
||||
@ -208,28 +205,28 @@ namespace Components
|
||||
wattron(OutputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||
wprintw(OutputWindow, "%s", "]");
|
||||
|
||||
if (Console::LineBufferIndex)
|
||||
if (LineBufferIndex)
|
||||
{
|
||||
wprintw(OutputWindow, "%s", Console::LineBuffer);
|
||||
wprintw(OutputWindow, "%s", LineBuffer);
|
||||
}
|
||||
|
||||
wprintw(OutputWindow, "%s", "\n");
|
||||
wattroff(OutputWindow, A_BOLD);
|
||||
wclear(InputWindow);
|
||||
|
||||
Console::ShowPrompt();
|
||||
ShowPrompt();
|
||||
|
||||
wrefresh(InputWindow);
|
||||
|
||||
Console::ScrollOutput(1);
|
||||
Console::RefreshOutput();
|
||||
ScrollOutput(1);
|
||||
RefreshOutput();
|
||||
|
||||
if (Console::LineBufferIndex)
|
||||
if (LineBufferIndex)
|
||||
{
|
||||
strcpy_s(Console::LineBuffer2, Console::LineBuffer);
|
||||
strcat_s(Console::LineBuffer, "\n");
|
||||
Console::LineBufferIndex = 0;
|
||||
return Console::LineBuffer;
|
||||
strcpy_s(LineBuffer2, LineBuffer);
|
||||
strcat_s(LineBuffer, "\n");
|
||||
LineBufferIndex = 0;
|
||||
return LineBuffer;
|
||||
}
|
||||
|
||||
break;
|
||||
@ -237,22 +234,22 @@ namespace Components
|
||||
case 'c' - 'a' + 1: // ctrl-c
|
||||
case 27:
|
||||
{
|
||||
Console::LineBuffer[0] = '\0';
|
||||
Console::LineBufferIndex = 0;
|
||||
LineBuffer[0] = '\0';
|
||||
LineBufferIndex = 0;
|
||||
|
||||
wclear(InputWindow);
|
||||
|
||||
Console::ShowPrompt();
|
||||
ShowPrompt();
|
||||
|
||||
wrefresh(InputWindow);
|
||||
break;
|
||||
}
|
||||
case 8: // backspace
|
||||
{
|
||||
if (Console::LineBufferIndex > 0)
|
||||
if (LineBufferIndex > 0)
|
||||
{
|
||||
Console::LineBufferIndex--;
|
||||
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
||||
LineBufferIndex--;
|
||||
LineBuffer[LineBufferIndex] = '\0';
|
||||
|
||||
wprintw(InputWindow, "%c %c", static_cast<char>(c), static_cast<char>(c));
|
||||
wrefresh(InputWindow);
|
||||
@ -261,35 +258,35 @@ namespace Components
|
||||
}
|
||||
case KEY_PPAGE:
|
||||
{
|
||||
Console::ScrollOutput(-1);
|
||||
Console::RefreshOutput();
|
||||
ScrollOutput(-1);
|
||||
RefreshOutput();
|
||||
break;
|
||||
}
|
||||
case KEY_NPAGE:
|
||||
{
|
||||
Console::ScrollOutput(1);
|
||||
Console::RefreshOutput();
|
||||
ScrollOutput(1);
|
||||
RefreshOutput();
|
||||
break;
|
||||
}
|
||||
case KEY_UP:
|
||||
{
|
||||
wclear(InputWindow);
|
||||
Console::ShowPrompt();
|
||||
wprintw(InputWindow, "%s", Console::LineBuffer2);
|
||||
ShowPrompt();
|
||||
wprintw(InputWindow, "%s", LineBuffer2);
|
||||
wrefresh(InputWindow);
|
||||
|
||||
strcpy_s(Console::LineBuffer, Console::LineBuffer2);
|
||||
Console::LineBufferIndex = strlen(Console::LineBuffer);
|
||||
strcpy_s(LineBuffer, LineBuffer2);
|
||||
LineBufferIndex = static_cast<int>(std::strlen(LineBuffer));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (c <= 127 && Console::LineBufferIndex < 1022)
|
||||
if (c <= 127 && LineBufferIndex < 1022)
|
||||
{
|
||||
// temporary workaround , find out what overwrites our index later on
|
||||
// temporary workaround, find out what overwrites our index later on
|
||||
//consoleLineBufferIndex = strlen(consoleLineBuffer);
|
||||
|
||||
Console::LineBuffer[Console::LineBufferIndex++] = static_cast<char>(c);
|
||||
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
||||
LineBuffer[LineBufferIndex++] = static_cast<char>(c);
|
||||
LineBuffer[LineBufferIndex] = '\0';
|
||||
wprintw(InputWindow, "%c", static_cast<char>(c));
|
||||
wrefresh(InputWindow);
|
||||
}
|
||||
@ -318,31 +315,31 @@ namespace Components
|
||||
|
||||
void Console::Create()
|
||||
{
|
||||
Console::OutputTop = 0;
|
||||
Console::OutBuffer = 0;
|
||||
Console::LastRefresh = 0;
|
||||
Console::LineBufferIndex = 0;
|
||||
Console::HasConsole = false;
|
||||
OutputTop = 0;
|
||||
OutBuffer = 0;
|
||||
LastRefresh = 0;
|
||||
LineBufferIndex = 0;
|
||||
HasConsole = false;
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info))
|
||||
{
|
||||
Console::Width = info.dwSize.X;
|
||||
Console::Height = info.srWindow.Bottom - info.srWindow.Top + 1;
|
||||
Width = info.dwSize.X;
|
||||
Height = info.srWindow.Bottom - info.srWindow.Top + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console::Height = 25;
|
||||
Console::Width = 80;
|
||||
Height = 25;
|
||||
Width = 80;
|
||||
}
|
||||
|
||||
initscr();
|
||||
raw();
|
||||
noecho();
|
||||
|
||||
OutputWindow = newpad(Console::Height - 1, Console::Width);
|
||||
InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
|
||||
InfoWindow = newwin(1, Console::Width, 0, 0);
|
||||
OutputWindow = newpad(Height - 1, Width);
|
||||
InputWindow = newwin(1, Width, Height - 1, 0);
|
||||
InfoWindow = newwin(1, Width, 0, 0);
|
||||
|
||||
scrollok(OutputWindow, true);
|
||||
idlok(OutputWindow, true);
|
||||
@ -370,7 +367,7 @@ namespace Components
|
||||
wrefresh(InfoWindow);
|
||||
wrefresh(InputWindow);
|
||||
|
||||
Console::RefreshOutput();
|
||||
RefreshOutput();
|
||||
}
|
||||
|
||||
void Console::Error(const char* fmt, ...)
|
||||
@ -384,7 +381,7 @@ namespace Components
|
||||
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "{}\n", buf);
|
||||
|
||||
Console::RefreshOutput();
|
||||
RefreshOutput();
|
||||
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
@ -394,7 +391,7 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
TerminateProcess(GetCurrentProcess(), 0xDEADDEAD);
|
||||
TerminateProcess(GetCurrentProcess(), EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void Console::Print(const char* message)
|
||||
@ -406,11 +403,9 @@ namespace Components
|
||||
{
|
||||
if (*p == '^')
|
||||
{
|
||||
char color;
|
||||
++p;
|
||||
|
||||
color = (*p - '0');
|
||||
|
||||
const char color = (*p - '0');
|
||||
if (color < 9 && color > 0)
|
||||
{
|
||||
wattron(OutputWindow, COLOR_PAIR(color + 2));
|
||||
@ -426,26 +421,26 @@ namespace Components
|
||||
|
||||
wattron(OutputWindow, COLOR_PAIR(9));
|
||||
|
||||
Console::RefreshOutput();
|
||||
RefreshOutput();
|
||||
}
|
||||
|
||||
HFONT __stdcall Console::ReplaceFont(
|
||||
[[maybe_unused]] int cHeight,
|
||||
int cWidth,
|
||||
int cEscapement,
|
||||
int cOrientation,
|
||||
[[maybe_unused]] int cWeight,
|
||||
DWORD bItalic,
|
||||
DWORD bUnderline,
|
||||
DWORD bStrikeOut,
|
||||
DWORD iCharSet,
|
||||
[[maybe_unused]] DWORD iOutPrecision,
|
||||
DWORD iClipPrecision,
|
||||
[[maybe_unused]] DWORD iQuality,
|
||||
[[maybe_unused]] DWORD iPitchAndFamily,
|
||||
HFONT CALLBACK Console::ReplaceFont(
|
||||
[[maybe_unused]] int cHeight,
|
||||
int cWidth,
|
||||
int cEscapement,
|
||||
int cOrientation,
|
||||
[[maybe_unused]] int cWeight,
|
||||
DWORD bItalic,
|
||||
DWORD bUnderline,
|
||||
DWORD bStrikeOut,
|
||||
DWORD iCharSet,
|
||||
[[maybe_unused]] DWORD iOutPrecision,
|
||||
DWORD iClipPrecision,
|
||||
[[maybe_unused]] DWORD iQuality,
|
||||
[[maybe_unused]] DWORD iPitchAndFamily,
|
||||
[[maybe_unused]] LPCSTR pszFaceName)
|
||||
{
|
||||
auto font = CreateFontA(
|
||||
HFONT font = CreateFontA(
|
||||
12,
|
||||
cWidth,
|
||||
cEscapement,
|
||||
@ -459,7 +454,8 @@ namespace Components
|
||||
iClipPrecision,
|
||||
NONANTIALIASED_QUALITY,
|
||||
0x31,
|
||||
"Terminus (TTF)"); // Terminus (TTF)
|
||||
"Terminus (TTF)"
|
||||
); // Terminus (TTF)
|
||||
|
||||
return font;
|
||||
}
|
||||
@ -467,7 +463,7 @@ namespace Components
|
||||
void Console::GetWindowPos(HWND hWnd, int* x, int* y)
|
||||
{
|
||||
HWND hWndParent = GetParent(hWnd);
|
||||
POINT p = { 0 };
|
||||
POINT p{};
|
||||
|
||||
MapWindowPoints(hWnd, hWndParent, &p, 1);
|
||||
|
||||
@ -478,8 +474,8 @@ namespace Components
|
||||
BOOL CALLBACK Console::ResizeChildWindow(HWND hwndChild, LPARAM lParam)
|
||||
{
|
||||
auto id = GetWindowLong(hwndChild, GWL_ID);
|
||||
bool isInputBox = id == INPUT_BOX;
|
||||
bool isOutputBox = id == OUTPUT_BOX;
|
||||
auto isInputBox = id == INPUT_BOX;
|
||||
auto isOutputBox = id == OUTPUT_BOX;
|
||||
|
||||
if (isInputBox || isOutputBox)
|
||||
{
|
||||
@ -496,31 +492,31 @@ namespace Components
|
||||
|
||||
HWND parent = Utils::Hook::Get<HWND>(0x64A3288);
|
||||
|
||||
float scale = GetDpiScale(parent);
|
||||
auto scale = GetDpiScale(parent);
|
||||
|
||||
if (isInputBox)
|
||||
{
|
||||
|
||||
int newX = childX; // No change!
|
||||
int newY = static_cast<int>((newParentRect.bottom - newParentRect.top) - 65 * scale);
|
||||
int newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29 * scale);
|
||||
int newHeight = static_cast<int>((childRect.bottom - childRect.top) * scale); // No change!
|
||||
auto newX = childX; // No change!
|
||||
auto newY = static_cast<int>((newParentRect.bottom - newParentRect.top) - 65 * scale);
|
||||
auto newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29 * scale);
|
||||
auto newHeight = static_cast<int>((childRect.bottom - childRect.top) * scale); // No change!
|
||||
|
||||
MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE);
|
||||
}
|
||||
|
||||
if (isOutputBox)
|
||||
{
|
||||
int newX = childX; // No change!
|
||||
int newY = childY; // No change!
|
||||
int newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29);
|
||||
|
||||
int margin = 70;
|
||||
auto newX = childX; // No change!
|
||||
auto newY = childY; // No change!
|
||||
auto newWidth = static_cast<int>((newParentRect.right - newParentRect.left) - 29);
|
||||
|
||||
#ifdef REMOVE_HEADERBAR
|
||||
margin = 10;
|
||||
constexpr auto margin = 10;
|
||||
#else
|
||||
constexpr auto margin = 70;
|
||||
#endif
|
||||
int newHeight = static_cast<int>((newParentRect.bottom - newParentRect.top) - 74 * scale - margin);
|
||||
auto newHeight = static_cast<int>((newParentRect.bottom - newParentRect.top) - 74 * scale - margin);
|
||||
|
||||
MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE);
|
||||
}
|
||||
@ -536,23 +532,21 @@ namespace Components
|
||||
// around whenever clearing occurs.
|
||||
void Console::MakeRoomForText([[maybe_unused]] int addedCharacters)
|
||||
{
|
||||
constexpr unsigned int maxChars = 0x4000;
|
||||
constexpr unsigned int maxAffectedChars = 0x100;
|
||||
constexpr auto maxChars = 0x4000;
|
||||
constexpr auto maxAffectedChars = 0x100;
|
||||
HWND outputBox = Utils::Hook::Get<HWND>(0x64A328C);
|
||||
|
||||
unsigned int totalChars;
|
||||
unsigned int totalClearLength = 0;
|
||||
auto totalClearLength = 0;
|
||||
|
||||
char str[maxAffectedChars];
|
||||
unsigned int fetchedCharacters = static_cast<unsigned int>(GetWindowText(outputBox, str, maxAffectedChars));
|
||||
|
||||
totalChars = GetWindowTextLengthA(outputBox);
|
||||
const auto fetchedCharacters = GetWindowTextA(outputBox, str, maxAffectedChars);
|
||||
|
||||
auto totalChars = GetWindowTextLengthA(outputBox);
|
||||
while (totalChars - totalClearLength > maxChars)
|
||||
{
|
||||
unsigned int clearLength = maxAffectedChars; // Default to full clear
|
||||
auto clearLength = maxAffectedChars; // Default to full clear
|
||||
|
||||
for (size_t i = 0; i < fetchedCharacters; i++)
|
||||
for (auto i = 0; i < fetchedCharacters; i++)
|
||||
{
|
||||
if (str[i] == '\n')
|
||||
{
|
||||
@ -567,10 +561,10 @@ namespace Components
|
||||
|
||||
if (totalClearLength > 0)
|
||||
{
|
||||
SendMessage(outputBox, WM_SETREDRAW, FALSE, 0);
|
||||
SendMessage(outputBox, EM_SETSEL, 0, totalClearLength);
|
||||
SendMessage(outputBox, EM_REPLACESEL, FALSE, 0);
|
||||
SendMessage(outputBox, WM_SETREDRAW, TRUE, 0);
|
||||
SendMessageA(outputBox, WM_SETREDRAW, FALSE, 0);
|
||||
SendMessageA(outputBox, EM_SETSEL, 0, totalClearLength);
|
||||
SendMessageA(outputBox, EM_REPLACESEL, FALSE, 0);
|
||||
SendMessageA(outputBox, WM_SETREDRAW, TRUE, 0);
|
||||
}
|
||||
|
||||
Utils::Hook::Set(0x64A38B8, totalChars - totalClearLength);
|
||||
@ -596,18 +590,11 @@ namespace Components
|
||||
{
|
||||
switch (Msg)
|
||||
{
|
||||
|
||||
|
||||
case WM_CREATE:
|
||||
{
|
||||
BOOL darkMode = true;
|
||||
|
||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||
if (SUCCEEDED(DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, reinterpret_cast<LPCVOID>(&darkMode), sizeof(darkMode))))
|
||||
{
|
||||
// cool !
|
||||
}
|
||||
|
||||
BOOL darkMode = TRUE;
|
||||
constexpr auto DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
|
||||
DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &darkMode, sizeof(darkMode));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -630,7 +617,6 @@ namespace Components
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fall through to basegame
|
||||
return Utils::Hook::Call<LRESULT CALLBACK(HWND, UINT, WPARAM, unsigned int)>(0x64DC50)(hWnd, Msg, wParam, lParam);
|
||||
}
|
||||
|
||||
@ -645,14 +631,14 @@ namespace Components
|
||||
|
||||
void Console::ApplyConsoleStyle()
|
||||
{
|
||||
Utils::Hook::Set<BYTE>(0x428A8E, 0); // Adjust logo Y pos
|
||||
Utils::Hook::Set<BYTE>(0x428A90, 0); // Adjust logo X pos
|
||||
Utils::Hook::Set<BYTE>(0x428AF2, 67); // Adjust output Y pos
|
||||
Utils::Hook::Set<DWORD>(0x428AC5, 397); // Adjust input Y pos
|
||||
Utils::Hook::Set<DWORD>(0x428951, 609); // Reduce window width
|
||||
Utils::Hook::Set<DWORD>(0x42895D, 423); // Reduce window height
|
||||
Utils::Hook::Set<DWORD>(0x428AC0, 597); // Reduce input width
|
||||
Utils::Hook::Set<DWORD>(0x428AED, 596); // Reduce output width
|
||||
Utils::Hook::Set<std::uint8_t>(0x428A8E, 0); // Adjust logo Y pos
|
||||
Utils::Hook::Set<std::uint8_t>(0x428A90, 0); // Adjust logo X pos
|
||||
Utils::Hook::Set<std::uint8_t>(0x428AF2, 67); // Adjust output Y pos
|
||||
Utils::Hook::Set<std::uint32_t>(0x428AC5, 397); // Adjust input Y pos
|
||||
Utils::Hook::Set<std::uint32_t>(0x428951, 609); // Reduce window width
|
||||
Utils::Hook::Set<std::uint32_t>(0x42895D, 423); // Reduce window height
|
||||
Utils::Hook::Set<std::uint32_t>(0x428AC0, 597); // Reduce input width
|
||||
Utils::Hook::Set<std::uint32_t>(0x428AED, 596); // Reduce output width
|
||||
|
||||
DWORD fontsInstalled;
|
||||
CustomConsoleFont = AddFontMemResourceEx(const_cast<void*>(reinterpret_cast<const void*>(Font::Terminus::DATA)), Font::Terminus::LENGTH, 0, &fontsInstalled);
|
||||
@ -680,35 +666,37 @@ namespace Components
|
||||
|
||||
// Never reset text
|
||||
Utils::Hook::Nop(0x4F57DF, 0x4F57F6 - 0x4F57DF);
|
||||
Utils::Hook(0x4F57DF, Console::Sys_PrintStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4F57DF, Sys_PrintStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
}
|
||||
|
||||
void Console::ConsoleRunner()
|
||||
{
|
||||
Console::SkipShutdown = false;
|
||||
SkipShutdown = false;
|
||||
Game::Sys_ShowConsole();
|
||||
|
||||
MSG message;
|
||||
while (IsWindow(Console::GetWindow()) != FALSE && GetMessageA(&message, nullptr, 0, 0))
|
||||
while (IsWindow(GetWindow()) != FALSE && GetMessageA(&message, nullptr, 0, 0))
|
||||
{
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageA(&message);
|
||||
}
|
||||
|
||||
if (Console::SkipShutdown) return;
|
||||
if (SkipShutdown) return;
|
||||
|
||||
if (Game::Sys_Milliseconds() - Console::LastRefresh > 100 &&
|
||||
if (Game::Sys_Milliseconds() -LastRefresh > 100 &&
|
||||
MessageBoxA(nullptr, "The application is not responding anymore, do you want to force its termination?", "Application is not responding", MB_ICONEXCLAMATION | MB_YESNO) == IDYES)
|
||||
{
|
||||
// Force process termination
|
||||
// if the main thread is not responding
|
||||
OutputDebugStringA("Process termination forced, as the main thread is not responding!");
|
||||
#ifdef _DEBUG
|
||||
OutputDebugStringA("Process termination was forced as the main thread is not responding!");
|
||||
#endif
|
||||
|
||||
// We can not force the termination in this thread
|
||||
// The destructor would be called in this thread
|
||||
// and would try to join this thread, which is impossible
|
||||
TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF);
|
||||
TerminateProcess(GetCurrentProcess(), EXIT_FAILURE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -729,7 +717,7 @@ namespace Components
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
_vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
|
||||
vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
perror(buffer);
|
||||
@ -759,7 +747,7 @@ namespace Components
|
||||
void Console::StoreSafeArea()
|
||||
{
|
||||
// Backup the original safe area
|
||||
Console::OriginalSafeArea = *Game::safeArea;
|
||||
OriginalSafeArea = *Game::safeArea;
|
||||
|
||||
// Apply new safe area and border
|
||||
float border = 6.0f;
|
||||
@ -775,12 +763,12 @@ namespace Components
|
||||
void Console::RestoreSafeArea()
|
||||
{
|
||||
// Restore the initial safe area
|
||||
*Game::safeArea = Console::OriginalSafeArea;
|
||||
*Game::safeArea = OriginalSafeArea;
|
||||
}
|
||||
|
||||
void Console::SetSkipShutdown()
|
||||
{
|
||||
Console::SkipShutdown = true;
|
||||
SkipShutdown = true;
|
||||
}
|
||||
|
||||
void Console::FreeNativeConsole()
|
||||
@ -798,11 +786,10 @@ namespace Components
|
||||
|
||||
void Console::ShowAsyncConsole()
|
||||
{
|
||||
Console::ConsoleThread = std::thread(Console::ConsoleRunner);
|
||||
ConsoleThread = std::thread(ConsoleRunner);
|
||||
}
|
||||
|
||||
Game::dvar_t* Console::RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min,
|
||||
float max, unsigned __int16 flags, const char* description)
|
||||
Game::dvar_t* Console::RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, float max, unsigned __int16 flags, const char* description)
|
||||
{
|
||||
static struct
|
||||
{
|
||||
@ -857,7 +844,7 @@ namespace Components
|
||||
{
|
||||
Command::Add("con_echo", []
|
||||
{
|
||||
Console::Con_ToggleConsole();
|
||||
Con_ToggleConsole();
|
||||
Game::I_strncpyz(Game::g_consoleField->buffer, "\\echo ", sizeof(Game::field_t::buffer));
|
||||
Game::g_consoleField->cursor = static_cast<int>(std::strlen(Game::g_consoleField->buffer));
|
||||
Game::Field_AdjustScroll(Game::ScrPlace_GetFullPlacement(), Game::g_consoleField);
|
||||
@ -878,24 +865,27 @@ namespace Components
|
||||
Utils::Hook::Set<float*>(0x5A4400, consoleColor);
|
||||
|
||||
// Remove the need to type '\' or '/' to send a console command
|
||||
Utils::Hook::Set<BYTE>(0x431565, 0xEB);
|
||||
Utils::Hook::Set<std::uint8_t>(0x431565, 0xEB);
|
||||
|
||||
// Internal console
|
||||
Utils::Hook(0x4F690C, Console::Con_ToggleConsole, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4F65A5, Console::Con_ToggleConsole, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4F690C, Con_ToggleConsole, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4F65A5, Con_ToggleConsole, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Allow the client console to always be opened (sv_allowClientConsole)
|
||||
Utils::Hook::Nop(0x4F68EC, 2);
|
||||
|
||||
// Patch safearea for ingame-console
|
||||
Utils::Hook(0x5A50EF, Console::DrawSolidConsoleStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x5A50EF, DrawSolidConsoleStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Check for bad food ;)
|
||||
Utils::Hook(0x4CB9F4, Console::GetAutoCompleteFileList, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4CB9F4, GetAutoCompleteFileList, HOOK_CALL).install()->quick();
|
||||
|
||||
// Patch console dvars
|
||||
Utils::Hook(0x4829AB, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4829EE, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x482A31, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x482A7A, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x482AC3, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4829AB, RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4829EE, RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x482A31, RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x482A7A, RegisterConColor, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x482AC3, RegisterConColor, HOOK_CALL).install()->quick();
|
||||
|
||||
// Modify console style
|
||||
ApplyConsoleStyle();
|
||||
@ -909,7 +899,7 @@ namespace Components
|
||||
|
||||
if (Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
||||
{
|
||||
Scheduler::Loop(Console::RefreshStatus, Scheduler::Pipeline::MAIN);
|
||||
Scheduler::Loop(RefreshStatus, Scheduler::Pipeline::MAIN);
|
||||
}
|
||||
|
||||
// Code below is not necessary when performing unit tests!
|
||||
@ -918,8 +908,8 @@ namespace Components
|
||||
// External console
|
||||
if (Flags::HasFlag("stdout"))
|
||||
{
|
||||
Utils::Hook(0x4B2080, Console::StdOutPrint, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x43D570, Console::StdOutError, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4B2080, StdOutPrint, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x43D570, StdOutError, HOOK_JUMP).install()->quick();
|
||||
}
|
||||
else if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) // ZoneBuilder uses the game's console, until the native one is adapted.
|
||||
{
|
||||
@ -930,43 +920,43 @@ namespace Components
|
||||
|
||||
Utils::Hook(0x60BB68, []
|
||||
{
|
||||
Console::ShowAsyncConsole();
|
||||
ShowAsyncConsole();
|
||||
}, HOOK_CALL).install()->quick();
|
||||
|
||||
Utils::Hook(0x4D69A2, []()
|
||||
Utils::Hook(0x4D69A2, []
|
||||
{
|
||||
Console::SetSkipShutdown();
|
||||
SetSkipShutdown();
|
||||
|
||||
// Sys_DestroyConsole
|
||||
Utils::Hook::Call<void()>(0x4528A0)();
|
||||
|
||||
if (Console::ConsoleThread.joinable())
|
||||
if (ConsoleThread.joinable())
|
||||
{
|
||||
Console::ConsoleThread.join();
|
||||
ConsoleThread.join();
|
||||
}
|
||||
}, HOOK_CALL).install()->quick();
|
||||
|
||||
Scheduler::Loop([]
|
||||
{
|
||||
Console::LastRefresh = Game::Sys_Milliseconds();
|
||||
LastRefresh = Game::Sys_Milliseconds();
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
}
|
||||
else if (Dedicated::IsEnabled()/* || ZoneBuilder::IsEnabled()*/)
|
||||
else if (Dedicated::IsEnabled())
|
||||
{
|
||||
DWORD type = GetFileType(GetStdHandle(STD_INPUT_HANDLE));
|
||||
if (type != FILE_TYPE_CHAR)
|
||||
{
|
||||
MessageBoxA(nullptr, "Console not supported, please use '-stdout' or '-console' flag!", "ERRROR", MB_ICONERROR);
|
||||
TerminateProcess(GetCurrentProcess(), 1);
|
||||
TerminateProcess(GetCurrentProcess(), EXIT_FAILURE);
|
||||
}
|
||||
|
||||
Utils::Hook::Nop(0x60BB58, 11);
|
||||
|
||||
Utils::Hook(0x4305E0, Console::Create, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4528A0, Console::Destroy, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4B2080, Console::Print, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x43D570, Console::Error, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4859A5, Console::Input, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4305E0, Create, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4528A0, Destroy, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4B2080, Print, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x43D570, Error, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4859A5, Input, HOOK_CALL).install()->quick();
|
||||
}
|
||||
else if(!Loader::IsPerformingUnitTests())
|
||||
{
|
||||
@ -976,10 +966,10 @@ namespace Components
|
||||
|
||||
Console::~Console()
|
||||
{
|
||||
Console::SetSkipShutdown();
|
||||
if (Console::ConsoleThread.joinable())
|
||||
SetSkipShutdown();
|
||||
if (ConsoleThread.joinable())
|
||||
{
|
||||
Console::ConsoleThread.join();
|
||||
ConsoleThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "Terminus_4.49.1.ttf.hpp"
|
||||
|
||||
#define OUTPUT_HEIGHT 250
|
||||
#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2))
|
||||
|
||||
@ -81,21 +79,7 @@ namespace Components
|
||||
static LRESULT CALLBACK ConWndProc(HWND hWnd, UINT Msg, WPARAM wParam, unsigned int lParam);
|
||||
static ATOM CALLBACK RegisterClassHook(WNDCLASSA* lpWndClass);
|
||||
static BOOL CALLBACK ResizeChildWindow(HWND hwndChild, LPARAM lParam);
|
||||
static HFONT CALLBACK ReplaceFont(
|
||||
int cHeight,
|
||||
int cWidth,
|
||||
int cEscapement,
|
||||
int cOrientation,
|
||||
int cWeight,
|
||||
DWORD bItalic,
|
||||
DWORD bUnderline,
|
||||
DWORD bStrikeOut,
|
||||
DWORD iCharSet,
|
||||
DWORD iOutPrecision,
|
||||
DWORD iClipPrecision,
|
||||
DWORD iQuality,
|
||||
DWORD iPitchAndFamily,
|
||||
LPCSTR pszFaceName);
|
||||
static HFONT CALLBACK ReplaceFont(int cHeight, int cWidth, int cEscapement, int cOrientation, int cWeight, DWORD bItalic, DWORD bUnderline, DWORD bStrikeOut, DWORD iCharSet, DWORD iOutPrecision, DWORD iClipPrecision, DWORD iQuality, DWORD iPitchAndFamily, LPCSTR pszFaceName);
|
||||
static void ApplyConsoleStyle();
|
||||
static void GetWindowPos(HWND hWnd, int* x, int* y);
|
||||
static void Sys_PrintStub();
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Dvar::Var D3D9Ex::RUseD3D9Ex;
|
||||
|
||||
#pragma region D3D9Device
|
||||
|
||||
HRESULT D3D9Ex::D3D9Device::QueryInterface(REFIID riid, void** ppvObj)
|
||||
@ -630,7 +632,7 @@ namespace Components
|
||||
|
||||
#pragma region D3D9
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::QueryInterface(REFIID riid, void** ppvObj)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::QueryInterface(REFIID riid, void** ppvObj)
|
||||
{
|
||||
*ppvObj = nullptr;
|
||||
|
||||
@ -644,84 +646,84 @@ namespace Components
|
||||
return hRes;
|
||||
}
|
||||
|
||||
ULONG __stdcall D3D9Ex::D3D9::AddRef()
|
||||
ULONG WINAPI D3D9Ex::D3D9::AddRef()
|
||||
{
|
||||
return m_pIDirect3D9->AddRef();
|
||||
}
|
||||
|
||||
ULONG __stdcall D3D9Ex::D3D9::Release()
|
||||
ULONG WINAPI D3D9Ex::D3D9::Release()
|
||||
{
|
||||
ULONG count = m_pIDirect3D9->Release();
|
||||
if (!count) delete this;
|
||||
return count;
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::RegisterSoftwareDevice(void* pInitializeFunction)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::RegisterSoftwareDevice(void* pInitializeFunction)
|
||||
{
|
||||
return m_pIDirect3D9->RegisterSoftwareDevice(pInitializeFunction);
|
||||
}
|
||||
|
||||
UINT __stdcall D3D9Ex::D3D9::GetAdapterCount()
|
||||
UINT WINAPI D3D9Ex::D3D9::GetAdapterCount()
|
||||
{
|
||||
return m_pIDirect3D9->GetAdapterCount();
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier)
|
||||
{
|
||||
return m_pIDirect3D9->GetAdapterIdentifier(Adapter, Flags, pIdentifier);
|
||||
}
|
||||
|
||||
UINT __stdcall D3D9Ex::D3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format)
|
||||
UINT WINAPI D3D9Ex::D3D9::GetAdapterModeCount(UINT Adapter, D3DFORMAT Format)
|
||||
{
|
||||
return m_pIDirect3D9->GetAdapterModeCount(Adapter, Format);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode)
|
||||
{
|
||||
return m_pIDirect3D9->EnumAdapterModes(Adapter, Format, Mode, pMode);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode)
|
||||
{
|
||||
return m_pIDirect3D9->GetAdapterDisplayMode(Adapter, pMode);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed)
|
||||
{
|
||||
return m_pIDirect3D9->CheckDeviceType(iAdapter, DevType, DisplayFormat, BackBufferFormat, bWindowed);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat)
|
||||
{
|
||||
return m_pIDirect3D9->CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels)
|
||||
{
|
||||
return m_pIDirect3D9->CheckDeviceMultiSampleType(Adapter, DeviceType, SurfaceFormat, Windowed, MultiSampleType, pQualityLevels);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat)
|
||||
{
|
||||
return m_pIDirect3D9->CheckDepthStencilMatch(Adapter, DeviceType, AdapterFormat, RenderTargetFormat, DepthStencilFormat);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat)
|
||||
{
|
||||
return m_pIDirect3D9->CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps)
|
||||
{
|
||||
return m_pIDirect3D9->GetDeviceCaps(Adapter, DeviceType, pCaps);
|
||||
}
|
||||
|
||||
HMONITOR __stdcall D3D9Ex::D3D9::GetAdapterMonitor(UINT Adapter)
|
||||
HMONITOR WINAPI D3D9Ex::D3D9::GetAdapterMonitor(UINT Adapter)
|
||||
{
|
||||
return m_pIDirect3D9->GetAdapterMonitor(Adapter);
|
||||
}
|
||||
|
||||
HRESULT __stdcall D3D9Ex::D3D9::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface)
|
||||
HRESULT WINAPI D3D9Ex::D3D9::CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface)
|
||||
{
|
||||
HRESULT hres = m_pIDirect3D9->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);
|
||||
*ppReturnedDeviceInterface = new D3D9Ex::D3D9Device(*ppReturnedDeviceInterface);
|
||||
@ -730,28 +732,26 @@ namespace Components
|
||||
|
||||
#pragma endregion
|
||||
|
||||
IDirect3D9* __stdcall D3D9Ex::Direct3DCreate9Stub(UINT sdk)
|
||||
IDirect3D9* CALLBACK D3D9Ex::Direct3DCreate9Stub(UINT sdk)
|
||||
{
|
||||
if (Dvar::Var("r_useD3D9Ex").get<bool>())
|
||||
if (RUseD3D9Ex.get<bool>())
|
||||
{
|
||||
IDirect3D9Ex* test = nullptr;
|
||||
if (FAILED(Direct3DCreate9Ex(sdk, &test))) return nullptr;
|
||||
|
||||
return (new D3D9Ex::D3D9(test));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Direct3DCreate9(sdk);
|
||||
return (new D3D9(test));
|
||||
}
|
||||
|
||||
return Direct3DCreate9(sdk);
|
||||
}
|
||||
|
||||
D3D9Ex::D3D9Ex()
|
||||
{
|
||||
if (Dedicated::IsEnabled()) return;
|
||||
|
||||
Dvar::Register<bool>("r_useD3D9Ex", false, Game::DVAR_ARCHIVE, "Use extended d3d9 interface!");
|
||||
RUseD3D9Ex = Dvar::Register<bool>("r_useD3D9Ex", false, Game::DVAR_ARCHIVE, "Use extended d3d9 interface!");
|
||||
|
||||
// Hook Interface creation
|
||||
Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub);
|
||||
Utils::Hook::Set(0x6D74D0, Direct3DCreate9Stub);
|
||||
}
|
||||
}
|
||||
|
@ -8,132 +8,131 @@ namespace Components
|
||||
D3D9Ex();
|
||||
|
||||
private:
|
||||
|
||||
class D3D9Device : public IDirect3DDevice9
|
||||
{
|
||||
public:
|
||||
D3D9Device(IDirect3DDevice9* pOriginal) : m_pIDirect3DDevice9(pOriginal) {};
|
||||
virtual ~D3D9Device() {};
|
||||
D3D9Device(IDirect3DDevice9* pOriginal) : m_pIDirect3DDevice9(pOriginal) {}
|
||||
virtual ~D3D9Device() = default;
|
||||
|
||||
HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObj) override;
|
||||
ULONG __stdcall AddRef() override;
|
||||
ULONG __stdcall Release() override;
|
||||
HRESULT __stdcall TestCooperativeLevel() override;
|
||||
UINT __stdcall GetAvailableTextureMem() override;
|
||||
HRESULT __stdcall EvictManagedResources() override;
|
||||
HRESULT __stdcall GetDirect3D(IDirect3D9** ppD3D9) override;
|
||||
HRESULT __stdcall GetDeviceCaps(D3DCAPS9* pCaps) override;
|
||||
HRESULT __stdcall GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE* pMode) override;
|
||||
HRESULT __stdcall GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override;
|
||||
HRESULT __stdcall SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9* pCursorBitmap) override;
|
||||
void __stdcall SetCursorPosition(int X, int Y, DWORD Flags) override;
|
||||
BOOL __stdcall ShowCursor(BOOL bShow) override;
|
||||
HRESULT __stdcall CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DSwapChain9** pSwapChain) override;
|
||||
HRESULT __stdcall GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9** pSwapChain) override;
|
||||
UINT __stdcall GetNumberOfSwapChains() override;
|
||||
HRESULT __stdcall Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) override;
|
||||
HRESULT __stdcall Present(CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) override;
|
||||
HRESULT __stdcall GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9** ppBackBuffer) override;
|
||||
HRESULT __stdcall GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS* pRasterStatus) override;
|
||||
HRESULT __stdcall SetDialogBoxMode(BOOL bEnableDialogs) override;
|
||||
void __stdcall SetGammaRamp(UINT iSwapChain, DWORD Flags, CONST D3DGAMMARAMP* pRamp) override;
|
||||
void __stdcall GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP* pRamp) override;
|
||||
HRESULT __stdcall CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9** ppVolumeTexture, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9** ppCubeTexture, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall UpdateSurface(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestinationSurface, CONST POINT* pDestPoint) override;
|
||||
HRESULT __stdcall UpdateTexture(IDirect3DBaseTexture9* pSourceTexture, IDirect3DBaseTexture9* pDestinationTexture) override;
|
||||
HRESULT __stdcall GetRenderTargetData(IDirect3DSurface9* pRenderTarget, IDirect3DSurface9* pDestSurface) override;
|
||||
HRESULT __stdcall GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9* pDestSurface) override;
|
||||
HRESULT __stdcall StretchRect(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestSurface, CONST RECT* pDestRect, D3DTEXTUREFILTERTYPE Filter) override;
|
||||
HRESULT __stdcall ColorFill(IDirect3DSurface9* pSurface, CONST RECT* pRect, D3DCOLOR color) override;
|
||||
HRESULT __stdcall CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override;
|
||||
HRESULT __stdcall SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9* pRenderTarget) override;
|
||||
HRESULT __stdcall GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9** ppRenderTarget) override;
|
||||
HRESULT __stdcall SetDepthStencilSurface(IDirect3DSurface9* pNewZStencil) override;
|
||||
HRESULT __stdcall GetDepthStencilSurface(IDirect3DSurface9** ppZStencilSurface) override;
|
||||
HRESULT __stdcall BeginScene() override;
|
||||
HRESULT __stdcall EndScene() override;
|
||||
HRESULT __stdcall Clear(DWORD Count, CONST D3DRECT* pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override;
|
||||
HRESULT __stdcall SetTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override;
|
||||
HRESULT __stdcall GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix) override;
|
||||
HRESULT __stdcall MultiplyTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override;
|
||||
HRESULT __stdcall SetViewport(CONST D3DVIEWPORT9* pViewport) override;
|
||||
HRESULT __stdcall GetViewport(D3DVIEWPORT9* pViewport) override;
|
||||
HRESULT __stdcall SetMaterial(CONST D3DMATERIAL9* pMaterial) override;
|
||||
HRESULT __stdcall GetMaterial(D3DMATERIAL9* pMaterial) override;
|
||||
HRESULT __stdcall SetLight(DWORD Index, CONST D3DLIGHT9* pLight) override;
|
||||
HRESULT __stdcall GetLight(DWORD Index, D3DLIGHT9* pLight) override;
|
||||
HRESULT __stdcall LightEnable(DWORD Index, BOOL Enable) override;
|
||||
HRESULT __stdcall GetLightEnable(DWORD Index, BOOL* pEnable) override;
|
||||
HRESULT __stdcall SetClipPlane(DWORD Index, CONST float* pPlane) override;
|
||||
HRESULT __stdcall GetClipPlane(DWORD Index, float* pPlane) override;
|
||||
HRESULT __stdcall SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override;
|
||||
HRESULT __stdcall GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) override;
|
||||
HRESULT __stdcall CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9** ppSB) override;
|
||||
HRESULT __stdcall BeginStateBlock() override;
|
||||
HRESULT __stdcall EndStateBlock(IDirect3DStateBlock9** ppSB) override;
|
||||
HRESULT __stdcall SetClipStatus(CONST D3DCLIPSTATUS9* pClipStatus) override;
|
||||
HRESULT __stdcall GetClipStatus(D3DCLIPSTATUS9* pClipStatus) override;
|
||||
HRESULT __stdcall GetTexture(DWORD Stage, IDirect3DBaseTexture9** ppTexture) override;
|
||||
HRESULT __stdcall SetTexture(DWORD Stage, IDirect3DBaseTexture9* pTexture) override;
|
||||
HRESULT __stdcall GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD* pValue) override;
|
||||
HRESULT __stdcall SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override;
|
||||
HRESULT __stdcall GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD* pValue) override;
|
||||
HRESULT __stdcall SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override;
|
||||
HRESULT __stdcall ValidateDevice(DWORD* pNumPasses) override;
|
||||
HRESULT __stdcall SetPaletteEntries(UINT PaletteNumber, CONST PALETTEENTRY* pEntries) override;
|
||||
HRESULT __stdcall GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries) override;
|
||||
HRESULT __stdcall SetCurrentTexturePalette(UINT PaletteNumber) override;
|
||||
HRESULT __stdcall GetCurrentTexturePalette(UINT *PaletteNumber) override;
|
||||
HRESULT __stdcall SetScissorRect(CONST RECT* pRect) override;
|
||||
HRESULT __stdcall GetScissorRect(RECT* pRect) override;
|
||||
HRESULT __stdcall SetSoftwareVertexProcessing(BOOL bSoftware) override;
|
||||
BOOL __stdcall GetSoftwareVertexProcessing() override;
|
||||
HRESULT __stdcall SetNPatchMode(float nSegments) override;
|
||||
float __stdcall GetNPatchMode() override;
|
||||
HRESULT __stdcall DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override;
|
||||
HRESULT __stdcall DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override;
|
||||
HRESULT __stdcall DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override;
|
||||
HRESULT __stdcall DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, CONST void* pIndexData, D3DFORMAT IndexDataFormat, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override;
|
||||
HRESULT __stdcall ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9* pDestBuffer, IDirect3DVertexDeclaration9* pVertexDecl, DWORD Flags) override;
|
||||
HRESULT __stdcall CreateVertexDeclaration(CONST D3DVERTEXELEMENT9* pVertexElements, IDirect3DVertexDeclaration9** ppDecl) override;
|
||||
HRESULT __stdcall SetVertexDeclaration(IDirect3DVertexDeclaration9* pDecl) override;
|
||||
HRESULT __stdcall GetVertexDeclaration(IDirect3DVertexDeclaration9** ppDecl) override;
|
||||
HRESULT __stdcall SetFVF(DWORD FVF) override;
|
||||
HRESULT __stdcall GetFVF(DWORD* pFVF) override;
|
||||
HRESULT __stdcall CreateVertexShader(CONST DWORD* pFunction, IDirect3DVertexShader9** ppShader) override;
|
||||
HRESULT __stdcall SetVertexShader(IDirect3DVertexShader9* pShader) override;
|
||||
HRESULT __stdcall GetVertexShader(IDirect3DVertexShader9** ppShader) override;
|
||||
HRESULT __stdcall SetVertexShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT __stdcall GetVertexShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT __stdcall SetVertexShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT __stdcall GetVertexShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT __stdcall SetVertexShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT __stdcall GetVertexShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT __stdcall SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride) override;
|
||||
HRESULT __stdcall GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9** ppStreamData, UINT* OffsetInBytes, UINT* pStride) override;
|
||||
HRESULT __stdcall SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override;
|
||||
HRESULT __stdcall GetStreamSourceFreq(UINT StreamNumber, UINT* Divider) override;
|
||||
HRESULT __stdcall SetIndices(IDirect3DIndexBuffer9* pIndexData) override;
|
||||
HRESULT __stdcall GetIndices(IDirect3DIndexBuffer9** ppIndexData) override;
|
||||
HRESULT __stdcall CreatePixelShader(CONST DWORD* pFunction, IDirect3DPixelShader9** ppShader) override;
|
||||
HRESULT __stdcall SetPixelShader(IDirect3DPixelShader9* pShader) override;
|
||||
HRESULT __stdcall GetPixelShader(IDirect3DPixelShader9** ppShader) override;
|
||||
HRESULT __stdcall SetPixelShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT __stdcall GetPixelShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT __stdcall SetPixelShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT __stdcall GetPixelShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT __stdcall SetPixelShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT __stdcall GetPixelShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT __stdcall DrawRectPatch(UINT Handle, CONST float* pNumSegs, CONST D3DRECTPATCH_INFO* pRectPatchInfo) override;
|
||||
HRESULT __stdcall DrawTriPatch(UINT Handle, CONST float* pNumSegs, CONST D3DTRIPATCH_INFO* pTriPatchInfo) override;
|
||||
HRESULT __stdcall DeletePatch(UINT Handle) override;
|
||||
HRESULT __stdcall CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9** ppQuery) override;
|
||||
HRESULT WINAPI QueryInterface(REFIID riid, void** ppvObj) override;
|
||||
ULONG WINAPI AddRef() override;
|
||||
ULONG WINAPI Release() override;
|
||||
HRESULT WINAPI TestCooperativeLevel() override;
|
||||
UINT WINAPI GetAvailableTextureMem() override;
|
||||
HRESULT WINAPI EvictManagedResources() override;
|
||||
HRESULT WINAPI GetDirect3D(IDirect3D9** ppD3D9) override;
|
||||
HRESULT WINAPI GetDeviceCaps(D3DCAPS9* pCaps) override;
|
||||
HRESULT WINAPI GetDisplayMode(UINT iSwapChain, D3DDISPLAYMODE* pMode) override;
|
||||
HRESULT WINAPI GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters) override;
|
||||
HRESULT WINAPI SetCursorProperties(UINT XHotSpot, UINT YHotSpot, IDirect3DSurface9* pCursorBitmap) override;
|
||||
void WINAPI SetCursorPosition(int X, int Y, DWORD Flags) override;
|
||||
BOOL WINAPI ShowCursor(BOOL bShow) override;
|
||||
HRESULT WINAPI CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DSwapChain9** pSwapChain) override;
|
||||
HRESULT WINAPI GetSwapChain(UINT iSwapChain, IDirect3DSwapChain9** pSwapChain) override;
|
||||
UINT WINAPI GetNumberOfSwapChains() override;
|
||||
HRESULT WINAPI Reset(D3DPRESENT_PARAMETERS* pPresentationParameters) override;
|
||||
HRESULT WINAPI Present(CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) override;
|
||||
HRESULT WINAPI GetBackBuffer(UINT iSwapChain, UINT iBackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface9** ppBackBuffer) override;
|
||||
HRESULT WINAPI GetRasterStatus(UINT iSwapChain, D3DRASTER_STATUS* pRasterStatus) override;
|
||||
HRESULT WINAPI SetDialogBoxMode(BOOL bEnableDialogs) override;
|
||||
void WINAPI SetGammaRamp(UINT iSwapChain, DWORD Flags, CONST D3DGAMMARAMP* pRamp) override;
|
||||
void WINAPI GetGammaRamp(UINT iSwapChain, D3DGAMMARAMP* pRamp) override;
|
||||
HRESULT WINAPI CreateTexture(UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI CreateVolumeTexture(UINT Width, UINT Height, UINT Depth, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DVolumeTexture9** ppVolumeTexture, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI CreateCubeTexture(UINT EdgeLength, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DCubeTexture9** ppCubeTexture, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI CreateVertexBuffer(UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI CreateIndexBuffer(UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI CreateRenderTarget(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Lockable, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI CreateDepthStencilSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI UpdateSurface(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestinationSurface, CONST POINT* pDestPoint) override;
|
||||
HRESULT WINAPI UpdateTexture(IDirect3DBaseTexture9* pSourceTexture, IDirect3DBaseTexture9* pDestinationTexture) override;
|
||||
HRESULT WINAPI GetRenderTargetData(IDirect3DSurface9* pRenderTarget, IDirect3DSurface9* pDestSurface) override;
|
||||
HRESULT WINAPI GetFrontBufferData(UINT iSwapChain, IDirect3DSurface9* pDestSurface) override;
|
||||
HRESULT WINAPI StretchRect(IDirect3DSurface9* pSourceSurface, CONST RECT* pSourceRect, IDirect3DSurface9* pDestSurface, CONST RECT* pDestRect, D3DTEXTUREFILTERTYPE Filter) override;
|
||||
HRESULT WINAPI ColorFill(IDirect3DSurface9* pSurface, CONST RECT* pRect, D3DCOLOR color) override;
|
||||
HRESULT WINAPI CreateOffscreenPlainSurface(UINT Width, UINT Height, D3DFORMAT Format, D3DPOOL Pool, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle) override;
|
||||
HRESULT WINAPI SetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9* pRenderTarget) override;
|
||||
HRESULT WINAPI GetRenderTarget(DWORD RenderTargetIndex, IDirect3DSurface9** ppRenderTarget) override;
|
||||
HRESULT WINAPI SetDepthStencilSurface(IDirect3DSurface9* pNewZStencil) override;
|
||||
HRESULT WINAPI GetDepthStencilSurface(IDirect3DSurface9** ppZStencilSurface) override;
|
||||
HRESULT WINAPI BeginScene() override;
|
||||
HRESULT WINAPI EndScene() override;
|
||||
HRESULT WINAPI Clear(DWORD Count, CONST D3DRECT* pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil) override;
|
||||
HRESULT WINAPI SetTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override;
|
||||
HRESULT WINAPI GetTransform(D3DTRANSFORMSTATETYPE State, D3DMATRIX* pMatrix) override;
|
||||
HRESULT WINAPI MultiplyTransform(D3DTRANSFORMSTATETYPE State, CONST D3DMATRIX* pMatrix) override;
|
||||
HRESULT WINAPI SetViewport(CONST D3DVIEWPORT9* pViewport) override;
|
||||
HRESULT WINAPI GetViewport(D3DVIEWPORT9* pViewport) override;
|
||||
HRESULT WINAPI SetMaterial(CONST D3DMATERIAL9* pMaterial) override;
|
||||
HRESULT WINAPI GetMaterial(D3DMATERIAL9* pMaterial) override;
|
||||
HRESULT WINAPI SetLight(DWORD Index, CONST D3DLIGHT9* pLight) override;
|
||||
HRESULT WINAPI GetLight(DWORD Index, D3DLIGHT9* pLight) override;
|
||||
HRESULT WINAPI LightEnable(DWORD Index, BOOL Enable) override;
|
||||
HRESULT WINAPI GetLightEnable(DWORD Index, BOOL* pEnable) override;
|
||||
HRESULT WINAPI SetClipPlane(DWORD Index, CONST float* pPlane) override;
|
||||
HRESULT WINAPI GetClipPlane(DWORD Index, float* pPlane) override;
|
||||
HRESULT WINAPI SetRenderState(D3DRENDERSTATETYPE State, DWORD Value) override;
|
||||
HRESULT WINAPI GetRenderState(D3DRENDERSTATETYPE State, DWORD* pValue) override;
|
||||
HRESULT WINAPI CreateStateBlock(D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9** ppSB) override;
|
||||
HRESULT WINAPI BeginStateBlock() override;
|
||||
HRESULT WINAPI EndStateBlock(IDirect3DStateBlock9** ppSB) override;
|
||||
HRESULT WINAPI SetClipStatus(CONST D3DCLIPSTATUS9* pClipStatus) override;
|
||||
HRESULT WINAPI GetClipStatus(D3DCLIPSTATUS9* pClipStatus) override;
|
||||
HRESULT WINAPI GetTexture(DWORD Stage, IDirect3DBaseTexture9** ppTexture) override;
|
||||
HRESULT WINAPI SetTexture(DWORD Stage, IDirect3DBaseTexture9* pTexture) override;
|
||||
HRESULT WINAPI GetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD* pValue) override;
|
||||
HRESULT WINAPI SetTextureStageState(DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value) override;
|
||||
HRESULT WINAPI GetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD* pValue) override;
|
||||
HRESULT WINAPI SetSamplerState(DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value) override;
|
||||
HRESULT WINAPI ValidateDevice(DWORD* pNumPasses) override;
|
||||
HRESULT WINAPI SetPaletteEntries(UINT PaletteNumber, CONST PALETTEENTRY* pEntries) override;
|
||||
HRESULT WINAPI GetPaletteEntries(UINT PaletteNumber, PALETTEENTRY* pEntries) override;
|
||||
HRESULT WINAPI SetCurrentTexturePalette(UINT PaletteNumber) override;
|
||||
HRESULT WINAPI GetCurrentTexturePalette(UINT *PaletteNumber) override;
|
||||
HRESULT WINAPI SetScissorRect(CONST RECT* pRect) override;
|
||||
HRESULT WINAPI GetScissorRect(RECT* pRect) override;
|
||||
HRESULT WINAPI SetSoftwareVertexProcessing(BOOL bSoftware) override;
|
||||
BOOL WINAPI GetSoftwareVertexProcessing() override;
|
||||
HRESULT WINAPI SetNPatchMode(float nSegments) override;
|
||||
float WINAPI GetNPatchMode() override;
|
||||
HRESULT WINAPI DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount) override;
|
||||
HRESULT WINAPI DrawIndexedPrimitive(D3DPRIMITIVETYPE PrimitiveType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount) override;
|
||||
HRESULT WINAPI DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT PrimitiveCount, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override;
|
||||
HRESULT WINAPI DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType, UINT MinVertexIndex, UINT NumVertices, UINT PrimitiveCount, CONST void* pIndexData, D3DFORMAT IndexDataFormat, CONST void* pVertexStreamZeroData, UINT VertexStreamZeroStride) override;
|
||||
HRESULT WINAPI ProcessVertices(UINT SrcStartIndex, UINT DestIndex, UINT VertexCount, IDirect3DVertexBuffer9* pDestBuffer, IDirect3DVertexDeclaration9* pVertexDecl, DWORD Flags) override;
|
||||
HRESULT WINAPI CreateVertexDeclaration(CONST D3DVERTEXELEMENT9* pVertexElements, IDirect3DVertexDeclaration9** ppDecl) override;
|
||||
HRESULT WINAPI SetVertexDeclaration(IDirect3DVertexDeclaration9* pDecl) override;
|
||||
HRESULT WINAPI GetVertexDeclaration(IDirect3DVertexDeclaration9** ppDecl) override;
|
||||
HRESULT WINAPI SetFVF(DWORD FVF) override;
|
||||
HRESULT WINAPI GetFVF(DWORD* pFVF) override;
|
||||
HRESULT WINAPI CreateVertexShader(CONST DWORD* pFunction, IDirect3DVertexShader9** ppShader) override;
|
||||
HRESULT WINAPI SetVertexShader(IDirect3DVertexShader9* pShader) override;
|
||||
HRESULT WINAPI GetVertexShader(IDirect3DVertexShader9** ppShader) override;
|
||||
HRESULT WINAPI SetVertexShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT WINAPI GetVertexShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT WINAPI SetVertexShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT WINAPI GetVertexShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT WINAPI SetVertexShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT WINAPI GetVertexShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT WINAPI SetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride) override;
|
||||
HRESULT WINAPI GetStreamSource(UINT StreamNumber, IDirect3DVertexBuffer9** ppStreamData, UINT* OffsetInBytes, UINT* pStride) override;
|
||||
HRESULT WINAPI SetStreamSourceFreq(UINT StreamNumber, UINT Divider) override;
|
||||
HRESULT WINAPI GetStreamSourceFreq(UINT StreamNumber, UINT* Divider) override;
|
||||
HRESULT WINAPI SetIndices(IDirect3DIndexBuffer9* pIndexData) override;
|
||||
HRESULT WINAPI GetIndices(IDirect3DIndexBuffer9** ppIndexData) override;
|
||||
HRESULT WINAPI CreatePixelShader(CONST DWORD* pFunction, IDirect3DPixelShader9** ppShader) override;
|
||||
HRESULT WINAPI SetPixelShader(IDirect3DPixelShader9* pShader) override;
|
||||
HRESULT WINAPI GetPixelShader(IDirect3DPixelShader9** ppShader) override;
|
||||
HRESULT WINAPI SetPixelShaderConstantF(UINT StartRegister, CONST float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT WINAPI GetPixelShaderConstantF(UINT StartRegister, float* pConstantData, UINT Vector4fCount) override;
|
||||
HRESULT WINAPI SetPixelShaderConstantI(UINT StartRegister, CONST int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT WINAPI GetPixelShaderConstantI(UINT StartRegister, int* pConstantData, UINT Vector4iCount) override;
|
||||
HRESULT WINAPI SetPixelShaderConstantB(UINT StartRegister, CONST BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT WINAPI GetPixelShaderConstantB(UINT StartRegister, BOOL* pConstantData, UINT BoolCount) override;
|
||||
HRESULT WINAPI DrawRectPatch(UINT Handle, CONST float* pNumSegs, CONST D3DRECTPATCH_INFO* pRectPatchInfo) override;
|
||||
HRESULT WINAPI DrawTriPatch(UINT Handle, CONST float* pNumSegs, CONST D3DTRIPATCH_INFO* pTriPatchInfo) override;
|
||||
HRESULT WINAPI DeletePatch(UINT Handle) override;
|
||||
HRESULT WINAPI CreateQuery(D3DQUERYTYPE Type, IDirect3DQuery9** ppQuery) override;
|
||||
|
||||
private:
|
||||
IDirect3DDevice9 *m_pIDirect3DDevice9;
|
||||
@ -145,28 +144,30 @@ namespace Components
|
||||
D3D9(IDirect3D9Ex *pOriginal) : m_pIDirect3D9(pOriginal) {};
|
||||
virtual ~D3D9() {};
|
||||
|
||||
HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObj) override;
|
||||
ULONG __stdcall AddRef() override;
|
||||
ULONG __stdcall Release() override;
|
||||
HRESULT __stdcall RegisterSoftwareDevice(void* pInitializeFunction) override;
|
||||
UINT __stdcall GetAdapterCount() override;
|
||||
HRESULT __stdcall GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) override;
|
||||
UINT __stdcall GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) override;
|
||||
HRESULT __stdcall EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) override;
|
||||
HRESULT __stdcall GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) override;
|
||||
HRESULT __stdcall CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) override;
|
||||
HRESULT __stdcall CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) override;
|
||||
HRESULT __stdcall CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) override;
|
||||
HRESULT __stdcall CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) override;
|
||||
HRESULT __stdcall CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) override;
|
||||
HRESULT __stdcall GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) override;
|
||||
HMONITOR __stdcall GetAdapterMonitor(UINT Adapter) override;
|
||||
HRESULT __stdcall CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) override;
|
||||
HRESULT WINAPI QueryInterface(REFIID riid, void** ppvObj) override;
|
||||
ULONG WINAPI AddRef() override;
|
||||
ULONG WINAPI Release() override;
|
||||
HRESULT WINAPI RegisterSoftwareDevice(void* pInitializeFunction) override;
|
||||
UINT WINAPI GetAdapterCount() override;
|
||||
HRESULT WINAPI GetAdapterIdentifier(UINT Adapter, DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier) override;
|
||||
UINT WINAPI GetAdapterModeCount(UINT Adapter, D3DFORMAT Format) override;
|
||||
HRESULT WINAPI EnumAdapterModes(UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode) override;
|
||||
HRESULT WINAPI GetAdapterDisplayMode(UINT Adapter, D3DDISPLAYMODE* pMode) override;
|
||||
HRESULT WINAPI CheckDeviceType(UINT iAdapter, D3DDEVTYPE DevType, D3DFORMAT DisplayFormat, D3DFORMAT BackBufferFormat, BOOL bWindowed) override;
|
||||
HRESULT WINAPI CheckDeviceFormat(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, DWORD Usage, D3DRESOURCETYPE RType, D3DFORMAT CheckFormat) override;
|
||||
HRESULT WINAPI CheckDeviceMultiSampleType(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SurfaceFormat, BOOL Windowed, D3DMULTISAMPLE_TYPE MultiSampleType, DWORD* pQualityLevels) override;
|
||||
HRESULT WINAPI CheckDepthStencilMatch(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT AdapterFormat, D3DFORMAT RenderTargetFormat, D3DFORMAT DepthStencilFormat) override;
|
||||
HRESULT WINAPI CheckDeviceFormatConversion(UINT Adapter, D3DDEVTYPE DeviceType, D3DFORMAT SourceFormat, D3DFORMAT TargetFormat) override;
|
||||
HRESULT WINAPI GetDeviceCaps(UINT Adapter, D3DDEVTYPE DeviceType, D3DCAPS9* pCaps) override;
|
||||
HMONITOR WINAPI GetAdapterMonitor(UINT Adapter) override;
|
||||
HRESULT WINAPI CreateDevice(UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface) override;
|
||||
|
||||
private:
|
||||
IDirect3D9 *m_pIDirect3D9;
|
||||
};
|
||||
|
||||
static IDirect3D9* __stdcall Direct3DCreate9Stub(UINT sdk);
|
||||
static Dvar::Var RUseD3D9Ex;
|
||||
|
||||
static IDirect3D9* CALLBACK Direct3DCreate9Stub(UINT sdk);
|
||||
};
|
||||
}
|
||||
|
@ -102,8 +102,6 @@ namespace Components
|
||||
|
||||
const char Debug::StrTemplate[] = "%s: %s All those moments will be lost in time, like tears in rain.";
|
||||
|
||||
const float Debug::ColorWhite[] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
std::string Debug::BuildPMFlagsString(const Game::playerState_s* ps)
|
||||
{
|
||||
std::string result;
|
||||
@ -163,19 +161,19 @@ namespace Components
|
||||
auto* const font2 = Game::UI_GetFontHandle(scrPlace, 6, MY_SCALE2);
|
||||
|
||||
Game::UI_DrawText(scrPlace, "Client View of Flags", maxChars, font2, -60.0f, 0, 1, 1,
|
||||
MY_SCALE2, ColorWhite, 1);
|
||||
MY_SCALE2, TextRenderer::WHITE_COLOR, 1);
|
||||
|
||||
const auto pmf = BuildPMFlagsString(&cgameGlob->predictedPlayerState);
|
||||
Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, pmf.data(), maxChars, font1, 30.0f, MY_Y, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
const auto pof = BuildPOFlagsString(&cgameGlob->predictedPlayerState);
|
||||
Game::UI_DrawText(scrPlace, pof.data(), maxChars, font1, 350.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, pof.data(), maxChars, font1, 350.0f, MY_Y, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
const auto plf = BuildPLFlagsString(&cgameGlob->predictedPlayerState);
|
||||
Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, plf.data(), maxChars, font1, 350.0f, 250.0f, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
const auto pef = BuildPEFlagsString(&cgameGlob->predictedPlayerState);
|
||||
Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, MY_Y, 1, 1, MY_SCALE_2, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, pef.data(), maxChars, font1, 525.0f, MY_Y, 1, 1, MY_SCALE_2, TextRenderer::WHITE_COLOR, 3);
|
||||
}
|
||||
|
||||
void Debug::CG_DrawDebugPlayerHealth(const int localClientNum)
|
||||
@ -223,27 +221,33 @@ namespace Components
|
||||
|
||||
sprintf_s(strFinal, StrTemplate, font1->fontName, StrButtons);
|
||||
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal));
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font1, MY_X, 10.0f, 1, 1, 0.4f, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font1, MY_X, 10.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
sprintf_s(strFinal, StrTemplate, font2->fontName, StrButtons);
|
||||
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal));
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font2, MY_X, 35.0f, 1, 1, 0.4f, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font2, MY_X, 35.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
sprintf_s(strFinal, StrTemplate, font3->fontName, StrButtons);
|
||||
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal));
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font3, MY_X, 60.0f, 1, 1, 0.4f, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font3, MY_X, 60.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
sprintf_s(strFinal, StrTemplate, font5->fontName, StrButtons);
|
||||
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal));
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font5, MY_X, 85.0f, 1, 1, 0.4f, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font5, MY_X, 85.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3);
|
||||
|
||||
sprintf_s(strFinal, StrTemplate, font6->fontName, StrButtons);
|
||||
Game::UI_FilterStringForButtonAnimation(strFinal, sizeof(strFinal));
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font6, MY_X, 110.0f, 1, 1, 0.4f, ColorWhite, 3);
|
||||
Game::UI_DrawText(scrPlace, strFinal, std::numeric_limits<int>::max(), font6, MY_X, 110.0f, 1, 1, 0.4f, TextRenderer::WHITE_COLOR, 3);
|
||||
}
|
||||
|
||||
void Debug::CG_DrawDebugOverlays_Hk(const int localClientNum)
|
||||
{
|
||||
assert(DebugOverlay);
|
||||
if (!DebugOverlay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (DebugOverlay->current.integer)
|
||||
{
|
||||
case 2:
|
||||
@ -269,9 +273,9 @@ namespace Components
|
||||
|
||||
void Debug::Com_Bug_f(Command::Params* params)
|
||||
{
|
||||
char newFileName[0x105]{};
|
||||
char to_ospath[MAX_PATH]{};
|
||||
char from_ospath[MAX_PATH]{};
|
||||
char newFileName[MAX_PATH]{};
|
||||
char to_ospath[MAX_OSPATH]{};
|
||||
char from_ospath[MAX_OSPATH]{};
|
||||
const char* bug;
|
||||
|
||||
if (!*Game::logfile)
|
||||
@ -344,10 +348,8 @@ namespace Components
|
||||
nullptr,
|
||||
};
|
||||
|
||||
DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0,
|
||||
Game::DVAR_NONE, "Toggles the display of various debug info.");
|
||||
BugName = Game::Dvar_RegisterString("bug_name", "bug0",
|
||||
Game::DVAR_CHEAT | Game::DVAR_CODINFO, "Name appended to the copied console log");
|
||||
DebugOverlay = Game::Dvar_RegisterEnum("debugOverlay", debugOverlayNames_0, 0, Game::DVAR_NONE, "Toggles the display of various debug info.");
|
||||
BugName = Game::Dvar_RegisterString("bug_name", "bug0", Game::DVAR_NONE, "Name appended to the copied console log");
|
||||
}
|
||||
|
||||
const Game::dvar_t* Debug::Dvar_Register_PlayerDebugHealth(const char* name, bool value, [[maybe_unused]] std::uint16_t flags, const char* description)
|
||||
@ -358,7 +360,7 @@ namespace Components
|
||||
|
||||
Debug::Debug()
|
||||
{
|
||||
Scheduler::Once(CL_InitDebugDvars, Scheduler::Pipeline::MAIN);
|
||||
Events::OnDvarInit(CL_InitDebugDvars);
|
||||
|
||||
// Hook end of CG_DrawDebugOverlays (This is to ensure some checks are done before our hook is executed).
|
||||
Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick();
|
||||
|
@ -28,8 +28,6 @@ namespace Components
|
||||
static constexpr auto MY_X = -25.0f;
|
||||
static constexpr auto MY_Y = 20.0f;
|
||||
|
||||
static const float ColorWhite[];
|
||||
|
||||
static std::string BuildPMFlagsString(const Game::playerState_s* ps);
|
||||
static std::string BuildPOFlagsString(const Game::playerState_s* ps);
|
||||
static std::string BuildPLFlagsString(const Game::playerState_s* ps);
|
||||
|
@ -1,6 +1,9 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include "CardTitles.hpp"
|
||||
#include "ClanTags.hpp"
|
||||
#include "Party.hpp"
|
||||
#include "ServerCommands.hpp"
|
||||
|
||||
namespace Components
|
||||
@ -8,6 +11,7 @@ namespace Components
|
||||
SteamID Dedicated::PlayerGuids[18][2];
|
||||
|
||||
Dvar::Var Dedicated::SVLanOnly;
|
||||
Dvar::Var Dedicated::SVMOTD;
|
||||
Dvar::Var Dedicated::COMLogFilter;
|
||||
|
||||
bool Dedicated::IsEnabled()
|
||||
@ -76,7 +80,7 @@ namespace Components
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
call Dedicated::PostInitialization
|
||||
call PostInitialization
|
||||
popad
|
||||
|
||||
// Start Com_EvenLoop
|
||||
@ -85,6 +89,36 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void Dedicated::Com_ClampMsec(const int msec)
|
||||
{
|
||||
if (msec > 500 && msec < 500000)
|
||||
{
|
||||
Game::Com_PrintWarning(Game::CON_CHANNEL_SYSTEM, "Hitch warning: %i msec frame time\n", msec);
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void Dedicated::Com_ClampMsec_Stub()
|
||||
{
|
||||
using namespace Game;
|
||||
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push ecx
|
||||
call Com_ClampMsec
|
||||
add esp, 0x4
|
||||
|
||||
popad
|
||||
|
||||
// Game's code
|
||||
mov edx, dword ptr com_sv_running
|
||||
|
||||
push 0x47DDB8
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
void Dedicated::TransmitGuids()
|
||||
{
|
||||
std::string list = Utils::String::VA("%c", 20);
|
||||
@ -96,7 +130,7 @@ namespace Components
|
||||
list.append(Utils::String::VA(" %llX", Game::svs_clients[i].steamID));
|
||||
|
||||
Utils::InfoString info(Game::svs_clients[i].userinfo);
|
||||
list.append(Utils::String::VA(" %llX", strtoull(info.get("realsteamId").data(), nullptr, 16)));
|
||||
list.append(Utils::String::VA(" %llX", std::strtoull(info.get("realsteamId").data(), nullptr, 16)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -111,10 +145,9 @@ namespace Components
|
||||
{
|
||||
Scheduler::Once([]
|
||||
{
|
||||
const auto partyEnable = Dvar::Var("party_enable").get<bool>();
|
||||
std::string mapname = (*Game::sv_mapname)->current.string;
|
||||
|
||||
if (!partyEnable) // Time wrapping should not occur in party servers, but yeah...
|
||||
if (!Party::IsEnabled()) // Time wrapping should not occur in party servers, but yeah...
|
||||
{
|
||||
if (mapname.empty()) mapname = "mp_rust";
|
||||
Command::Execute(std::format("map {}", mapname), true);
|
||||
@ -127,13 +160,13 @@ namespace Components
|
||||
void Dedicated::Heartbeat()
|
||||
{
|
||||
// Do not send a heartbeat if sv_lanOnly is set to true
|
||||
if (Dedicated::SVLanOnly.get<bool>())
|
||||
if (SVLanOnly.get<bool>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto masterPort = Dvar::Var("masterPort").get<int>();
|
||||
const auto* masterServerName = Dvar::Var("masterServerName").get<const char*>();
|
||||
const auto masterPort = (*Game::com_masterPort)->current.integer;
|
||||
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
|
||||
|
||||
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));
|
||||
|
||||
@ -141,25 +174,19 @@ namespace Components
|
||||
Network::SendCommand(master, "heartbeat", "IW4");
|
||||
}
|
||||
|
||||
Game::dvar_t* Dedicated::Dvar_RegisterSVNetworkFps(const char* dvarName, int, int min, int, int, const char* description)
|
||||
{
|
||||
return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::DVAR_NONE, description);
|
||||
}
|
||||
|
||||
Dedicated::Dedicated()
|
||||
{
|
||||
Dedicated::COMLogFilter = Dvar::Register<bool>("com_logFilter", true,
|
||||
COMLogFilter = Dvar::Register<bool>("com_logFilter", true,
|
||||
Game::DVAR_LATCH, "Removes ~95% of unneeded lines from the log");
|
||||
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
if (IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
{
|
||||
// Make sure all callbacks are handled
|
||||
Scheduler::Loop(Steam::SteamAPI_RunCallbacks, Scheduler::Pipeline::SERVER);
|
||||
|
||||
Dedicated::SVLanOnly = Dvar::Register<bool>("sv_lanOnly", false,
|
||||
Game::DVAR_NONE, "Don't act as node");
|
||||
SVLanOnly = Dvar::Register<bool>("sv_lanOnly", false, Game::DVAR_NONE, "Don't act as node");
|
||||
|
||||
Utils::Hook(0x60BE98, Dedicated::InitDedicatedServer, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x60BE98, InitDedicatedServer, HOOK_CALL).install()->quick();
|
||||
|
||||
Utils::Hook::Set<BYTE>(0x683370, 0xC3); // steam sometimes doesn't like the server
|
||||
|
||||
@ -198,9 +225,6 @@ namespace Components
|
||||
// isHost script call return 0
|
||||
Utils::Hook::Set<DWORD>(0x5DEC04, 0);
|
||||
|
||||
// Manually register sv_network_fps
|
||||
Utils::Hook(0x4D3C7B, Dedicated::Dvar_RegisterSVNetworkFps, HOOK_CALL).install()->quick();
|
||||
|
||||
// r_loadForRenderer default to 0
|
||||
Utils::Hook::Set<BYTE>(0x519DDF, 0);
|
||||
|
||||
@ -217,18 +241,21 @@ namespace Components
|
||||
Utils::Hook::Set<BYTE>(0x4B4D19, 0xEB);
|
||||
|
||||
// Intercept time wrapping
|
||||
Utils::Hook(0x62737D, Dedicated::TimeWrapStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x62737D, TimeWrapStub, HOOK_CALL).install()->quick();
|
||||
//Utils::Hook::Set<DWORD>(0x62735C, 50'000); // Time wrap after 50 seconds (for testing - i don't want to wait 3 weeks)
|
||||
|
||||
if (!ZoneBuilder::IsEnabled())
|
||||
{
|
||||
Scheduler::Once([]
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
Dvar::Register<const char*>("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
SVMOTD = Dvar::Register<const char*>("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers");
|
||||
});
|
||||
|
||||
// Post initialization point
|
||||
Utils::Hook(0x60BFBF, Dedicated::PostInitializationStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x60BFBF, PostInitializationStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
Utils::Hook(0x47DDB2, Com_ClampMsec_Stub, HOOK_JUMP).install()->quick(); // Com_Frame_Try_Block_Function
|
||||
Utils::Hook::Nop(0x47DDB2 + 5, 1);
|
||||
|
||||
// Transmit custom data
|
||||
Scheduler::Loop([]
|
||||
@ -238,16 +265,16 @@ namespace Components
|
||||
}, Scheduler::Pipeline::SERVER, 10s);
|
||||
|
||||
// Heartbeats
|
||||
Scheduler::Once(Dedicated::Heartbeat, Scheduler::Pipeline::SERVER);
|
||||
Scheduler::Loop(Dedicated::Heartbeat, Scheduler::Pipeline::SERVER, 2min);
|
||||
Scheduler::Once(Heartbeat, Scheduler::Pipeline::SERVER);
|
||||
Scheduler::Loop(Heartbeat, Scheduler::Pipeline::SERVER, 2min);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < ARRAYSIZE(Dedicated::PlayerGuids); ++i)
|
||||
for (int i = 0; i < ARRAYSIZE(PlayerGuids); ++i)
|
||||
{
|
||||
Dedicated::PlayerGuids[i][0].bits = 0;
|
||||
Dedicated::PlayerGuids[i][1].bits = 0;
|
||||
PlayerGuids[i][0].bits = 0;
|
||||
PlayerGuids[i][1].bits = 0;
|
||||
}
|
||||
|
||||
// Intercept server commands
|
||||
@ -255,12 +282,12 @@ namespace Components
|
||||
{
|
||||
for (int client = 0; client < 18; client++)
|
||||
{
|
||||
Dedicated::PlayerGuids[client][0].bits = strtoull(params->get(2 * client + 1), nullptr, 16);
|
||||
Dedicated::PlayerGuids[client][1].bits = strtoull(params->get(2 * client + 2), nullptr, 16);
|
||||
PlayerGuids[client][0].bits = std::strtoull(params->get(2 * client + 1), nullptr, 16);
|
||||
PlayerGuids[client][1].bits = std::strtoull(params->get(2 * client + 2), nullptr, 16);
|
||||
|
||||
if (Steam::Proxy::SteamFriends && Dedicated::PlayerGuids[client][1].bits != 0)
|
||||
if (Steam::Proxy::SteamFriends && PlayerGuids[client][1].bits != 0)
|
||||
{
|
||||
Steam::Proxy::SteamFriends->SetPlayedWith(Dedicated::PlayerGuids[client][1]);
|
||||
Steam::Proxy::SteamFriends->SetPlayedWith(PlayerGuids[client][1]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,9 +297,9 @@ namespace Components
|
||||
|
||||
Scheduler::Loop([]
|
||||
{
|
||||
if (Dedicated::IsRunning())
|
||||
if (IsRunning())
|
||||
{
|
||||
Dedicated::TransmitGuids();
|
||||
TransmitGuids();
|
||||
}
|
||||
}, Scheduler::Pipeline::SERVER, 15s);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ namespace Components
|
||||
|
||||
static SteamID PlayerGuids[18][2];
|
||||
static Dvar::Var SVLanOnly;
|
||||
static Dvar::Var SVMOTD;
|
||||
static Dvar::Var COMLogFilter;
|
||||
|
||||
static bool IsEnabled();
|
||||
@ -22,10 +23,11 @@ namespace Components
|
||||
static void PostInitialization();
|
||||
static void PostInitializationStub();
|
||||
|
||||
static void Com_ClampMsec(int msec);
|
||||
static void Com_ClampMsec_Stub();
|
||||
|
||||
static void TransmitGuids();
|
||||
|
||||
static void TimeWrapStub(Game::errorParm_t code, const char* message);
|
||||
|
||||
static Game::dvar_t* Dvar_RegisterSVNetworkFps(const char* dvarName, int value, int min, int max, int flags, const char* description);
|
||||
};
|
||||
}
|
||||
|
148
src/Components/Modules/Discord.cpp
Normal file
148
src/Components/Modules/Discord.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Discord.hpp"
|
||||
#include "Party.hpp"
|
||||
|
||||
#include <discord_rpc.h>
|
||||
|
||||
namespace Components
|
||||
{
|
||||
static DiscordRichPresence DiscordPresence;
|
||||
|
||||
bool Discord::Initialized_;
|
||||
|
||||
static unsigned int GetDiscordNonce()
|
||||
{
|
||||
static auto nonce = Utils::Cryptography::Rand::GenerateInt();
|
||||
return nonce;
|
||||
}
|
||||
|
||||
static void Ready([[maybe_unused]] const DiscordUser* request)
|
||||
{
|
||||
ZeroMemory(&DiscordPresence, sizeof(DiscordPresence));
|
||||
|
||||
DiscordPresence.instance = 1;
|
||||
|
||||
Logger::Print("Discord: Ready\n");
|
||||
|
||||
Discord_UpdatePresence(&DiscordPresence);
|
||||
}
|
||||
|
||||
static void JoinGame(const char* joinSecret)
|
||||
{
|
||||
Game::Cbuf_AddText(0, Utils::String::VA("connect %s\n", joinSecret));
|
||||
}
|
||||
|
||||
static void JoinRequest(const DiscordUser* request)
|
||||
{
|
||||
Logger::Debug("Discord: Join request from {} ({})\n", request->username, request->userId);
|
||||
Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
|
||||
}
|
||||
|
||||
static void Errored(const int errorCode, const char* message)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_ERROR, "Discord: Error ({}): {}\n", errorCode, message);
|
||||
}
|
||||
|
||||
void Discord::UpdateDiscord()
|
||||
{
|
||||
Discord_RunCallbacks();
|
||||
|
||||
if (!Game::CL_IsCgameInitialized())
|
||||
{
|
||||
DiscordPresence.details = "Multiplayer";
|
||||
DiscordPresence.state = "Main Menu";
|
||||
DiscordPresence.largeImageKey = "menu_multiplayer";
|
||||
|
||||
DiscordPresence.partySize = 0;
|
||||
DiscordPresence.partyMax = 0;
|
||||
DiscordPresence.startTimestamp = 0;
|
||||
|
||||
Discord_UpdatePresence(&DiscordPresence);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* map = Game::UI_GetMapDisplayName((*Game::ui_mapname)->current.string);
|
||||
|
||||
const Game::StringTable* table;
|
||||
Game::StringTable_GetAsset_FastFile("mp/gameTypesTable.csv", &table);
|
||||
const auto row = Game::StringTable_LookupRowNumForValue(table, 0, (*Game::ui_gametype)->current.string);
|
||||
if (row != -1)
|
||||
{
|
||||
const auto* value = Game::StringTable_GetColumnValueForRow(table, row, 1);
|
||||
const auto* localize = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_LOCALIZE_ENTRY, value).localize;
|
||||
DiscordPresence.details = Utils::String::Format("{} on {}", localize ? localize->value : "Team Deathmatch", map);
|
||||
}
|
||||
else
|
||||
{
|
||||
DiscordPresence.details = Utils::String::Format("Team Deathmatch on {}", map);
|
||||
}
|
||||
|
||||
const auto* hostName = Game::cls->servername;
|
||||
if (std::strcmp(hostName, "localhost") == 0)
|
||||
{
|
||||
DiscordPresence.state = "Private Match";
|
||||
DiscordPresence.partyPrivacy = DISCORD_PARTY_PRIVATE;
|
||||
}
|
||||
else
|
||||
{
|
||||
char hostNameBuffer[256]{};
|
||||
TextRenderer::StripColors(Party::GetHostName().data(), hostNameBuffer, sizeof(hostNameBuffer));
|
||||
TextRenderer::StripAllTextIcons(hostNameBuffer, hostNameBuffer, sizeof(hostNameBuffer));
|
||||
|
||||
DiscordPresence.state = hostNameBuffer;
|
||||
DiscordPresence.partyPrivacy = DISCORD_PARTY_PUBLIC;
|
||||
}
|
||||
|
||||
std::hash<Network::Address> hashFn;
|
||||
const auto address = Party::Target();
|
||||
|
||||
DiscordPresence.partyId = Utils::String::VA("%zu", hashFn(address) ^ GetDiscordNonce());
|
||||
DiscordPresence.joinSecret = address.getCString();
|
||||
DiscordPresence.partySize = Game::cgArray[0].snap ? Game::cgArray[0].snap->numClients : 1;
|
||||
DiscordPresence.partyMax = Party::GetMaxClients();
|
||||
|
||||
if (!DiscordPresence.startTimestamp)
|
||||
{
|
||||
DiscordPresence.startTimestamp = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
DiscordPresence.largeImageKey = "menu_multiplayer";
|
||||
|
||||
Discord_UpdatePresence(&DiscordPresence);
|
||||
}
|
||||
|
||||
Discord::Discord()
|
||||
{
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordEventHandlers handlers;
|
||||
ZeroMemory(&handlers, sizeof(handlers));
|
||||
handlers.ready = Ready;
|
||||
handlers.errored = Errored;
|
||||
handlers.disconnected = Errored;
|
||||
handlers.joinGame = JoinGame;
|
||||
handlers.spectateGame = nullptr;
|
||||
handlers.joinRequest = JoinRequest;
|
||||
|
||||
Discord_Initialize("1072930169385394288", &handlers, 1, nullptr);
|
||||
|
||||
Scheduler::Once(UpdateDiscord, Scheduler::Pipeline::MAIN);
|
||||
Scheduler::Loop(UpdateDiscord, Scheduler::Pipeline::MAIN, 15s);
|
||||
|
||||
Initialized_ = true;
|
||||
}
|
||||
|
||||
void Discord::preDestroy()
|
||||
{
|
||||
if (!Initialized_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Discord_Shutdown();
|
||||
}
|
||||
}
|
17
src/Components/Modules/Discord.hpp
Normal file
17
src/Components/Modules/Discord.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace Components
|
||||
{
|
||||
class Discord : public Component
|
||||
{
|
||||
public:
|
||||
Discord();
|
||||
|
||||
void preDestroy() override;
|
||||
|
||||
private:
|
||||
static bool Initialized_;
|
||||
|
||||
static void UpdateDiscord();
|
||||
};
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include "Discovery.hpp"
|
||||
#include "ServerList.hpp"
|
||||
|
||||
@ -28,6 +30,8 @@ namespace Components
|
||||
IsTerminating = false;
|
||||
Thread = std::thread([]
|
||||
{
|
||||
Com_InitThreadData();
|
||||
|
||||
while (!IsTerminating)
|
||||
{
|
||||
if (IsPerforming)
|
||||
@ -47,7 +51,7 @@ namespace Components
|
||||
IsPerforming = false;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
Game::Sys_Sleep(50);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,10 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
#include <Utils/WebIO.hpp>
|
||||
|
||||
#include "Download.hpp"
|
||||
#include "MapRotation.hpp"
|
||||
#include "Party.hpp"
|
||||
#include "ServerInfo.hpp"
|
||||
|
||||
#include <mongoose.h>
|
||||
@ -8,6 +13,13 @@ namespace Components
|
||||
{
|
||||
static mg_mgr Mgr;
|
||||
|
||||
Dvar::Var Download::SV_wwwDownload;
|
||||
Dvar::Var Download::SV_wwwBaseUrl;
|
||||
|
||||
Dvar::Var Download::UIDlTimeLeft;
|
||||
Dvar::Var Download::UIDlProgress;
|
||||
Dvar::Var Download::UIDlTransRate;
|
||||
|
||||
Download::ClientDownload Download::CLDownload;
|
||||
|
||||
std::thread Download::ServerThread;
|
||||
@ -27,9 +39,9 @@ namespace Components
|
||||
|
||||
Scheduler::Once([]
|
||||
{
|
||||
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(0));
|
||||
Dvar::Var("ui_dl_progress").set("(0/0) %");
|
||||
Dvar::Var("ui_dl_transRate").set("0.0 MB/s");
|
||||
UIDlTimeLeft.set(Utils::String::FormatTimeSpan(0));
|
||||
UIDlProgress.set("(0/0) %");
|
||||
UIDlTransRate.set("0.0 MB/s");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
|
||||
Command::Execute("openmenu mod_download_popmenu", false);
|
||||
@ -138,7 +150,7 @@ namespace Components
|
||||
}
|
||||
|
||||
auto host = "http://" + download->target.getString();
|
||||
auto fastHost = Dvar::Var("sv_wwwBaseUrl").get<std::string>();
|
||||
auto fastHost = SV_wwwBaseUrl.get<std::string>();
|
||||
if (Utils::String::StartsWith(fastHost, "https://"))
|
||||
{
|
||||
download->thread.detach();
|
||||
@ -173,7 +185,7 @@ namespace Components
|
||||
// -mod.ff
|
||||
// /-mod2
|
||||
// ...
|
||||
if (Dvar::Var("sv_wwwDownload").get<bool>())
|
||||
if (SV_wwwDownload.get<bool>())
|
||||
{
|
||||
if (!Utils::String::EndsWith(fastHost, "/")) fastHost.append("/");
|
||||
url = fastHost + path;
|
||||
@ -211,7 +223,7 @@ namespace Components
|
||||
DownloadProgress(&fDownload, bytes - fDownload.receivedBytes);
|
||||
});
|
||||
|
||||
bool result = false;
|
||||
auto result = false;
|
||||
fDownload.buffer = webIO.get(url, &result);
|
||||
if (!result) fDownload.buffer.clear();
|
||||
|
||||
@ -321,6 +333,7 @@ namespace Components
|
||||
Scheduler::Once([]
|
||||
{
|
||||
Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data());
|
||||
const_cast<Game::dvar_t*>((*Game::fs_gameDirVar))->modified = true;
|
||||
|
||||
mod.clear();
|
||||
|
||||
@ -336,10 +349,6 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Server
|
||||
|
||||
void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes)
|
||||
{
|
||||
fDownload->receivedBytes += bytes;
|
||||
@ -365,22 +374,22 @@ namespace Components
|
||||
Scheduler::Once([]
|
||||
{
|
||||
framePushed = false;
|
||||
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
|
||||
}, Scheduler::Pipeline::CLIENT);
|
||||
UIDlProgress.set(std::format("({}/{}) {}%", dlIndex, dlSize, dlProgress));
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
}
|
||||
|
||||
auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
|
||||
if (delta > 300)
|
||||
{
|
||||
bool doFormat = fDownload->download->lastTimeStamp != 0;
|
||||
const auto doFormat = fDownload->download->lastTimeStamp != 0;
|
||||
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
|
||||
|
||||
auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
|
||||
const auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
|
||||
|
||||
int timeLeft = 0;
|
||||
if (fDownload->download->timeStampBytes)
|
||||
{
|
||||
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
|
||||
const double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
|
||||
timeLeft = static_cast<int>(timeLeftD);
|
||||
}
|
||||
|
||||
@ -394,8 +403,8 @@ namespace Components
|
||||
|
||||
Scheduler::Once([]
|
||||
{
|
||||
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
|
||||
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
|
||||
UIDlTimeLeft.set(Utils::String::FormatTimeSpan(dlTimeLeft));
|
||||
UIDlTransRate.set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
}
|
||||
|
||||
@ -403,6 +412,10 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Server
|
||||
|
||||
static std::string InfoHandler()
|
||||
{
|
||||
const auto status = ServerInfo::GetInfo();
|
||||
@ -411,6 +424,7 @@ namespace Components
|
||||
std::unordered_map<std::string, nlohmann::json> info;
|
||||
info["status"] = status.to_json();
|
||||
info["host"] = host.to_json();
|
||||
info["map_rotation"] = MapRotation::to_json();
|
||||
|
||||
std::vector<nlohmann::json> players;
|
||||
|
||||
@ -449,35 +463,36 @@ namespace Components
|
||||
static std::string ListHandler()
|
||||
{
|
||||
static nlohmann::json jsonList;
|
||||
static auto handled = false;
|
||||
static std::filesystem::path fsGamePre;
|
||||
|
||||
const std::filesystem::path fs_gameDirVar((*Game::fs_gameDirVar)->current.string);
|
||||
const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string;
|
||||
|
||||
if (!fs_gameDirVar.empty() && !handled)
|
||||
if (!fsGame.empty() && (fsGamePre != fsGame))
|
||||
{
|
||||
handled = true;
|
||||
fsGamePre = fsGame;
|
||||
|
||||
std::vector<nlohmann::json> fileList;
|
||||
|
||||
const auto path = Dvar::Var("fs_basepath").get<std::string>() / fs_gameDirVar;
|
||||
const auto path = (*Game::fs_basepath)->current.string / fsGame;
|
||||
auto list = FileSystem::GetSysFileList(path.generic_string(), "iwd", false);
|
||||
list.emplace_back("mod.ff");
|
||||
|
||||
for (const auto& file : list)
|
||||
{
|
||||
auto filename = path / file;
|
||||
if (file.find("_svr_") != std::string::npos)
|
||||
|
||||
if (file.find("_svr_") != std::string::npos) // Files that are 'server only' are skipped
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, nlohmann::json> jsonFileList;
|
||||
auto fileBuffer = Utils::IO::ReadFile(filename.generic_string());
|
||||
if (fileBuffer.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, nlohmann::json> jsonFileList;
|
||||
jsonFileList["name"] = file;
|
||||
jsonFileList["size"] = fileBuffer.size();
|
||||
jsonFileList["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
||||
@ -496,7 +511,7 @@ namespace Components
|
||||
static std::string mapNamePre;
|
||||
static nlohmann::json jsonList;
|
||||
|
||||
const auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
||||
const std::string mapName = Party::IsInUserMapLobby() ? (*Game::ui_mapname)->current.string : Maps::GetUserMap()->getName();
|
||||
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
|
||||
{
|
||||
mapNamePre.clear();
|
||||
@ -508,7 +523,7 @@ namespace Components
|
||||
|
||||
mapNamePre = mapName;
|
||||
|
||||
const std::filesystem::path basePath(Dvar::Var("fs_basepath").get<std::string>());
|
||||
const std::filesystem::path basePath = (*Game::fs_basepath)->current.string;
|
||||
const auto path = basePath / "usermaps" / mapName;
|
||||
|
||||
for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
||||
@ -541,17 +556,16 @@ namespace Components
|
||||
|
||||
Utils::String::Replace(url, "\\", "/");
|
||||
|
||||
// Strip /file
|
||||
url = url.substr(6);
|
||||
url = url.substr(6); // Strip /file
|
||||
Utils::String::Replace(url, "%20", " ");
|
||||
|
||||
auto isMap = false;
|
||||
if (url.starts_with("map/"))
|
||||
{
|
||||
isMap = true;
|
||||
url = url.substr(4);
|
||||
url = url.substr(4); // Strip map/
|
||||
|
||||
auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
||||
std::string mapName = (Party::IsInUserMapLobby() ? (*Game::ui_mapname)->current.string : Maps::GetUserMap()->getName());
|
||||
auto isValidFile = false;
|
||||
for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
||||
{
|
||||
@ -579,10 +593,10 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
std::string file;
|
||||
const auto fsGame = Dvar::Var("fs_game").get<std::string>();
|
||||
const auto path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + (isMap ? "" : fsGame + "\\") + url;
|
||||
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
|
||||
const auto path = std::format("{}\\{}{}", (*Game::fs_basepath)->current.string, isMap ? ""s : (fsGame + "\\"s), url);
|
||||
|
||||
std::string file;
|
||||
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
|
||||
{
|
||||
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found %s", path.data());
|
||||
@ -598,44 +612,7 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
static void HTMLHandler(mg_connection* c, mg_http_message* hm)
|
||||
{
|
||||
auto url = "html" + std::string(hm->uri.ptr, hm->uri.len);
|
||||
FileSystem::File file;
|
||||
|
||||
if (url.ends_with("/"))
|
||||
{
|
||||
url.append("index.html");
|
||||
file = FileSystem::File(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = FileSystem::File(url);
|
||||
if (!file.exists())
|
||||
{
|
||||
url.append("/index.html");
|
||||
file = FileSystem::File(url);
|
||||
}
|
||||
}
|
||||
|
||||
const auto mimeType = Utils::GetMimeType(url);
|
||||
|
||||
if (file.exists())
|
||||
{
|
||||
mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n");
|
||||
mg_printf(c, "Content-Type: %s\r\n", mimeType.data());
|
||||
mg_printf(c, "Content-Length: %d\r\n", static_cast<int>(file.getBuffer().size()));
|
||||
mg_printf(c, "%s", "Connection: close\r\n");
|
||||
mg_printf(c, "%s", "\r\n");
|
||||
mg_send(c, file.getBuffer().data(), file.getBuffer().size());
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
static void EventHandler(mg_connection* c, int ev, void* ev_data, [[maybe_unused]] void* fn_data)
|
||||
static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data)
|
||||
{
|
||||
if (ev != MG_EV_HTTP_MSG)
|
||||
{
|
||||
@ -643,7 +620,7 @@ namespace Components
|
||||
}
|
||||
|
||||
auto* hm = static_cast<mg_http_message*>(ev_data);
|
||||
std::string url(hm->uri.ptr, hm->uri.len);
|
||||
const std::string url(hm->uri.ptr, hm->uri.len);
|
||||
|
||||
if (url.starts_with("/info"))
|
||||
{
|
||||
@ -666,8 +643,12 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
HTMLHandler(c, hm);
|
||||
mg_http_serve_opts opts = { .root_dir = "iw4x/html" }; // Serve local dir
|
||||
mg_http_serve_dir(c, hm, &opts);
|
||||
}
|
||||
|
||||
c->is_resp = FALSE; // This is important, the lack of this line of code will make the server die (in-game)
|
||||
c->is_draining = TRUE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@ -680,37 +661,41 @@ namespace Components
|
||||
|
||||
if (Dedicated::IsEnabled())
|
||||
{
|
||||
mg_mgr_init(&Mgr);
|
||||
|
||||
Network::OnStart([]
|
||||
if (!Flags::HasFlag("disable-mongoose"))
|
||||
{
|
||||
const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr);
|
||||
if (!nc)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n");
|
||||
}
|
||||
});
|
||||
mg_mgr_init(&Mgr);
|
||||
|
||||
ServerRunning = true;
|
||||
Terminate = false;
|
||||
ServerThread = Utils::Thread::CreateNamedThread("Mongoose", []
|
||||
{
|
||||
Com_InitThreadData();
|
||||
|
||||
while (!Terminate)
|
||||
Network::OnStart([]
|
||||
{
|
||||
mg_mgr_poll(&Mgr, 100);
|
||||
}
|
||||
});
|
||||
const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr);
|
||||
if (!nc)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n");
|
||||
Terminate = true;
|
||||
}
|
||||
});
|
||||
|
||||
ServerRunning = true;
|
||||
Terminate = false;
|
||||
ServerThread = Utils::Thread::CreateNamedThread("Mongoose", []
|
||||
{
|
||||
Com_InitThreadData();
|
||||
|
||||
while (!Terminate)
|
||||
{
|
||||
mg_mgr_poll(&Mgr, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Scheduler::Once([]
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
Dvar::Register<const char*>("ui_dl_timeLeft", "", Game::DVAR_NONE, "");
|
||||
Dvar::Register<const char*>("ui_dl_progress", "", Game::DVAR_NONE, "");
|
||||
Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, "");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
UIDlTimeLeft = Dvar::Register<const char*>("ui_dl_timeLeft", "", Game::DVAR_NONE, "");
|
||||
UIDlProgress = Dvar::Register<const char*>("ui_dl_progress", "", Game::DVAR_NONE, "");
|
||||
UIDlTransRate = Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, "");
|
||||
});
|
||||
|
||||
UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
||||
{
|
||||
@ -718,11 +703,11 @@ namespace Components
|
||||
});
|
||||
}
|
||||
|
||||
Scheduler::Once([]
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server.");
|
||||
Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
SV_wwwDownload = Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server.");
|
||||
SV_wwwBaseUrl = Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
|
||||
});
|
||||
}
|
||||
|
||||
Download::~Download()
|
||||
|
@ -13,6 +13,13 @@ namespace Components
|
||||
static void InitiateClientDownload(const std::string& mod, bool needPassword, bool map = false);
|
||||
static void InitiateMapDownload(const std::string& map, bool needPassword);
|
||||
|
||||
static Dvar::Var SV_wwwDownload;
|
||||
static Dvar::Var SV_wwwBaseUrl;
|
||||
|
||||
static Dvar::Var UIDlTimeLeft;
|
||||
static Dvar::Var UIDlProgress;
|
||||
static Dvar::Var UIDlTransRate;
|
||||
|
||||
private:
|
||||
class ClientDownload
|
||||
{
|
||||
|
@ -2,16 +2,13 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg";
|
||||
|
||||
Dvar::Var Dvar::Name;
|
||||
|
||||
Dvar::Var::Var(const std::string& dvarName)
|
||||
: dvar_(Game::Dvar_FindVar(dvarName.data()))
|
||||
{
|
||||
this->dvar_ = Game::Dvar_FindVar(dvarName.data());
|
||||
|
||||
// If the dvar can't be found it will be registered as an empty string dvar
|
||||
if (this->dvar_ == nullptr)
|
||||
if (!this->dvar_)
|
||||
{
|
||||
this->dvar_ = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", Game::DVAR_SOURCE_INTERNAL));
|
||||
}
|
||||
@ -24,13 +21,17 @@ namespace Components
|
||||
|
||||
template <> const char* Dvar::Var::get()
|
||||
{
|
||||
if (this->dvar_ == nullptr)
|
||||
if (!this->dvar_)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_STRING || this->dvar_->type == Game::DVAR_TYPE_ENUM)
|
||||
{
|
||||
if (this->dvar_->current.string != nullptr)
|
||||
if (this->dvar_->current.string)
|
||||
{
|
||||
return this->dvar_->current.string;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -38,8 +39,10 @@ namespace Components
|
||||
|
||||
template <> int Dvar::Var::get()
|
||||
{
|
||||
if (this->dvar_ == nullptr)
|
||||
if (!this->dvar_)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_INT || this->dvar_->type == Game::DVAR_TYPE_ENUM)
|
||||
{
|
||||
@ -51,8 +54,10 @@ namespace Components
|
||||
|
||||
template <> unsigned int Dvar::Var::get()
|
||||
{
|
||||
if (this->dvar_ == nullptr)
|
||||
if (!this->dvar_)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_INT)
|
||||
{
|
||||
@ -64,8 +69,10 @@ namespace Components
|
||||
|
||||
template <> float Dvar::Var::get()
|
||||
{
|
||||
if (this->dvar_ == nullptr)
|
||||
if (!this->dvar_)
|
||||
{
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT)
|
||||
{
|
||||
@ -75,26 +82,12 @@ namespace Components
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
template <> float* Dvar::Var::get()
|
||||
{
|
||||
static Game::vec4_t vector{0.f, 0.f, 0.f, 0.f};
|
||||
|
||||
if (this->dvar_ == nullptr)
|
||||
return vector;
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3 ||
|
||||
this->dvar_->type == Game::DVAR_TYPE_FLOAT_4)
|
||||
{
|
||||
return this->dvar_->current.vector;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
template <> bool Dvar::Var::get()
|
||||
{
|
||||
if (this->dvar_ == nullptr)
|
||||
if (!this->dvar_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_BOOL)
|
||||
{
|
||||
@ -111,7 +104,9 @@ namespace Components
|
||||
|
||||
void Dvar::Var::set(const char* string)
|
||||
{
|
||||
assert(string);
|
||||
assert(this->dvar_->type == Game::DVAR_TYPE_STRING);
|
||||
|
||||
if (this->dvar_)
|
||||
{
|
||||
Game::Dvar_SetString(this->dvar_, string);
|
||||
@ -206,17 +201,7 @@ namespace Components
|
||||
return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag, description);
|
||||
}
|
||||
|
||||
void Dvar::ResetDvarsValue()
|
||||
{
|
||||
if (!Utils::IO::FileExists(ArchiveDvarPath))
|
||||
return;
|
||||
|
||||
Command::Execute("exec archivedvars.cfg", true);
|
||||
// Clean up
|
||||
Utils::IO::RemoveFile(ArchiveDvarPath);
|
||||
}
|
||||
|
||||
Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, std::uint16_t flags, const char* description)
|
||||
const Game::dvar_t* Dvar::Dvar_RegisterName(const char* dvarName, const char* /*value*/, std::uint16_t flags, const char* description)
|
||||
{
|
||||
// Name watcher
|
||||
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
||||
@ -250,19 +235,29 @@ namespace Components
|
||||
{
|
||||
const char* steamName = Steam::Proxy::SteamFriends->GetPersonaName();
|
||||
|
||||
if (steamName && *steamName != '\0')
|
||||
if (steamName && *steamName)
|
||||
{
|
||||
username = steamName;
|
||||
}
|
||||
}
|
||||
|
||||
Name = Register<const char*>(name, username.data(), flags | Game::DVAR_ARCHIVE, description);
|
||||
Name = Register<const char*>(dvarName, username.data(), flags | Game::DVAR_ARCHIVE, description);
|
||||
return Name.get<Game::dvar_t*>();
|
||||
}
|
||||
|
||||
const Game::dvar_t* Dvar::Dvar_RegisterSVNetworkFps(const char* dvarName, int /*value*/, int min, int /*max*/, std::uint16_t /*flags*/, const char* description)
|
||||
{
|
||||
return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::DVAR_NONE, description);
|
||||
}
|
||||
|
||||
const Game::dvar_t* Dvar::Dvar_RegisterPerkExtendedMeleeRange(const char* dvarName, float value, float min, float /*max*/, std::uint16_t flags, const char* description)
|
||||
{
|
||||
return Game::Dvar_RegisterFloat(dvarName, value, min, 10000.0f, flags, description);
|
||||
}
|
||||
|
||||
void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string)
|
||||
{
|
||||
static std::array<const char*, 8> exceptions =
|
||||
static std::array exceptions =
|
||||
{
|
||||
"ui_showEndOfGame",
|
||||
"systemlink",
|
||||
@ -276,7 +271,7 @@ namespace Components
|
||||
|
||||
for (const auto& entry : exceptions)
|
||||
{
|
||||
if (Utils::String::Compare(dvarName, entry))
|
||||
if (!_stricmp(dvarName, entry))
|
||||
{
|
||||
Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_INTERNAL);
|
||||
return;
|
||||
@ -291,47 +286,52 @@ namespace Components
|
||||
Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_EXTERNAL);
|
||||
}
|
||||
|
||||
bool Dvar::AreArchiveDvarsProtected()
|
||||
bool Dvar::AreArchiveDvarsUnprotected()
|
||||
{
|
||||
static std::optional<bool> flag;
|
||||
|
||||
if (!flag.has_value())
|
||||
{
|
||||
flag.emplace(Flags::HasFlag("protect-saved-dvars"));
|
||||
flag.emplace(Flags::HasFlag("unprotect-dvars"));
|
||||
}
|
||||
|
||||
return flag.value();
|
||||
}
|
||||
|
||||
void Dvar::SaveArchiveDvar(const Game::dvar_t* var)
|
||||
bool Dvar::IsSettingDvarsDisabled()
|
||||
{
|
||||
if (!Utils::IO::FileExists(ArchiveDvarPath))
|
||||
static std::optional<bool> flag;
|
||||
|
||||
if (!flag.has_value())
|
||||
{
|
||||
Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n", false);
|
||||
flag.emplace(Flags::HasFlag("protect-dvars"));
|
||||
}
|
||||
|
||||
Utils::IO::WriteFile(ArchiveDvarPath, std::format("set {} \"{}\"\n", var->name, Game::Dvar_DisplayableValue(var)), true);
|
||||
return flag.value();
|
||||
}
|
||||
|
||||
void Dvar::DvarSetFromStringByName_Stub(const char* dvarName, const char* value)
|
||||
{
|
||||
if (IsSettingDvarsDisabled())
|
||||
{
|
||||
Logger::Debug("Not allowing server to set '{}'", dvarName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the dvar original value if it has the archive flag
|
||||
const auto* dvar = Game::Dvar_FindVar(dvarName);
|
||||
if (dvar != nullptr && dvar->flags & Game::DVAR_ARCHIVE)
|
||||
if (dvar && dvar->flags & Game::DVAR_ARCHIVE)
|
||||
{
|
||||
if (AreArchiveDvarsProtected())
|
||||
if (!AreArchiveDvarsUnprotected())
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvar->name);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DVARS
|
||||
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Server is overriding saved dvar '{}'\n", dvarName);
|
||||
#endif
|
||||
SaveArchiveDvar(dvar);
|
||||
}
|
||||
|
||||
if (dvar != nullptr && std::strcmp(dvar->name, "com_errorResolveCommand") == 0)
|
||||
if (dvar && std::strcmp(dvar->name, "com_errorResolveCommand") == 0)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to set '{}'\n", dvar->name);
|
||||
return;
|
||||
@ -417,11 +417,6 @@ namespace Components
|
||||
// remove write protection from fs_game
|
||||
Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT);
|
||||
|
||||
// cheat protect g_hardcore
|
||||
Utils::Hook::Xor<std::uint32_t>(0x5E374F, Game::DVAR_CHEAT);
|
||||
Utils::Hook::Xor<std::uint32_t>(0x4D3689, Game::DVAR_CHEAT);
|
||||
Utils::Hook::Xor<std::uint32_t>(0x4197C3, Game::DVAR_CHEAT);
|
||||
|
||||
// set cg_fov max to 160.0
|
||||
// because that's the max on SP
|
||||
static float cg_Fov = 160.0f;
|
||||
@ -440,6 +435,12 @@ namespace Components
|
||||
// Hook dvar 'name' registration
|
||||
Utils::Hook(0x40531C, Dvar_RegisterName, HOOK_CALL).install()->quick();
|
||||
|
||||
// Hook dvar 'sv_network_fps' registration
|
||||
Utils::Hook(0x4D3C7B, Dvar_RegisterSVNetworkFps, HOOK_CALL).install()->quick();
|
||||
|
||||
// Hook dvar 'perk_extendedMeleeRange' and set a higher max, better than having people force this with external programs
|
||||
Utils::Hook(0x492D2F, Dvar_RegisterPerkExtendedMeleeRange, HOOK_CALL).install()->quick();
|
||||
|
||||
// un-cheat safeArea_* and add archive flags
|
||||
Utils::Hook::Xor<std::uint32_t>(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal
|
||||
Utils::Hook::Xor<std::uint32_t>(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical
|
||||
@ -467,12 +468,6 @@ namespace Components
|
||||
// Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server
|
||||
Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick();
|
||||
|
||||
// If the game closed abruptly, the dvars would not have been restored
|
||||
Scheduler::Once(ResetDvarsValue, Scheduler::Pipeline::MAIN);
|
||||
|
||||
// Reset archive dvars when client leaves a server
|
||||
Events::OnSteamDisconnect(ResetDvarsValue);
|
||||
|
||||
// For debugging
|
||||
Utils::Hook(0x6483FA, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x648438, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
||||
@ -480,9 +475,4 @@ namespace Components
|
||||
// Fix crash
|
||||
Utils::Hook(0x4B7120, Dvar_EnumToString_Stub, HOOK_JUMP).install()->quick();
|
||||
}
|
||||
|
||||
Dvar::~Dvar()
|
||||
{
|
||||
Utils::IO::RemoveFile(ArchiveDvarPath);
|
||||
}
|
||||
}
|
||||
|
@ -32,25 +32,24 @@ namespace Components
|
||||
};
|
||||
|
||||
Dvar();
|
||||
~Dvar();
|
||||
|
||||
// Only strings and bools use this type of declaration
|
||||
template<typename T> static Var Register(const char* dvarName, T value, std::uint16_t flag, const char* description);
|
||||
template<typename T> static Var Register(const char* dvarName, T value, T min, T max, std::uint16_t flag, const char* description);
|
||||
|
||||
static void ResetDvarsValue();
|
||||
|
||||
private:
|
||||
static const char* ArchiveDvarPath;
|
||||
static Var Name;
|
||||
|
||||
static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, std::uint16_t flags, const char* description);
|
||||
private:
|
||||
|
||||
static const Game::dvar_t* Dvar_RegisterName(const char* dvarName, const char* value, std::uint16_t flags, const char* description);
|
||||
static const Game::dvar_t* Dvar_RegisterSVNetworkFps(const char* dvarName, int value, int min, int max, std::uint16_t flags, const char* description);
|
||||
static const Game::dvar_t* Dvar_RegisterPerkExtendedMeleeRange(const char* dvarName, float value, float min, float max, std::uint16_t flags, const char* description);
|
||||
|
||||
static void SetFromStringByNameExternal(const char* dvarName, const char* string);
|
||||
static void SetFromStringByNameSafeExternal(const char* dvarName, const char* string);
|
||||
|
||||
static bool AreArchiveDvarsProtected();
|
||||
static void SaveArchiveDvar(const Game::dvar_t* var);
|
||||
static bool AreArchiveDvarsUnprotected();
|
||||
static bool IsSettingDvarsDisabled();
|
||||
static void DvarSetFromStringByName_Stub(const char* dvarName, const char* value);
|
||||
|
||||
static void OnRegisterVariant(Game::dvar_t* dvar);
|
||||
|
@ -7,14 +7,14 @@ namespace Components
|
||||
|
||||
int Elevators::PM_CorrectAllSolid(Game::pmove_s* pm, Game::pml_t* pml, Game::trace_t* trace)
|
||||
{
|
||||
assert(pm != nullptr);
|
||||
assert(pm->ps != nullptr);
|
||||
assert(pm);
|
||||
assert(pm->ps);
|
||||
|
||||
Game::vec3_t point;
|
||||
auto* ps = pm->ps;
|
||||
|
||||
auto i = 0;
|
||||
const auto elevatorSetting = Elevators::BG_Elevators.get<int>();
|
||||
const auto elevatorSetting = BG_Elevators.get<int>();
|
||||
while (true)
|
||||
{
|
||||
point[0] = ps->origin[0] + (*Game::CorrectSolidDeltas)[i][0];
|
||||
@ -24,7 +24,7 @@ namespace Components
|
||||
Game::PM_playerTrace(pm, trace, point, point, &pm->bounds, ps->clientNum, pm->tracemask);
|
||||
|
||||
// If the player wishes to glitch without effort they can do so
|
||||
if (!trace->startsolid || elevatorSetting == Elevators::EASY)
|
||||
if (!trace->startsolid || elevatorSetting == EASY)
|
||||
{
|
||||
ps->origin[0] = point[0];
|
||||
ps->origin[1] = point[1];
|
||||
@ -35,7 +35,7 @@ namespace Components
|
||||
|
||||
// If elevators are disabled we need to check that startsolid is false before proceeding
|
||||
// like later versions of the game do
|
||||
if (!trace->startsolid || elevatorSetting >= Elevators::ENABLED)
|
||||
if (!trace->startsolid || elevatorSetting >= ENABLED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -69,7 +69,7 @@ namespace Components
|
||||
Game::PM_Trace(pm, results, start, end, bounds, passEntityNum, contentMask);
|
||||
|
||||
// Allow the player to stand even when there is no headroom
|
||||
if (Elevators::BG_Elevators.get<int>() == Elevators::EASY)
|
||||
if (BG_Elevators.get<int>() == EASY)
|
||||
{
|
||||
results->allsolid = false;
|
||||
}
|
||||
@ -85,7 +85,7 @@ namespace Components
|
||||
push [esp + 0x8 + 0x24]
|
||||
push [esp + 0x8 + 0x24]
|
||||
push eax
|
||||
call Elevators::PM_CorrectAllSolid
|
||||
call PM_CorrectAllSolid
|
||||
add esp, 0xC
|
||||
|
||||
mov [esp + 0x20], eax
|
||||
@ -98,7 +98,7 @@ namespace Components
|
||||
|
||||
Elevators::Elevators()
|
||||
{
|
||||
Scheduler::Once([]
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
static const char* values[] =
|
||||
{
|
||||
@ -108,16 +108,15 @@ namespace Components
|
||||
nullptr
|
||||
};
|
||||
|
||||
Elevators::BG_Elevators = Game::Dvar_RegisterEnum("bg_elevators", values,
|
||||
Elevators::ENABLED, Game::DVAR_CODINFO, "Elevators glitch settings");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
BG_Elevators = Game::Dvar_RegisterEnum("bg_elevators", values, ENABLED, Game::DVAR_CODINFO, "Elevators glitch settings");
|
||||
});
|
||||
|
||||
Utils::Hook(0x57369E, Elevators::PM_CorrectAllSolidStub, HOOK_CALL).install()->quick(); // PM_GroundTrace
|
||||
Utils::Hook(0x57369E, PM_CorrectAllSolidStub, HOOK_CALL).install()->quick(); // PM_GroundTrace
|
||||
|
||||
// Place hooks in PM_CheckDuck. If the elevators dvar is set to easy the
|
||||
// flags for duck/prone will always be removed from the player state
|
||||
Utils::Hook(0x570EC5, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x570E0B, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x570D70, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x570EC5, PM_Trace_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x570E0B, PM_Trace_Hk, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x570D70, PM_Trace_Hk, HOOK_CALL).install()->quick();
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,23 @@
|
||||
namespace Components
|
||||
{
|
||||
Utils::Signal<Events::ClientCallback> Events::ClientDisconnectSignal;
|
||||
Utils::Signal<Events::ClientConnectCallback> Events::ClientConnectSignal;
|
||||
Utils::Signal<Events::Callback> Events::SteamDisconnectSignal;
|
||||
Utils::Signal<Events::Callback> Events::ShutdownSystemSignal;
|
||||
Utils::Signal<Events::Callback> Events::ClientInitSignal;
|
||||
Utils::Signal<Events::Callback> Events::ServerInitSignal;
|
||||
Utils::Signal<Events::Callback> Events::DvarInitSignal;
|
||||
|
||||
void Events::OnClientDisconnect(const Utils::Slot<ClientCallback>& callback)
|
||||
{
|
||||
ClientDisconnectSignal.connect(callback);
|
||||
}
|
||||
|
||||
void Events::OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback)
|
||||
{
|
||||
ClientConnectSignal.connect(callback);
|
||||
}
|
||||
|
||||
void Events::OnSteamDisconnect(const Utils::Slot<Callback>& callback)
|
||||
{
|
||||
SteamDisconnectSignal.connect(callback);
|
||||
@ -33,6 +40,11 @@ namespace Components
|
||||
ServerInitSignal.connect(callback);
|
||||
}
|
||||
|
||||
void Events::OnDvarInit(const Utils::Slot<Callback>& callback)
|
||||
{
|
||||
DvarInitSignal.connect(callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Should be called when a client drops from the server
|
||||
* but not "between levels" (Quake-III-Arena)
|
||||
@ -44,6 +56,13 @@ namespace Components
|
||||
Utils::Hook::Call<void(int)>(0x4AA430)(clientNum); // ClientDisconnect
|
||||
}
|
||||
|
||||
void Events::SV_UserinfoChanged_Hk(Game::client_t* cl)
|
||||
{
|
||||
ClientConnectSignal(cl);
|
||||
|
||||
Utils::Hook::Call<void(Game::client_t*)>(0x401950)(cl); // SV_UserinfoChanged
|
||||
}
|
||||
|
||||
void Events::SteamDisconnect_Hk()
|
||||
{
|
||||
SteamDisconnectSignal();
|
||||
@ -74,10 +93,20 @@ namespace Components
|
||||
Utils::Hook::Call<void()>(0x474320)(); // SV_InitGameMode
|
||||
}
|
||||
|
||||
void Events::Com_InitDvars_Hk()
|
||||
{
|
||||
DvarInitSignal();
|
||||
DvarInitSignal.clear();
|
||||
|
||||
Utils::Hook::Call<void()>(0x60AD10)(); // Com_InitDvars
|
||||
}
|
||||
|
||||
Events::Events()
|
||||
{
|
||||
Utils::Hook(0x625235, ClientDisconnect_Hk, HOOK_CALL).install()->quick(); // SV_FreeClient
|
||||
|
||||
Utils::Hook(0x4612BD, SV_UserinfoChanged_Hk, HOOK_CALL).install()->quick(); // SV_DirectConnect
|
||||
|
||||
Utils::Hook(0x403582, SteamDisconnect_Hk, HOOK_CALL).install()->quick(); // CL_Disconnect
|
||||
|
||||
Utils::Hook(0x47548B, Scr_ShutdownSystem_Hk, HOOK_CALL).install()->quick(); // G_LoadGame
|
||||
@ -85,6 +114,8 @@ namespace Components
|
||||
|
||||
Utils::Hook(0x60BE5B, CL_InitOnceForAllClients_HK, HOOK_CALL).install()->quick(); // Com_Init_Try_Block_Function
|
||||
|
||||
Utils::Hook(0x60BB3A, Com_InitDvars_Hk, HOOK_CALL).install()->quick(); // Com_Init_Try_Block_Function
|
||||
|
||||
Utils::Hook(0x4D3665, SV_Init_Hk, HOOK_CALL).install()->quick(); // SV_Init
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace Components
|
||||
{
|
||||
public:
|
||||
typedef void(ClientCallback)(int clientNum);
|
||||
typedef void(ClientConnectCallback)(Game::client_t* cl);
|
||||
typedef void(Callback)();
|
||||
|
||||
Events();
|
||||
@ -13,6 +14,9 @@ namespace Components
|
||||
// Server side
|
||||
static void OnClientDisconnect(const Utils::Slot<ClientCallback>& callback);
|
||||
|
||||
// Server side
|
||||
static void OnClientConnect(const Utils::Slot<ClientConnectCallback>& callback);
|
||||
|
||||
// Client side
|
||||
static void OnSteamDisconnect(const Utils::Slot<Callback>& callback);
|
||||
|
||||
@ -23,17 +27,24 @@ namespace Components
|
||||
// Client & Server (triggered once)
|
||||
static void OnSVInit(const Utils::Slot<Callback>& callback);
|
||||
|
||||
// Client & Server (triggered once)
|
||||
static void OnDvarInit(const Utils::Slot<Callback>& callback);
|
||||
|
||||
private:
|
||||
static Utils::Signal<ClientCallback> ClientDisconnectSignal;
|
||||
static Utils::Signal<ClientConnectCallback> ClientConnectSignal;
|
||||
static Utils::Signal<Callback> SteamDisconnectSignal;
|
||||
static Utils::Signal<Callback> ShutdownSystemSignal;
|
||||
static Utils::Signal<Callback> ClientInitSignal;
|
||||
static Utils::Signal<Callback> ServerInitSignal;
|
||||
static Utils::Signal<Callback> DvarInitSignal;
|
||||
|
||||
static void ClientDisconnect_Hk(int clientNum);
|
||||
static void SV_UserinfoChanged_Hk(Game::client_t* cl);
|
||||
static void SteamDisconnect_Hk();
|
||||
static void Scr_ShutdownSystem_Hk(unsigned char sys);
|
||||
static void CL_InitOnceForAllClients_HK();
|
||||
static void SV_Init_Hk();
|
||||
static void Com_InitDvars_Hk();
|
||||
};
|
||||
}
|
||||
|
@ -134,7 +134,9 @@ namespace Components
|
||||
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(MiniDumpType), &ex, nullptr, nullptr))
|
||||
{
|
||||
MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR);
|
||||
#ifdef _DEBUG
|
||||
OutputDebugStringA("Failed to create new minidump!");
|
||||
#endif
|
||||
Utils::OutputDebugLastError();
|
||||
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "FastFiles.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
FastFiles::Key FastFiles::CurrentKey;
|
||||
symmetric_CTR FastFiles::CurrentCTR;
|
||||
std::vector<std::string> FastFiles::ZonePaths;
|
||||
|
||||
Dvar::Var FastFiles::g_loadingInitialZones;
|
||||
|
||||
bool FastFiles::IsIW4xZone = false;
|
||||
bool FastFiles::StreamRead = false;
|
||||
|
||||
@ -129,8 +135,10 @@ namespace Components
|
||||
|
||||
|
||||
// This has to be called only once, when the game starts
|
||||
void FastFiles::LoadInitialZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync)
|
||||
void FastFiles::LoadInitialZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync)
|
||||
{
|
||||
g_loadingInitialZones.set(true);
|
||||
|
||||
std::vector<Game::XZoneInfo> data;
|
||||
Utils::Merge(&data, zoneInfo, zoneCount);
|
||||
|
||||
@ -148,7 +156,7 @@ namespace Components
|
||||
}
|
||||
|
||||
// This has to be called every time the cgame is reinitialized
|
||||
void FastFiles::LoadDLCUIZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync)
|
||||
void FastFiles::LoadDLCUIZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync)
|
||||
{
|
||||
std::vector<Game::XZoneInfo> data;
|
||||
Utils::Merge(&data, zoneInfo, zoneCount);
|
||||
@ -173,7 +181,7 @@ namespace Components
|
||||
return FastFiles::LoadLocalizeZones(data.data(), data.size(), sync);
|
||||
}
|
||||
|
||||
void FastFiles::LoadGfxZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync)
|
||||
void FastFiles::LoadGfxZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync)
|
||||
{
|
||||
std::vector<Game::XZoneInfo> data;
|
||||
Utils::Merge(&data, zoneInfo, zoneCount);
|
||||
@ -187,7 +195,7 @@ namespace Components
|
||||
}
|
||||
|
||||
// This has to be called every time fastfiles are loaded :D
|
||||
void FastFiles::LoadLocalizeZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync)
|
||||
void FastFiles::LoadLocalizeZones(Game::XZoneInfo* zoneInfo, unsigned int zoneCount, int sync)
|
||||
{
|
||||
std::vector<Game::XZoneInfo> data;
|
||||
Utils::Merge(&data, zoneInfo, zoneCount);
|
||||
@ -209,6 +217,11 @@ namespace Components
|
||||
data.push_back(info);
|
||||
|
||||
Game::DB_LoadXAssets(data.data(), data.size(), sync);
|
||||
|
||||
Scheduler::OnGameInitialized([]
|
||||
{
|
||||
g_loadingInitialZones.set(false);
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
}
|
||||
|
||||
// Name is a bit weird, due to FasFileS and ExistS :P
|
||||
@ -232,14 +245,14 @@ namespace Components
|
||||
|
||||
const char* FastFiles::GetZoneLocation(const char* file)
|
||||
{
|
||||
const char* dir = Dvar::Var("fs_basepath").get<const char*>();
|
||||
const auto* dir = (*Game::fs_basepath)->current.string;
|
||||
|
||||
std::vector<std::string> paths;
|
||||
auto modDir = Dvar::Var("fs_game").get<std::string>();
|
||||
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
|
||||
|
||||
if ((file == "mod"s || file == "mod.ff"s) && !modDir.empty())
|
||||
if ((file == "mod"s || file == "mod.ff"s) && !fsGame.empty())
|
||||
{
|
||||
paths.push_back(std::format("{}\\", modDir));
|
||||
paths.push_back(std::format("{}\\", fsGame));
|
||||
}
|
||||
|
||||
if (Utils::String::StartsWith(file, "mp_"))
|
||||
@ -390,7 +403,7 @@ namespace Components
|
||||
return Utils::Hook::Call<int(unsigned char*, int, unsigned char*)>(0x5BA240)(buffer, length, ivValue);
|
||||
}
|
||||
|
||||
int FastFiles::InflateInitDecrypt(z_streamp strm, const char *version, int stream_size)
|
||||
static int InflateInitDecrypt(z_streamp strm, const char* version, int stream_size)
|
||||
{
|
||||
if (Zones::Version() >= 319)
|
||||
{
|
||||
@ -398,7 +411,6 @@ namespace Components
|
||||
}
|
||||
|
||||
return Utils::Hook::Call<int(z_streamp, const char*, int)>(0x4D8090)(strm, version, stream_size);
|
||||
//return inflateInit_(strm, version, stream_size);
|
||||
}
|
||||
|
||||
void FastFiles::AuthLoadInflateDecryptBaseFunc(unsigned char* buffer)
|
||||
@ -433,7 +445,7 @@ namespace Components
|
||||
return std::min(partialProgress + (currentProgress * singleProgress), 1.0f);
|
||||
}
|
||||
|
||||
void FastFiles::LoadZonesStub(Game::XZoneInfo *zoneInfo, unsigned int zoneCount)
|
||||
void FastFiles::LoadZonesStub(Game::XZoneInfo* zoneInfo, unsigned int zoneCount)
|
||||
{
|
||||
FastFiles::CurrentZone = 0;
|
||||
FastFiles::MaxZones = zoneCount;
|
||||
@ -497,6 +509,7 @@ namespace Components
|
||||
FastFiles::FastFiles()
|
||||
{
|
||||
Dvar::Register<bool>("ui_zoneDebug", false, Game::DVAR_ARCHIVE, "Display current loaded zone.");
|
||||
g_loadingInitialZones = Dvar::Register<bool>("g_loadingInitialZones", true, Game::DVAR_NONE, "Is loading initial zones");
|
||||
|
||||
// Fix XSurface assets
|
||||
Utils::Hook(0x0048E8A5, FastFiles::Load_XSurfaceArray, HOOK_CALL).install()->quick();
|
||||
@ -547,7 +560,7 @@ namespace Components
|
||||
Utils::Hook(0x4D02F0, FastFiles::AuthLoadInitCrypto, HOOK_CALL).install()->quick();
|
||||
|
||||
// Initial stage decryption
|
||||
Utils::Hook(0x4D0306, FastFiles::InflateInitDecrypt, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D0306, InflateInitDecrypt, HOOK_CALL).install()->quick();
|
||||
|
||||
// Hash bit decryption
|
||||
Utils::Hook(0x5B9958, FastFiles::AuthLoadInflateCompare, HOOK_CALL).install()->quick();
|
||||
@ -577,7 +590,7 @@ namespace Components
|
||||
if (FastFiles::Current().empty() || !Dvar::Var("ui_zoneDebug").get<bool>()) return;
|
||||
|
||||
auto* const font = Game::R_RegisterFont("fonts/consoleFont", 0);
|
||||
float color[4] = {1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f)};
|
||||
float color[4] = { 1.0f, 1.0f, 1.0f, (Game::CL_IsCgameInitialized() ? 0.3f : 1.0f) };
|
||||
|
||||
auto FFTotalSize = *reinterpret_cast<std::uint32_t*>(0x10AA5D8);
|
||||
auto FFCurrentOffset = *reinterpret_cast<std::uint32_t*>(0x10AA608);
|
||||
@ -614,11 +627,11 @@ namespace Components
|
||||
while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms);
|
||||
});
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef _DEBUG
|
||||
// ZoneBuilder debugging
|
||||
Utils::IO::WriteFile("userraw/logs/iw4_reads.log", "", false);
|
||||
Utils::Hook(0x4A8FA0, FastFiles::LogStreamRead, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4BCB62, []()
|
||||
Utils::Hook(0x4BCB62, []
|
||||
{
|
||||
FastFiles::StreamRead = true;
|
||||
Utils::Hook::Call<void(bool)>(0x4B8DB0)(true); // currently set to Load_GfxWorld
|
||||
|
@ -18,6 +18,8 @@ namespace Components
|
||||
|
||||
static unsigned char ZoneKey[1191];
|
||||
|
||||
static symmetric_CTR CurrentCTR;
|
||||
|
||||
private:
|
||||
union Key
|
||||
{
|
||||
@ -38,8 +40,9 @@ namespace Components
|
||||
|
||||
static char LastByteRead;
|
||||
|
||||
static Dvar::Var g_loadingInitialZones;
|
||||
|
||||
static Key CurrentKey;
|
||||
static symmetric_CTR CurrentCTR;
|
||||
static std::vector<std::string> ZonePaths;
|
||||
static const char* GetZoneLocation(const char* file);
|
||||
static void LoadInitialZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync);
|
||||
@ -55,7 +58,6 @@ namespace Components
|
||||
static int AuthLoadInflateCompare(unsigned char* buffer, int length, unsigned char* ivValue);
|
||||
static void AuthLoadInflateDecryptBase();
|
||||
static void AuthLoadInflateDecryptBaseFunc(unsigned char* buffer);
|
||||
static int InflateInitDecrypt(z_streamp strm, const char *version, int stream_size);
|
||||
|
||||
static void LoadZonesStub(Game::XZoneInfo *zoneInfo, unsigned int zoneCount);
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace Components
|
||||
|
||||
void FileSystem::File::read(Game::FsThread thread)
|
||||
{
|
||||
std::lock_guard _(FileSystem::FSMutex);
|
||||
std::lock_guard _(FSMutex);
|
||||
|
||||
assert(!filePath.empty());
|
||||
|
||||
@ -38,14 +38,14 @@ namespace Components
|
||||
{
|
||||
this->buffer.clear();
|
||||
|
||||
Game::RawFile* rawfile = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data()).rawfile;
|
||||
auto* rawfile = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data()).rawfile;
|
||||
if (!rawfile || Game::DB_IsXAssetDefault(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data())) return;
|
||||
|
||||
this->buffer.resize(Game::DB_GetRawFileLen(rawfile));
|
||||
Game::DB_GetRawBuffer(rawfile, this->buffer.data(), static_cast<int>(this->buffer.size()));
|
||||
}
|
||||
|
||||
FileSystem::FileReader::FileReader(const std::string& file) : handle(0), name(file)
|
||||
FileSystem::FileReader::FileReader(std::string file) : handle(0), name(std::move(file))
|
||||
{
|
||||
this->size = Game::FS_FOpenFileReadCurrentThread(this->name.data(), &this->handle);
|
||||
}
|
||||
@ -58,25 +58,25 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
bool FileSystem::FileReader::exists()
|
||||
bool FileSystem::FileReader::exists() const noexcept
|
||||
{
|
||||
return (this->size >= 0 && this->handle);
|
||||
}
|
||||
|
||||
std::string FileSystem::FileReader::getName()
|
||||
std::string FileSystem::FileReader::getName() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
int FileSystem::FileReader::getSize()
|
||||
int FileSystem::FileReader::getSize() const noexcept
|
||||
{
|
||||
return this->size;
|
||||
}
|
||||
|
||||
std::string FileSystem::FileReader::getBuffer()
|
||||
std::string FileSystem::FileReader::getBuffer() const
|
||||
{
|
||||
Utils::Memory::Allocator allocator;
|
||||
if (!this->exists()) return std::string();
|
||||
if (!this->exists()) return {};
|
||||
|
||||
const auto position = Game::FS_FTell(this->handle);
|
||||
this->seek(0, Game::FS_SEEK_SET);
|
||||
@ -93,9 +93,9 @@ namespace Components
|
||||
return {buffer, static_cast<std::size_t>(this->size)};
|
||||
}
|
||||
|
||||
bool FileSystem::FileReader::read(void* buffer, size_t _size)
|
||||
bool FileSystem::FileReader::read(void* buffer, std::size_t _size) const noexcept
|
||||
{
|
||||
if (!this->exists() || static_cast<size_t>(this->size) < _size || Game::FS_Read(buffer, _size, this->handle) != static_cast<int>(_size))
|
||||
if (!this->exists() || static_cast<std::size_t>(this->size) < _size || Game::FS_Read(buffer, static_cast<int>(_size), this->handle) != static_cast<int>(_size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -103,7 +103,7 @@ namespace Components
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileSystem::FileReader::seek(int offset, int origin)
|
||||
void FileSystem::FileReader::seek(int offset, int origin) const
|
||||
{
|
||||
if (this->exists())
|
||||
{
|
||||
@ -111,11 +111,11 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystem::FileWriter::write(const std::string& data)
|
||||
void FileSystem::FileWriter::write(const std::string& data) const
|
||||
{
|
||||
if (this->handle)
|
||||
{
|
||||
Game::FS_Write(data.data(), data.size(), this->handle);
|
||||
Game::FS_Write(data.data(), static_cast<int>(data.size()), this->handle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,27 +204,26 @@ namespace Components
|
||||
|
||||
bool FileSystem::_DeleteFile(const std::string& folder, const std::string& file)
|
||||
{
|
||||
char path[MAX_PATH] = {0};
|
||||
Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").get<const char*>(), reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&path));
|
||||
char path[MAX_PATH]{};
|
||||
Game::FS_BuildPathToFile((*Game::fs_basepath)->current.string, reinterpret_cast<char*>(0x63D0BB8), Utils::String::VA("%s/%s", folder.data(), file.data()), reinterpret_cast<char**>(&path));
|
||||
return Game::FS_Remove(path);
|
||||
}
|
||||
|
||||
int FileSystem::ReadFile(const char* path, char** buffer)
|
||||
{
|
||||
if (!buffer) return -1;
|
||||
else *buffer = nullptr;
|
||||
if (!path) return -1;
|
||||
|
||||
std::lock_guard _(FileSystem::Mutex);
|
||||
FileSystem::FileReader reader(path);
|
||||
std::lock_guard _(Mutex);
|
||||
FileReader reader(path);
|
||||
|
||||
int size = reader.getSize();
|
||||
if (reader.exists() && size >= 0)
|
||||
{
|
||||
*buffer = FileSystem::AllocateFile(size + 1);
|
||||
*buffer = AllocateFile(size + 1);
|
||||
if (reader.read(*buffer, size)) return size;
|
||||
|
||||
FileSystem::FreeFile(*buffer);
|
||||
FreeFile(*buffer);
|
||||
*buffer = nullptr;
|
||||
}
|
||||
|
||||
@ -233,19 +232,19 @@ namespace Components
|
||||
|
||||
char* FileSystem::AllocateFile(int size)
|
||||
{
|
||||
return FileSystem::MemAllocator.allocateArray<char>(size);
|
||||
return MemAllocator.allocateArray<char>(size);
|
||||
}
|
||||
|
||||
void FileSystem::FreeFile(void* buffer)
|
||||
{
|
||||
FileSystem::MemAllocator.free(buffer);
|
||||
MemAllocator.free(buffer);
|
||||
}
|
||||
|
||||
void FileSystem::RegisterFolder(const char* folder)
|
||||
{
|
||||
const auto fs_cdpath = Dvar::Var("fs_cdpath").get<std::string>();
|
||||
const auto fs_basepath = Dvar::Var("fs_basepath").get<std::string>();
|
||||
const auto fs_homepath = Dvar::Var("fs_homepath").get<std::string>();
|
||||
const std::string fs_cdpath = (*Game::fs_cdpath)->current.string;
|
||||
const std::string fs_basepath = (*Game::fs_basepath)->current.string;
|
||||
const std::string fs_homepath = (*Game::fs_homepath)->current.string;
|
||||
|
||||
if (!fs_cdpath.empty()) Game::FS_AddLocalizedGameDirectory(fs_cdpath.data(), folder);
|
||||
if (!fs_basepath.empty()) Game::FS_AddLocalizedGameDirectory(fs_basepath.data(), folder);
|
||||
@ -256,10 +255,10 @@ namespace Components
|
||||
{
|
||||
if (ZoneBuilder::IsEnabled())
|
||||
{
|
||||
FileSystem::RegisterFolder("zonedata");
|
||||
RegisterFolder("zonedata");
|
||||
}
|
||||
|
||||
FileSystem::RegisterFolder("userraw");
|
||||
RegisterFolder("userraw");
|
||||
}
|
||||
|
||||
__declspec(naked) void FileSystem::StartupStub()
|
||||
@ -286,34 +285,34 @@ namespace Components
|
||||
|
||||
void FileSystem::FsStartupSync(const char* a1)
|
||||
{
|
||||
std::lock_guard _(FileSystem::FSMutex);
|
||||
std::lock_guard _(FSMutex);
|
||||
return Utils::Hook::Call<void(const char*)>(0x4823A0)(a1); // FS_Startup
|
||||
}
|
||||
|
||||
void FileSystem::FsRestartSync(int a1, int a2)
|
||||
void FileSystem::FsRestartSync(int localClientNum, int checksumFeed)
|
||||
{
|
||||
std::lock_guard _(FileSystem::FSMutex);
|
||||
std::lock_guard _(FSMutex);
|
||||
Maps::GetUserMap()->freeIwd();
|
||||
Utils::Hook::Call<void(int, int)>(0x461A50)(a1, a2); // FS_Restart
|
||||
Utils::Hook::Call<void(int, int)>(0x461A50)(localClientNum, checksumFeed); // FS_Restart
|
||||
Maps::GetUserMap()->reloadIwd();
|
||||
}
|
||||
|
||||
void FileSystem::FsShutdownSync(int a1)
|
||||
void FileSystem::FsShutdownSync(int closemfp)
|
||||
{
|
||||
std::lock_guard _(FileSystem::FSMutex);
|
||||
std::lock_guard _(FSMutex);
|
||||
Maps::GetUserMap()->freeIwd();
|
||||
Utils::Hook::Call<void(int)>(0x4A46C0)(a1); // FS_Shutdown
|
||||
Utils::Hook::Call<void(int)>(0x4A46C0)(closemfp); // FS_Shutdown
|
||||
}
|
||||
|
||||
void FileSystem::DelayLoadImagesSync()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(FileSystem::FSMutex);
|
||||
std::lock_guard _(FSMutex);
|
||||
return Utils::Hook::Call<void()>(0x494060)(); // DB_LoadDelayedImages
|
||||
}
|
||||
|
||||
int FileSystem::LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image)
|
||||
int FileSystem::LoadTextureSync(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image)
|
||||
{
|
||||
std::lock_guard _(FileSystem::FSMutex);
|
||||
std::lock_guard _(FSMutex);
|
||||
return Game::Load_Texture(loadDef, image);
|
||||
}
|
||||
|
||||
@ -331,23 +330,20 @@ namespace Components
|
||||
|
||||
FileSystem::FileSystem()
|
||||
{
|
||||
FileSystem::MemAllocator.clear();
|
||||
|
||||
// Thread safe file system interaction
|
||||
Utils::Hook(0x4F4BFF, FileSystem::AllocateFile, HOOK_CALL).install()->quick();
|
||||
//Utils::Hook(Game::FS_ReadFile, FileSystem::ReadFile, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(Game::FS_FreeFile, FileSystem::FreeFile, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4F4BFF, AllocateFile, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(Game::FS_FreeFile, FreeFile, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Filesystem config checks
|
||||
Utils::Hook(0x6098FD, FileSystem::ExecIsFSStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x6098FD, ExecIsFSStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Don't strip the folders from the config name (otherwise our ExecIsFSStub fails)
|
||||
Utils::Hook::Nop(0x6098F2, 5);
|
||||
|
||||
// Register additional folders
|
||||
Utils::Hook(0x482647, FileSystem::StartupStub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x482647, StartupStub, HOOK_JUMP).install()->quick();
|
||||
|
||||
// exec whitelist removal (YAYFINITY WARD)
|
||||
// exec whitelist removal
|
||||
Utils::Hook::Nop(0x609685, 5);
|
||||
Utils::Hook::Nop(0x60968C, 2);
|
||||
|
||||
@ -355,30 +351,30 @@ namespace Components
|
||||
Utils::Hook::Nop(0x642A4B, 5);
|
||||
|
||||
// Ignore bad magic, when trying to free hunk when it's already cleared
|
||||
Utils::Hook::Set<WORD>(0x49AACE, 0xC35E);
|
||||
Utils::Hook::Set<std::uint16_t>(0x49AACE, 0xC35E);
|
||||
|
||||
// Synchronize filesystem starts
|
||||
Utils::Hook(0x4290C6, FileSystem::FsStartupSync, HOOK_CALL).install()->quick(); // FS_InitFilesystem
|
||||
Utils::Hook(0x461A88, FileSystem::FsStartupSync, HOOK_CALL).install()->quick(); // FS_Restart
|
||||
Utils::Hook(0x4290C6, FsStartupSync, HOOK_CALL).install()->quick(); // FS_InitFilesystem
|
||||
Utils::Hook(0x461A88, FsStartupSync, HOOK_CALL).install()->quick(); // FS_Restart
|
||||
|
||||
// Synchronize filesystem restarts
|
||||
Utils::Hook(0x4A745B, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // SV_SpawnServer
|
||||
Utils::Hook(0x4C8609, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart
|
||||
Utils::Hook(0x5AC68E, FileSystem::FsRestartSync, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
|
||||
Utils::Hook(0x4A745B, FsRestartSync, HOOK_CALL).install()->quick(); // SV_SpawnServer
|
||||
Utils::Hook(0x4C8609, FsRestartSync, HOOK_CALL).install()->quick(); // FS_ConditionalRestart
|
||||
Utils::Hook(0x5AC68E, 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
|
||||
Utils::Hook(0x461A55, FsShutdownSync, HOOK_CALL).install()->quick(); // FS_Restart
|
||||
Utils::Hook(0x4D40DB, FsShutdownSync, HOOK_CALL).install()->quick(); // Com_Quitf
|
||||
|
||||
// Synchronize db image loading
|
||||
Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D32BC, FileSystem::LoadTextureSync, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x415AB8, DelayLoadImagesSync, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D32BC, LoadTextureSync, HOOK_CALL).install()->quick();
|
||||
|
||||
// Handle IWD freeing
|
||||
Utils::Hook(0x642F60, FileSystem::IwdFreeStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x642F60, IwdFreeStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Set the working dir based on info from the Xlabs launcher
|
||||
Utils::Hook(0x4326E0, FileSystem::Sys_DefaultInstallPath_Hk, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x4326E0, Sys_DefaultInstallPath_Hk, HOOK_JUMP).install()->quick();
|
||||
}
|
||||
|
||||
FileSystem::~FileSystem()
|
||||
|
@ -10,9 +10,9 @@ namespace Components
|
||||
public:
|
||||
virtual ~AbstractFile() = default;
|
||||
|
||||
virtual bool exists() = 0;
|
||||
virtual std::string getName() = 0;
|
||||
virtual std::string& getBuffer() = 0;
|
||||
[[nodiscard]] virtual bool exists() const noexcept = 0;
|
||||
[[nodiscard]] virtual std::string getName() const = 0;
|
||||
[[nodiscard]] virtual std::string& getBuffer() = 0;
|
||||
|
||||
virtual explicit operator bool()
|
||||
{
|
||||
@ -27,9 +27,9 @@ namespace Components
|
||||
File(std::string file) : filePath{std::move(file)} { this->read(); }
|
||||
File(std::string file, Game::FsThread thread) : filePath{std::move(file)} { this->read(thread); }
|
||||
|
||||
bool exists() override { return !this->buffer.empty(); }
|
||||
std::string getName() override { return this->filePath; }
|
||||
std::string& getBuffer() override { return this->buffer; }
|
||||
[[nodiscard]] bool exists() const noexcept override { return !this->buffer.empty(); }
|
||||
[[nodiscard]] std::string getName() const override { return this->filePath; }
|
||||
[[nodiscard]] std::string& getBuffer() override { return this->buffer; }
|
||||
|
||||
private:
|
||||
std::string filePath;
|
||||
@ -44,9 +44,9 @@ namespace Components
|
||||
RawFile() = default;
|
||||
RawFile(std::string file) : filePath(std::move(file)) { this->read(); }
|
||||
|
||||
bool exists() override { return !this->buffer.empty(); }
|
||||
std::string getName() override { return this->filePath; }
|
||||
std::string& getBuffer() override { return this->buffer; }
|
||||
[[nodiscard]] bool exists() const noexcept override { return !this->buffer.empty(); }
|
||||
[[nodiscard]] std::string getName() const override { return this->filePath; }
|
||||
[[nodiscard]] std::string& getBuffer() override { return this->buffer; }
|
||||
|
||||
private:
|
||||
std::string filePath;
|
||||
@ -58,16 +58,16 @@ namespace Components
|
||||
class FileReader
|
||||
{
|
||||
public:
|
||||
FileReader() : handle(0), size(-1), name() {}
|
||||
FileReader(const std::string& file);
|
||||
FileReader() : handle(0), size(-1) {}
|
||||
FileReader(std::string file);
|
||||
~FileReader();
|
||||
|
||||
bool exists();
|
||||
std::string getName();
|
||||
std::string getBuffer();
|
||||
int getSize();
|
||||
bool read(void* buffer, size_t size);
|
||||
void seek(int offset, int origin);
|
||||
[[nodiscard]] bool exists() const noexcept;
|
||||
[[nodiscard]] std::string getName() const;
|
||||
[[nodiscard]] std::string getBuffer() const;
|
||||
[[nodiscard]] int getSize() const noexcept;
|
||||
bool read(void* buffer, std::size_t size) const noexcept;
|
||||
void seek(int offset, int origin) const;
|
||||
|
||||
private:
|
||||
int handle;
|
||||
@ -81,7 +81,7 @@ namespace Components
|
||||
FileWriter(std::string file, bool append = false) : handle(0), filePath(std::move(file)) { this->open(append); }
|
||||
~FileWriter() { this->close(); }
|
||||
|
||||
void write(const std::string& data);
|
||||
void write(const std::string& data) const;
|
||||
|
||||
private:
|
||||
int handle;
|
||||
@ -115,10 +115,10 @@ namespace Components
|
||||
static int ExecIsFSStub(const char* execFilename);
|
||||
|
||||
static void FsStartupSync(const char* a1);
|
||||
static void FsRestartSync(int a1, int a2);
|
||||
static void FsShutdownSync(int a1);
|
||||
static void FsRestartSync(int localClientNum, int checksumFeed);
|
||||
static void FsShutdownSync(int closemfp);
|
||||
static void DelayLoadImagesSync();
|
||||
static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image);
|
||||
static int LoadTextureSync(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image);
|
||||
|
||||
static void IwdFreeStub(Game::iwd_t* iwd);
|
||||
|
||||
|
@ -5,8 +5,6 @@ namespace Components
|
||||
class Flags : public Component
|
||||
{
|
||||
public:
|
||||
Flags() = default;
|
||||
|
||||
static bool HasFlag(const std::string& flag);
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,11 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4100)
|
||||
#include <proto/friends.pb.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include "Party.hpp"
|
||||
#include "UIFeeder.hpp"
|
||||
|
||||
namespace Components
|
||||
@ -556,8 +563,7 @@ namespace Components
|
||||
{
|
||||
Friends::LoggedOn = false;
|
||||
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
return;
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
||||
|
||||
Friends::UIStreamFriendly = Dvar::Register<bool>("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI");
|
||||
Friends::CLAnonymous = Dvar::Register<bool>("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam");
|
||||
|
@ -3,19 +3,25 @@
|
||||
#include "Int64.hpp"
|
||||
#include "IO.hpp"
|
||||
#include "Script.hpp"
|
||||
#include "ScriptError.hpp"
|
||||
#include "ScriptExtension.hpp"
|
||||
#include "ScriptPatches.hpp"
|
||||
#include "ScriptStorage.hpp"
|
||||
#include "String.hpp"
|
||||
#include "UserInfo.hpp"
|
||||
|
||||
namespace Components
|
||||
namespace Components::GSC
|
||||
{
|
||||
GSC::GSC()
|
||||
{
|
||||
Loader::Register(new Int64());
|
||||
Loader::Register(new IO());
|
||||
Loader::Register(new Script());
|
||||
Loader::Register(new ScriptError());
|
||||
Loader::Register(new ScriptExtension());
|
||||
Loader::Register(new ScriptPatches());
|
||||
Loader::Register(new ScriptStorage());
|
||||
Loader::Register(new String());
|
||||
Loader::Register(new UserInfo());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Components
|
||||
namespace Components::GSC
|
||||
{
|
||||
class GSC : public Component
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "IO.hpp"
|
||||
#include "Script.hpp"
|
||||
|
||||
namespace Components
|
||||
namespace Components::GSC
|
||||
{
|
||||
const char* IO::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" };
|
||||
|
||||
@ -14,15 +14,15 @@ namespace Components
|
||||
auto* text = Game::Scr_GetString(1);
|
||||
auto* mode = Game::Scr_GetString(2);
|
||||
|
||||
if (path == nullptr)
|
||||
if (!path)
|
||||
{
|
||||
Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n");
|
||||
Game::Scr_ParamError(0, "FileWrite: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (text == nullptr || mode == nullptr)
|
||||
if (!text || !mode)
|
||||
{
|
||||
Game::Scr_Error("^1FileWrite: Illegal parameters!\n");
|
||||
Game::Scr_Error("FileWrite: Illegal parameters!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,9 +57,9 @@ namespace Components
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
|
||||
if (path == nullptr)
|
||||
if (!path)
|
||||
{
|
||||
Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n");
|
||||
Game::Scr_ParamError(0, "FileRead: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -74,22 +74,25 @@ namespace Components
|
||||
|
||||
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
|
||||
|
||||
if (!FileSystem::FileReader(scriptData).exists())
|
||||
const auto file = FileSystem::FileReader(scriptData);
|
||||
if (!file.exists())
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: file '{}' not found!\n", scriptData);
|
||||
return;
|
||||
}
|
||||
|
||||
Game::Scr_AddString(FileSystem::FileReader(scriptData).getBuffer().data());
|
||||
auto buffer = file.getBuffer();
|
||||
buffer = buffer.substr(0, 1024 - 1); // 1024 is the max string size for the SL system
|
||||
Game::Scr_AddString(buffer.data());
|
||||
});
|
||||
|
||||
Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>)
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
|
||||
if (path == nullptr)
|
||||
if (!path)
|
||||
{
|
||||
Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n");
|
||||
Game::Scr_ParamError(0, "FileExists: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -103,7 +106,6 @@ namespace Components
|
||||
}
|
||||
|
||||
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
|
||||
|
||||
Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists());
|
||||
});
|
||||
|
||||
@ -111,9 +113,9 @@ namespace Components
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
|
||||
if (path == nullptr)
|
||||
if (!path)
|
||||
{
|
||||
Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n");
|
||||
Game::Scr_ParamError(0, "FileRemove: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -121,7 +123,7 @@ namespace Components
|
||||
{
|
||||
if (std::strstr(path, QueryStrings[i]) != nullptr)
|
||||
{
|
||||
Logger::Print("^1FileRemove: directory traversal is not allowed!\n");
|
||||
Logger::Print("FileRemove: directory traversal is not allowed!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user