commit
26069a993d
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
lfs: false
|
lfs: false
|
||||||
|
|
||||||
- name: Add msbuild to PATH
|
- name: Add msbuild to PATH
|
||||||
uses: microsoft/setup-msbuild@v1.1
|
uses: microsoft/setup-msbuild@v1.1.3
|
||||||
|
|
||||||
- name: Generate project files
|
- name: Generate project files
|
||||||
run: tools/premake5 vs2022
|
run: tools/premake5 vs2022
|
||||||
@ -48,20 +48,13 @@ jobs:
|
|||||||
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=Win32 build/iw4x.sln
|
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=Win32 build/iw4x.sln
|
||||||
|
|
||||||
- name: Upload ${{matrix.configuration}} binaries
|
- name: Upload ${{matrix.configuration}} binaries
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3.1.0
|
||||||
with:
|
with:
|
||||||
name: ${{matrix.configuration}} binaries
|
name: ${{matrix.configuration}} binaries
|
||||||
path: |
|
path: |
|
||||||
build/bin/Win32/${{matrix.configuration}}/iw4x.dll
|
build/bin/Win32/${{matrix.configuration}}/iw4x.dll
|
||||||
build/bin/Win32/${{matrix.configuration}}/iw4x.pdb
|
build/bin/Win32/${{matrix.configuration}}/iw4x.pdb
|
||||||
|
|
||||||
# - name: Upload ${{matrix.configuration}} data artifacts
|
|
||||||
# uses: actions/upload-artifact@v2
|
|
||||||
# with:
|
|
||||||
# name: ${{matrix.configuration}} data artifacts
|
|
||||||
# path: |
|
|
||||||
# data/*
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy artifacts
|
name: Deploy artifacts
|
||||||
needs: build
|
needs: build
|
||||||
@ -77,16 +70,10 @@ jobs:
|
|||||||
run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV
|
run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Download Release binaries
|
- name: Download Release binaries
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: Release binaries
|
name: Release binaries
|
||||||
|
|
||||||
# - name: Download Release data artifacts
|
|
||||||
# uses: actions/download-artifact@v2
|
|
||||||
# with:
|
|
||||||
# name: Release data artifacts
|
|
||||||
# path: data
|
|
||||||
|
|
||||||
# Set up committer info and GPG key
|
# Set up committer info and GPG key
|
||||||
- name: Install SSH key
|
- name: Install SSH key
|
||||||
uses: shimataro/ssh-key-action@v2
|
uses: shimataro/ssh-key-action@v2
|
||||||
|
2
.github/workflows/draft-new-release.yml
vendored
2
.github/workflows/draft-new-release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
name: "Draft a new release"
|
name: "Draft a new release"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Normalize version
|
- name: Normalize version
|
||||||
id: normalize_version
|
id: normalize_version
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out files
|
- name: Check out files
|
||||||
if: github.event.pull_request.merged
|
if: github.event.pull_request.merged
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
lfs: false
|
lfs: false
|
||||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -17,7 +17,7 @@
|
|||||||
[submodule "deps/mongoose"]
|
[submodule "deps/mongoose"]
|
||||||
path = deps/mongoose
|
path = deps/mongoose
|
||||||
url = https://github.com/cesanta/mongoose.git
|
url = https://github.com/cesanta/mongoose.git
|
||||||
branch = dev
|
branch = master
|
||||||
[submodule "deps/protobuf"]
|
[submodule "deps/protobuf"]
|
||||||
path = deps/protobuf
|
path = deps/protobuf
|
||||||
url = https://github.com/google/protobuf.git
|
url = https://github.com/google/protobuf.git
|
||||||
|
34
CHANGELOG.md
34
CHANGELOG.md
@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [0.7.6] - 2022-11-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `GetStat` GSC method (#485)
|
||||||
|
- Add `SetStat` GSC method (#485)
|
||||||
|
- Add `statGet` command (#485)
|
||||||
|
- Add `FileRemove` GSC function (#509)
|
||||||
|
- Add `cg_drawDisconnect` Dvar (#551)
|
||||||
|
- Add loading info strings from raw file (#557)
|
||||||
|
- Add localized file string parsing to Zone Builder (.str) (#569)
|
||||||
|
- Add single localized file string parsing to Zone Builder (#569)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Remove non-existent menus from code (#483)
|
||||||
|
- Change font and colour of the external console (#477)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix crash for debug build (#476)
|
||||||
|
- Fix auth bug with the connect protocol `iw4x://` (#478)
|
||||||
|
- Fix party server not receiving packets (#500)
|
||||||
|
- Fix crash caused by server thread issues (#561)
|
||||||
|
- Fix hours spent playing IW4x not counting towards the hours spent playing MW2 on Steam (#573)
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
|
||||||
|
- HTTPS is not supported for fast downloads at the moment.
|
||||||
|
- Sound issue fix is experimental as the bug is not fully understood.
|
||||||
|
- `reloadmenus` command does not free resources used by custom menus.
|
||||||
|
|
||||||
## [0.7.5] - 2022-09-03
|
## [0.7.5] - 2022-09-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -22,7 +54,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
|
|||||||
|
|
||||||
- Steam status is no longer set to busy (#417)
|
- Steam status is no longer set to busy (#417)
|
||||||
- `HttpGet`& `HttpCancel` are disabled for security reasons (#449)
|
- `HttpGet`& `HttpCancel` are disabled for security reasons (#449)
|
||||||
- 'g_allowVote' is a replicated Dvar (#457)
|
- `g_allowVote` is a replicated Dvar (#457)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
| `-bigminidumps` | Include all code sections from loaded modules in the dump. |
|
| `-bigminidumps` | Include all code sections from loaded modules in the dump. |
|
||||||
| `-reallybigminidumps` | Include data sections from all loaded modules in the dump. |
|
| `-reallybigminidumps` | Include data sections from all loaded modules in the dump. |
|
||||||
| `-dump` | Write info of loaded assets to the raw folder as they are being loaded. |
|
| `-dump` | Write info of loaded assets to the raw folder as they are being loaded. |
|
||||||
| `-monitor` | This flag is for internal use and it is used to indicate if an external console is present. |
|
|
||||||
| `-nointro` | Skip game's cinematic intro. |
|
| `-nointro` | Skip game's cinematic intro. |
|
||||||
| `-version` | Print IW4x build info on startup. |
|
| `-version` | Print IW4x build info on startup. |
|
||||||
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
|
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
|
||||||
|
2
deps/GSL
vendored
2
deps/GSL
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 10df83d292bf5bbdc487e57dc8c2dc8c7a01f4d1
|
Subproject commit 517ed29228d18cf2c5004d10826090108e06f049
|
94
deps/extra/font/Terminus_4.49.1.License.txt
vendored
Normal file
94
deps/extra/font/Terminus_4.49.1.License.txt
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
Copyright (C) 2020 Dimitar Toshkov Zhekov,
|
||||||
|
with Reserved Font Name "Terminus Font".
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
38607
deps/extra/font/Terminus_4.49.1.ttf.hpp
vendored
Normal file
38607
deps/extra/font/Terminus_4.49.1.ttf.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
|||||||
Subproject commit ddfe2e8aa7c4239463a8a1d26724aef123333549
|
Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4b47368501321c795d5b54d87a5bab35a21a7940
|
Subproject commit 03de03dee753442d4b23166982514639c4ccbc39
|
2
deps/mongoose
vendored
2
deps/mongoose
vendored
@ -1 +1 @@
|
|||||||
Subproject commit cb602f178ccea7f0c790cf5510f7a29c017db954
|
Subproject commit db81c30d24df98031e41e33423a5eef0e89ba8c6
|
2
deps/nlohmannjson
vendored
2
deps/nlohmannjson
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 307c053b9b250abc6e6d478a4336fd98592ae173
|
Subproject commit a3e6e26dc83a726b292f5be0492fcc408663ce55
|
2
deps/pdcurses
vendored
2
deps/pdcurses
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 2fa0f10dd844da47ee83c05a40a1ec541ceb95e1
|
Subproject commit f2d31a2633eb042f7bf1f79cba81522915a04579
|
27
deps/premake/fonts.lua
vendored
Normal file
27
deps/premake/fonts.lua
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
fonts = {}
|
||||||
|
|
||||||
|
function fonts.import()
|
||||||
|
fonts.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function fonts.includes()
|
||||||
|
includedirs {
|
||||||
|
path.join(dependencies.basePath, "extra/font"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function fonts.project()
|
||||||
|
project "fonts"
|
||||||
|
language "C"
|
||||||
|
|
||||||
|
fonts.includes()
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(dependencies.basePath, "extra/font/*.hpp"),
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings "Off"
|
||||||
|
kind "SharedItems"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, fonts)
|
4
deps/premake/mongoose.lua
vendored
4
deps/premake/mongoose.lua
vendored
@ -20,8 +20,8 @@ function mongoose.project()
|
|||||||
|
|
||||||
files
|
files
|
||||||
{
|
{
|
||||||
path.join(mongoose.source, "*.c"),
|
path.join(mongoose.source, "mongoose.c"),
|
||||||
path.join(mongoose.source, "*.h"),
|
path.join(mongoose.source, "mongoose.h"),
|
||||||
}
|
}
|
||||||
|
|
||||||
warnings "Off"
|
warnings "Off"
|
||||||
|
2
deps/protobuf
vendored
2
deps/protobuf
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 50bdb17409d4133d51ab6cfa095700f4c816576e
|
Subproject commit 57786d126249b5ed4f42b579047941805e742949
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 5752b171fd4cc96b8d1f9526ec1940199c6e9740
|
Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc
|
@ -57,7 +57,6 @@ namespace Components
|
|||||||
Loader::Register(new IPCPipe());
|
Loader::Register(new IPCPipe());
|
||||||
Loader::Register(new MapDump());
|
Loader::Register(new MapDump());
|
||||||
Loader::Register(new ModList());
|
Loader::Register(new ModList());
|
||||||
Loader::Register(new Monitor());
|
|
||||||
Loader::Register(new Network());
|
Loader::Register(new Network());
|
||||||
Loader::Register(new Session());
|
Loader::Register(new Session());
|
||||||
Loader::Register(new Theatre());
|
Loader::Register(new Theatre());
|
||||||
@ -72,11 +71,9 @@ namespace Components
|
|||||||
Loader::Register(new Dedicated());
|
Loader::Register(new Dedicated());
|
||||||
Loader::Register(new Discovery());
|
Loader::Register(new Discovery());
|
||||||
Loader::Register(new FastFiles());
|
Loader::Register(new FastFiles());
|
||||||
Loader::Register(new FrameTime());
|
|
||||||
Loader::Register(new Gametypes());
|
Loader::Register(new Gametypes());
|
||||||
Loader::Register(new Materials());
|
Loader::Register(new Materials());
|
||||||
Loader::Register(new Scheduler());
|
Loader::Register(new Scheduler());
|
||||||
Loader::Register(new Threading());
|
|
||||||
Loader::Register(new CardTitles());
|
Loader::Register(new CardTitles());
|
||||||
Loader::Register(new FileSystem());
|
Loader::Register(new FileSystem());
|
||||||
Loader::Register(new ModelSurfs());
|
Loader::Register(new ModelSurfs());
|
||||||
@ -91,7 +88,6 @@ namespace Components
|
|||||||
Loader::Register(new ZoneBuilder());
|
Loader::Register(new ZoneBuilder());
|
||||||
Loader::Register(new AssetHandler());
|
Loader::Register(new AssetHandler());
|
||||||
Loader::Register(new Localization());
|
Loader::Register(new Localization());
|
||||||
//Loader::Register(new MusicalTalent());
|
|
||||||
Loader::Register(new ServerCommands());
|
Loader::Register(new ServerCommands());
|
||||||
Loader::Register(new StructuredData());
|
Loader::Register(new StructuredData());
|
||||||
Loader::Register(new ConnectProtocol());
|
Loader::Register(new ConnectProtocol());
|
||||||
|
@ -80,7 +80,6 @@ namespace Components
|
|||||||
#include "Modules/Console.hpp"
|
#include "Modules/Console.hpp"
|
||||||
#include "Modules/UIScript.hpp"
|
#include "Modules/UIScript.hpp"
|
||||||
#include "Modules/ModList.hpp"
|
#include "Modules/ModList.hpp"
|
||||||
#include "Modules/Monitor.hpp"
|
|
||||||
#include "Modules/Network.hpp"
|
#include "Modules/Network.hpp"
|
||||||
#include "Modules/Theatre.hpp"
|
#include "Modules/Theatre.hpp"
|
||||||
#include "Modules/QuickPatch.hpp"
|
#include "Modules/QuickPatch.hpp"
|
||||||
@ -104,11 +103,9 @@ namespace Components
|
|||||||
#include "Modules/Discovery.hpp"
|
#include "Modules/Discovery.hpp"
|
||||||
#include "Modules/Exception.hpp"
|
#include "Modules/Exception.hpp"
|
||||||
#include "Modules/FastFiles.hpp"
|
#include "Modules/FastFiles.hpp"
|
||||||
#include "Modules/FrameTime.hpp"
|
|
||||||
#include "Modules/Gametypes.hpp"
|
#include "Modules/Gametypes.hpp"
|
||||||
#include "Modules/Materials.hpp"
|
#include "Modules/Materials.hpp"
|
||||||
#include "Modules/Singleton.hpp"
|
#include "Modules/Singleton.hpp"
|
||||||
#include "Modules/Threading.hpp"
|
|
||||||
#include "Modules/CardTitles.hpp"
|
#include "Modules/CardTitles.hpp"
|
||||||
#include "Modules/FileSystem.hpp"
|
#include "Modules/FileSystem.hpp"
|
||||||
#include "Modules/ModelSurfs.hpp"
|
#include "Modules/ModelSurfs.hpp"
|
||||||
@ -121,7 +118,6 @@ namespace Components
|
|||||||
#include "Modules/ZoneBuilder.hpp"
|
#include "Modules/ZoneBuilder.hpp"
|
||||||
#include "Modules/AssetHandler.hpp"
|
#include "Modules/AssetHandler.hpp"
|
||||||
#include "Modules/Localization.hpp"
|
#include "Modules/Localization.hpp"
|
||||||
#include "Modules/MusicalTalent.hpp"
|
|
||||||
#include "Modules/ServerCommands.hpp"
|
#include "Modules/ServerCommands.hpp"
|
||||||
#include "Modules/StructuredData.hpp"
|
#include "Modules/StructuredData.hpp"
|
||||||
#include "Modules/ConnectProtocol.hpp"
|
#include "Modules/ConnectProtocol.hpp"
|
||||||
|
@ -8,12 +8,12 @@ namespace Components
|
|||||||
class IAsset
|
class IAsset
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~IAsset() {};
|
virtual ~IAsset() {}
|
||||||
virtual Game::XAssetType getType() { return Game::XAssetType::ASSET_TYPE_INVALID; };
|
virtual Game::XAssetType getType() { return Game::XAssetType::ASSET_TYPE_INVALID; }
|
||||||
virtual void mark(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) { /*ErrorTypeNotSupported(this);*/ };
|
virtual void mark(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) {}
|
||||||
virtual void save(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) { /*ErrorTypeNotSupported(this);*/ };
|
virtual void save(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) {}
|
||||||
virtual void dump(Game::XAssetHeader /*header*/) { /*ErrorTypeNotSupported(this);*/ };
|
virtual void dump(Game::XAssetHeader /*header*/) {}
|
||||||
virtual void load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, ZoneBuilder::Zone* /*builder*/) { /*ErrorTypeNotSupported(this);*/ };
|
virtual void load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, ZoneBuilder::Zone* /*builder*/) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef Game::XAssetHeader(Callback)(Game::XAssetType type, const std::string& name);
|
typedef Game::XAssetHeader(Callback)(Game::XAssetType type, const std::string& name);
|
||||||
|
@ -80,7 +80,7 @@ namespace Assets
|
|||||||
|
|
||||||
void IFont_s::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
void IFont_s::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||||
{
|
{
|
||||||
Game::Font_s *asset = header.font;
|
const auto* asset = header.font;
|
||||||
|
|
||||||
if (asset->material)
|
if (asset->material)
|
||||||
{
|
{
|
||||||
@ -98,27 +98,33 @@ namespace Assets
|
|||||||
Components::FileSystem::File fontDefFile(Utils::String::VA("%s.json", name.data()));
|
Components::FileSystem::File fontDefFile(Utils::String::VA("%s.json", name.data()));
|
||||||
Components::FileSystem::File fontFile(Utils::String::VA("%s.ttf", name.data()));
|
Components::FileSystem::File fontFile(Utils::String::VA("%s.ttf", name.data()));
|
||||||
|
|
||||||
if (fontDefFile.exists() && fontFile.exists())
|
if (!fontDefFile.exists() || !fontFile.exists())
|
||||||
{
|
{
|
||||||
auto fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
|
|
||||||
|
|
||||||
if (!fontDef.is_object())
|
|
||||||
{
|
|
||||||
Components::Logger::Error(Game::ERR_FATAL, "Font define {} is invalid", name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int w = fontDef["textureWidth"].get<int>();
|
nlohmann::json fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
|
||||||
int h = fontDef["textureHeight"].get<int>();
|
try
|
||||||
|
{
|
||||||
|
fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Components::Logger::Error(Game::ERR_FATAL, "Json Parse Error: {}. Font {} is invalid", ex.what(), name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int size = fontDef["size"].get<int>();
|
auto w = fontDef["textureWidth"].get<int>();
|
||||||
int yOffset = fontDef["yOffset"].get<int>();
|
auto h = fontDef["textureHeight"].get<int>();
|
||||||
|
|
||||||
|
auto size = fontDef["size"].get<int>();
|
||||||
|
auto yOffset = fontDef["yOffset"].get<int>();
|
||||||
|
|
||||||
auto* pixels = builder->getAllocator()->allocateArray<uint8_t>(w * h);
|
auto* pixels = builder->getAllocator()->allocateArray<uint8_t>(w * h);
|
||||||
|
|
||||||
// Setup assets
|
// Setup assets
|
||||||
const auto* texName = builder->getAllocator()->duplicateString(Utils::String::VA("if_%s", name.data() + 6 /* skip "fonts/" */));
|
const auto* texName = builder->getAllocator()->duplicateString(Utils::String::VA("if_%s", name.data() + 6 /* skip "fonts/" */));
|
||||||
const auto* fontName = builder->getAllocator()->duplicateString(name.data());
|
const auto* fontName = builder->getAllocator()->duplicateString(name);
|
||||||
const auto* glowMaterialName = builder->getAllocator()->duplicateString(Utils::String::VA("%s_glow", name.data()));
|
const auto* glowMaterialName = builder->getAllocator()->duplicateString(Utils::String::VA("%s_glow", name.data()));
|
||||||
|
|
||||||
auto* image = builder->getAllocator()->allocate<Game::GfxImage>();
|
auto* image = builder->getAllocator()->allocate<Game::GfxImage>();
|
||||||
@ -142,20 +148,22 @@ namespace Assets
|
|||||||
glowMaterial->textureTable = material->textureTable;
|
glowMaterial->textureTable = material->textureTable;
|
||||||
glowMaterial->info.name = glowMaterialName;
|
glowMaterial->info.name = glowMaterialName;
|
||||||
|
|
||||||
std::vector<uint16_t> charset;
|
std::vector<std::uint16_t> charset;
|
||||||
|
|
||||||
if (fontDef["charset"].is_array())
|
if (fontDef["charset"].is_array())
|
||||||
{
|
{
|
||||||
nlohmann::json::array_t charsetArray = fontDef["charset"];
|
nlohmann::json::array_t charsetArray = fontDef["charset"];
|
||||||
for (auto& ch : charsetArray)
|
for (auto& ch : charsetArray)
|
||||||
charset.push_back(static_cast<uint16_t>(ch.get<int>()));
|
{
|
||||||
|
charset.push_back(static_cast<std::uint16_t>(ch.get<int>()));
|
||||||
|
}
|
||||||
|
|
||||||
// order matters
|
// order matters
|
||||||
std::sort(charset.begin(), charset.end());
|
std::ranges::sort(charset);
|
||||||
|
|
||||||
for (uint16_t i = 32; i < 128; i++)
|
for (std::uint16_t i = 32; i < 128; i++)
|
||||||
{
|
{
|
||||||
if (std::find(charset.begin(), charset.end(), i) == charset.end())
|
if (std::ranges::find(charset, i) == charset.end())
|
||||||
{
|
{
|
||||||
Components::Logger::Error(Game::ERR_FATAL, "Font {} missing codepoint {}", name.data(), i);
|
Components::Logger::Error(Game::ERR_FATAL, "Font {} missing codepoint {}", name.data(), i);
|
||||||
}
|
}
|
||||||
@ -163,9 +171,11 @@ namespace Assets
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (uint16_t i = 32; i < 128; i++)
|
for (std::uint16_t i = 32; i < 128; i++)
|
||||||
|
{
|
||||||
charset.push_back(i);
|
charset.push_back(i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto* font = builder->getAllocator()->allocate<Game::Font_s>();
|
auto* font = builder->getAllocator()->allocate<Game::Font_s>();
|
||||||
|
|
||||||
@ -173,7 +183,7 @@ namespace Assets
|
|||||||
font->pixelHeight = size;
|
font->pixelHeight = size;
|
||||||
font->material = material;
|
font->material = material;
|
||||||
font->glowMaterial = glowMaterial;
|
font->glowMaterial = glowMaterial;
|
||||||
font->glyphCount = charset.size();
|
font->glyphCount = static_cast<int>(charset.size());
|
||||||
font->glyphs = builder->getAllocator()->allocateArray<Game::Glyph>(charset.size());
|
font->glyphs = builder->getAllocator()->allocateArray<Game::Glyph>(charset.size());
|
||||||
|
|
||||||
// Generate glyph data
|
// Generate glyph data
|
||||||
@ -236,7 +246,7 @@ namespace Assets
|
|||||||
// Generate RGBA data
|
// Generate RGBA data
|
||||||
auto* rgbaPixels = outIwi.data() + sizeof(Game::GfxImageFileHeader);
|
auto* rgbaPixels = outIwi.data() + sizeof(Game::GfxImageFileHeader);
|
||||||
|
|
||||||
for (int i = 0; i < w * h * 4; i += 4)
|
for (auto i = 0; i < w * h * 4; i += 4)
|
||||||
{
|
{
|
||||||
rgbaPixels[i + 0] = static_cast<char>(255);
|
rgbaPixels[i + 0] = static_cast<char>(255);
|
||||||
rgbaPixels[i + 1] = static_cast<char>(255);
|
rgbaPixels[i + 1] = static_cast<char>(255);
|
||||||
@ -246,7 +256,6 @@ namespace Assets
|
|||||||
|
|
||||||
Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi);
|
Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||||
{
|
{
|
||||||
|
@ -3,13 +3,37 @@
|
|||||||
|
|
||||||
namespace Assets
|
namespace Assets
|
||||||
{
|
{
|
||||||
|
void ILocalizeEntry::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||||
|
{
|
||||||
|
const auto path = "localizedstrings/" + name;
|
||||||
|
|
||||||
|
Components::FileSystem::File rawFile(path);
|
||||||
|
if (!rawFile.exists())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::Logger::Debug("Parsing localized string \"{}\"...", path);
|
||||||
|
|
||||||
|
auto* asset = builder->getAllocator()->allocate<Game::LocalizeEntry>();
|
||||||
|
if (!asset)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asset->name = builder->getAllocator()->duplicateString(name);
|
||||||
|
asset->value = builder->getAllocator()->duplicateString(rawFile.getBuffer());
|
||||||
|
|
||||||
|
header->localize = asset;
|
||||||
|
}
|
||||||
|
|
||||||
void ILocalizeEntry::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
void ILocalizeEntry::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||||
{
|
{
|
||||||
AssertSize(Game::LocalizeEntry, 8);
|
AssertSize(Game::LocalizeEntry, 8);
|
||||||
|
|
||||||
Utils::Stream* buffer = builder->getBuffer();
|
auto* buffer = builder->getBuffer();
|
||||||
Game::LocalizeEntry* asset = header.localize;
|
auto* asset = header.localize;
|
||||||
Game::LocalizeEntry* dest = buffer->dest<Game::LocalizeEntry>();
|
auto* dest = buffer->dest<Game::LocalizeEntry>();
|
||||||
buffer->save(asset);
|
buffer->save(asset);
|
||||||
|
|
||||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||||
@ -28,4 +52,33 @@ namespace Assets
|
|||||||
|
|
||||||
buffer->popBlock();
|
buffer->popBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ILocalizeEntry::ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename)
|
||||||
|
{
|
||||||
|
std::vector<Game::LocalizeEntry*> assets;
|
||||||
|
const auto _0 = gsl::finally([]
|
||||||
|
{
|
||||||
|
Components::Localization::ParseOutput(nullptr);
|
||||||
|
Components::Localization::PrefixOverride = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
Components::Localization::PrefixOverride = Utils::String::ToUpper(name) + "_";
|
||||||
|
Components::Localization::ParseOutput([&assets](Game::LocalizeEntry* asset)
|
||||||
|
{
|
||||||
|
assets.push_back(asset);
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto* psLoadedFile = Game::SE_Load(filename.data(), false);
|
||||||
|
if (psLoadedFile)
|
||||||
|
{
|
||||||
|
Game::Com_PrintError(Game::CON_CHANNEL_SYSTEM, "^1Localization ERROR: %s\n", psLoadedFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto type = Game::DB_GetXAssetNameType("localize");
|
||||||
|
for (const auto& entry : assets)
|
||||||
|
{
|
||||||
|
builder->addRawAsset(type, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,11 @@ namespace Assets
|
|||||||
class ILocalizeEntry : public Components::AssetHandler::IAsset
|
class ILocalizeEntry : public Components::AssetHandler::IAsset
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY; };
|
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY; }
|
||||||
|
|
||||||
|
void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override;
|
||||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||||
|
|
||||||
|
static void ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,8 @@ namespace Assets
|
|||||||
"_add_lin_nofog",
|
"_add_lin_nofog",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<std::string, std::string> techSetCorrespondance = {
|
static std::unordered_map<std::string, std::string> techSetCorrespondance =
|
||||||
|
{
|
||||||
{"effect", "effect_blend"},
|
{"effect", "effect_blend"},
|
||||||
{"effect", "effect_blend"},
|
{"effect", "effect_blend"},
|
||||||
{"effect_nofog", "effect_blend_nofog"},
|
{"effect_nofog", "effect_blend_nofog"},
|
||||||
@ -50,12 +51,6 @@ namespace Assets
|
|||||||
{"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"},
|
{"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"},
|
||||||
{"mc_unlit", "mc_unlit_replace_lin_nocast"},
|
{"mc_unlit", "mc_unlit_replace_lin_nocast"},
|
||||||
{"mc_unlit_alphatest", "mc_unlit_blend_lin"}
|
{"mc_unlit_alphatest", "mc_unlit_blend_lin"}
|
||||||
/*,
|
|
||||||
{"", ""},
|
|
||||||
{"", ""},
|
|
||||||
{"", ""},
|
|
||||||
{"", ""},
|
|
||||||
{"", ""},*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Components::FileSystem::File materialFile(Utils::String::VA("materials/%s.iw4xMaterial", name.data()));
|
Components::FileSystem::File materialFile(Utils::String::VA("materials/%s.iw4xMaterial", name.data()));
|
||||||
@ -64,7 +59,7 @@ namespace Assets
|
|||||||
Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer());
|
Utils::Stream::Reader reader(builder->getAllocator(), materialFile.getBuffer());
|
||||||
|
|
||||||
char* magic = reader.readArray<char>(7);
|
char* magic = reader.readArray<char>(7);
|
||||||
if (std::memcmp(magic, "IW4xMat", 7))
|
if (std::memcmp(magic, "IW4xMat", 7) != 0)
|
||||||
{
|
{
|
||||||
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, header is invalid!", name);
|
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, header is invalid!", name);
|
||||||
}
|
}
|
||||||
@ -76,7 +71,7 @@ namespace Assets
|
|||||||
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, expected version is {}, but it was {}!", name, IW4X_MAT_VERSION, version);
|
Components::Logger::Error(Game::ERR_FATAL, "Reading material '{}' failed, expected version is {}, but it was {}!", name, IW4X_MAT_VERSION, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::Material* asset = reader.readObject<Game::Material>();
|
auto* asset = reader.readObject<Game::Material>();
|
||||||
|
|
||||||
if (asset->info.name)
|
if (asset->info.name)
|
||||||
{
|
{
|
||||||
@ -260,7 +255,7 @@ namespace Assets
|
|||||||
Game::XAssetHeader header = entry->asset.header;
|
Game::XAssetHeader header = entry->asset.header;
|
||||||
|
|
||||||
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet
|
if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet
|
||||||
&& !std::string(header.material->info.name).contains("icon")) // Yeah this has a tendency to fuck up a LOT of transparent materials
|
&& std::string(header.material->info.name).find("icon") == std::string::npos) // Yeah this has a tendency to fuck up a LOT of transparent materials
|
||||||
{
|
{
|
||||||
Components::Logger::Print("Material {} with techset {} has been mapped to {} (last chance!), taking the sort key of material {}\n",
|
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.name, asset->techniqueSet->name, header.material->techniqueSet->name, header.material->info.name);
|
||||||
@ -408,13 +403,13 @@ namespace Assets
|
|||||||
buffer->align(Utils::Stream::ALIGN_4);
|
buffer->align(Utils::Stream::ALIGN_4);
|
||||||
builder->storePointer(asset->textureTable);
|
builder->storePointer(asset->textureTable);
|
||||||
|
|
||||||
Game::MaterialTextureDef* destTextureTable = buffer->dest<Game::MaterialTextureDef>();
|
auto* destTextureTable = buffer->dest<Game::MaterialTextureDef>();
|
||||||
buffer->saveArray(asset->textureTable, asset->textureCount);
|
buffer->saveArray(asset->textureTable, asset->textureCount);
|
||||||
|
|
||||||
for (char i = 0; i < asset->textureCount; ++i)
|
for (std::uint8_t i = 0; i < asset->textureCount; ++i)
|
||||||
{
|
{
|
||||||
Game::MaterialTextureDef* destTextureDef = &destTextureTable[i];
|
auto* destTextureDef = &destTextureTable[i];
|
||||||
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
|
auto* textureDef = &asset->textureTable[i];
|
||||||
|
|
||||||
if (textureDef->semantic == SEMANTIC_WATER_MAP)
|
if (textureDef->semantic == SEMANTIC_WATER_MAP)
|
||||||
{
|
{
|
||||||
|
@ -7,31 +7,44 @@ namespace Assets
|
|||||||
{
|
{
|
||||||
Components::FileSystem::File rawFile(name);
|
Components::FileSystem::File rawFile(name);
|
||||||
|
|
||||||
if (rawFile.exists())
|
if (!rawFile.exists())
|
||||||
{
|
{
|
||||||
Game::RawFile* asset = builder->getAllocator()->allocate<Game::RawFile>();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (asset)
|
auto* asset = builder->getAllocator()->allocate<Game::RawFile>();
|
||||||
|
if (!asset)
|
||||||
{
|
{
|
||||||
//std::string data = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto data = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
|
||||||
|
|
||||||
asset->name = builder->getAllocator()->duplicateString(name);
|
asset->name = builder->getAllocator()->duplicateString(name);
|
||||||
|
|
||||||
|
if (data.size() < rawFile.getBuffer().size())
|
||||||
|
{
|
||||||
|
asset->buffer = builder->getAllocator()->duplicateString(data);
|
||||||
|
asset->compressedLen = static_cast<int>(data.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
asset->buffer = builder->getAllocator()->duplicateString(rawFile.getBuffer());
|
asset->buffer = builder->getAllocator()->duplicateString(rawFile.getBuffer());
|
||||||
asset->compressedLen = 0;//data.size();
|
asset->compressedLen = 0;
|
||||||
asset->len = rawFile.getBuffer().size();
|
}
|
||||||
|
|
||||||
|
asset->len = static_cast<int>(rawFile.getBuffer().size());
|
||||||
|
|
||||||
header->rawfile = asset;
|
header->rawfile = asset;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||||
{
|
{
|
||||||
AssertSize(Game::RawFile, 16);
|
AssertSize(Game::RawFile, 16);
|
||||||
|
|
||||||
Utils::Stream* buffer = builder->getBuffer();
|
auto* buffer = builder->getBuffer();
|
||||||
Game::RawFile* asset = header.rawfile;
|
auto* asset = header.rawfile;
|
||||||
Game::RawFile* dest = buffer->dest<Game::RawFile>();
|
auto* dest = buffer->dest<Game::RawFile>();
|
||||||
buffer->save(asset);
|
buffer->save(asset);
|
||||||
|
|
||||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||||
|
@ -10,6 +10,6 @@ namespace Assets
|
|||||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void saveStringTableCellArray(Components::ZoneBuilder::Zone* builder, Game::StringTableCell* values, int count);
|
static void saveStringTableCellArray(Components::ZoneBuilder::Zone* builder, Game::StringTableCell* values, int count);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ namespace Assets
|
|||||||
|
|
||||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||||
|
|
||||||
void saveStructuredDataEnumArray(Game::StructuredDataEnum* enums, int numEnums, Components::ZoneBuilder::Zone* builder);
|
static void saveStructuredDataEnumArray(Game::StructuredDataEnum* enums, int numEnums, Components::ZoneBuilder::Zone* builder);
|
||||||
void saveStructuredDataStructArray(Game::StructuredDataStruct* structs, int numStructs, Components::ZoneBuilder::Zone* builder);
|
static void saveStructuredDataStructArray(Game::StructuredDataStruct* structs, int numStructs, Components::ZoneBuilder::Zone* builder);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,26 @@ namespace Assets
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::snd_alias_list_t* aliasList = builder->getAllocator()->allocate<Game::snd_alias_list_t>();
|
auto* aliasList = builder->getAllocator()->allocate<Game::snd_alias_list_t>();
|
||||||
if (!aliasList)
|
if (!aliasList)
|
||||||
{
|
{
|
||||||
Components::Logger::Print("Error allocating memory for sound alias structure!\n");
|
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to allocate memory for sound alias structure!\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json infoData = nlohmann::json::parse(aliasFile.getBuffer());
|
nlohmann::json infoData;
|
||||||
nlohmann::json aliasesContainer = infoData["head"];
|
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;
|
nlohmann::json::array_t aliases = aliasesContainer;
|
||||||
|
|
||||||
aliasList->count = aliases.size();
|
aliasList->count = aliases.size();
|
||||||
@ -34,7 +44,7 @@ namespace Assets
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
aliasList->aliasName = builder->getAllocator()->duplicateString(name.c_str());
|
aliasList->aliasName = builder->getAllocator()->duplicateString(name);
|
||||||
|
|
||||||
for (size_t i = 0; i < aliasList->count; i++)
|
for (size_t i = 0; i < aliasList->count; i++)
|
||||||
{
|
{
|
||||||
@ -288,19 +298,14 @@ namespace Assets
|
|||||||
|
|
||||||
if (volumeFalloffCurve.is_string())
|
if (volumeFalloffCurve.is_string())
|
||||||
{
|
{
|
||||||
std::string fallOffCurve = volumeFalloffCurve.get<std::string>();
|
auto fallOffCurve = volumeFalloffCurve.get<std::string>();
|
||||||
|
|
||||||
if (fallOffCurve.size() == 0)
|
if (fallOffCurve.empty())
|
||||||
{
|
{
|
||||||
fallOffCurve = "$default";
|
fallOffCurve = "$default";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto curve = Components::AssetHandler::FindAssetForZone(
|
auto curve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, fallOffCurve, builder).sndCurve;
|
||||||
Game::XAssetType::ASSET_TYPE_SOUND_CURVE,
|
|
||||||
fallOffCurve.c_str(),
|
|
||||||
builder
|
|
||||||
).sndCurve;
|
|
||||||
|
|
||||||
alias->volumeFalloffCurve = curve;
|
alias->volumeFalloffCurve = curve;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,9 +381,9 @@ namespace Assets
|
|||||||
{
|
{
|
||||||
AssertSize(Game::snd_alias_list_t, 12);
|
AssertSize(Game::snd_alias_list_t, 12);
|
||||||
|
|
||||||
Utils::Stream* buffer = builder->getBuffer();
|
auto* buffer = builder->getBuffer();
|
||||||
Game::snd_alias_list_t* asset = header.sound;
|
auto* asset = header.sound;
|
||||||
Game::snd_alias_list_t* dest = buffer->dest<Game::snd_alias_list_t>();
|
auto* dest = buffer->dest<Game::snd_alias_list_t>();
|
||||||
buffer->save(asset);
|
buffer->save(asset);
|
||||||
|
|
||||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||||
@ -402,7 +407,7 @@ namespace Assets
|
|||||||
buffer->align(Utils::Stream::ALIGN_4);
|
buffer->align(Utils::Stream::ALIGN_4);
|
||||||
builder->storePointer(asset->head);
|
builder->storePointer(asset->head);
|
||||||
|
|
||||||
Game::snd_alias_t* destHead = buffer->dest<Game::snd_alias_t>();
|
auto* destHead = buffer->dest<Game::snd_alias_t>();
|
||||||
buffer->saveArray(asset->head, asset->count);
|
buffer->saveArray(asset->head, asset->count);
|
||||||
|
|
||||||
for (unsigned int i = 0; i < asset->count; ++i)
|
for (unsigned int i = 0; i < asset->count; ++i)
|
||||||
@ -453,7 +458,7 @@ namespace Assets
|
|||||||
buffer->align(Utils::Stream::ALIGN_4);
|
buffer->align(Utils::Stream::ALIGN_4);
|
||||||
builder->storePointer(alias->soundFile);
|
builder->storePointer(alias->soundFile);
|
||||||
|
|
||||||
Game::SoundFile* destSoundFile = buffer->dest<Game::SoundFile>();
|
auto* destSoundFile = buffer->dest<Game::SoundFile>();
|
||||||
buffer->save(alias->soundFile);
|
buffer->save(alias->soundFile);
|
||||||
|
|
||||||
// Save_SoundFileRef
|
// Save_SoundFileRef
|
||||||
@ -503,7 +508,7 @@ namespace Assets
|
|||||||
buffer->align(Utils::Stream::ALIGN_4);
|
buffer->align(Utils::Stream::ALIGN_4);
|
||||||
builder->storePointer(alias->speakerMap);
|
builder->storePointer(alias->speakerMap);
|
||||||
|
|
||||||
Game::SpeakerMap* destSoundFile = buffer->dest<Game::SpeakerMap>();
|
auto* destSoundFile = buffer->dest<Game::SpeakerMap>();
|
||||||
buffer->save(alias->speakerMap);
|
buffer->save(alias->speakerMap);
|
||||||
|
|
||||||
if (alias->speakerMap->name)
|
if (alias->speakerMap->name)
|
||||||
|
@ -277,7 +277,7 @@ namespace Components
|
|||||||
|
|
||||||
void Auth::StoreKey()
|
void Auth::StoreKey()
|
||||||
{
|
{
|
||||||
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && !Monitor::IsEnabled() && Auth::GuidKey.isValid())
|
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && Auth::GuidKey.isValid())
|
||||||
{
|
{
|
||||||
Proto::Auth::Certificate cert;
|
Proto::Auth::Certificate cert;
|
||||||
cert.set_token(Auth::GuidToken.toString());
|
cert.set_token(Auth::GuidToken.toString());
|
||||||
|
@ -130,12 +130,14 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string error;
|
nlohmann::json banData;
|
||||||
const auto banData = nlohmann::json::parse(bans.getBuffer());
|
try
|
||||||
|
|
||||||
if (!banData.is_object())
|
|
||||||
{
|
{
|
||||||
Logger::Debug("bans.json contains invalid data");
|
banData = nlohmann::json::parse(bans.getBuffer());
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +146,7 @@ namespace Components
|
|||||||
|
|
||||||
if (idList.is_array())
|
if (idList.is_array())
|
||||||
{
|
{
|
||||||
nlohmann::json::array_t arr = idList;
|
const nlohmann::json::array_t arr = idList;
|
||||||
|
|
||||||
for (auto &idEntry : arr)
|
for (auto &idEntry : arr)
|
||||||
{
|
{
|
||||||
@ -161,7 +163,7 @@ namespace Components
|
|||||||
|
|
||||||
if (ipList.is_array())
|
if (ipList.is_array())
|
||||||
{
|
{
|
||||||
nlohmann::json::array_t arr = ipList;
|
const nlohmann::json::array_t arr = ipList;
|
||||||
|
|
||||||
for (auto &ipEntry : arr)
|
for (auto &ipEntry : arr)
|
||||||
{
|
{
|
||||||
@ -225,7 +227,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Command::Add("banClient", [](Command::Params* params)
|
Command::Add("banClient", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
if (!(*Game::com_sv_running)->current.enabled)
|
if (!Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
Logger::Print("Server is not running.\n");
|
Logger::Print("Server is not running.\n");
|
||||||
return;
|
return;
|
||||||
@ -269,7 +271,7 @@ namespace Components
|
|||||||
|
|
||||||
Command::Add("unbanClient", [](Command::Params* params)
|
Command::Add("unbanClient", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
if (!(*Game::com_sv_running)->current.enabled)
|
if (!Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
Logger::Print("Server is not running.\n");
|
Logger::Print("Server is not running.\n");
|
||||||
return;
|
return;
|
||||||
|
@ -132,7 +132,7 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_botai[entref.entnum] = {0};
|
ZeroMemory(&g_botai[entref.entnum], sizeof(BotMovementInfo));
|
||||||
g_botai[entref.entnum].weapon = 1;
|
g_botai[entref.entnum].weapon = 1;
|
||||||
g_botai[entref.entnum].active = true;
|
g_botai[entref.entnum].active = true;
|
||||||
});
|
});
|
||||||
@ -317,7 +317,7 @@ namespace Components
|
|||||||
// Zero the bot command array
|
// Zero the bot command array
|
||||||
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
|
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
|
||||||
{
|
{
|
||||||
std::memset(&g_botai[i], 0, sizeof(BotMovementInfo));
|
ZeroMemory(&g_botai[i], sizeof(BotMovementInfo));
|
||||||
g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon
|
g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ namespace Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Game's code
|
// Game's code
|
||||||
if (surfaceType != Game::materialSurfType_t::SURF_TYPE_DEFAULT)
|
if (surfaceType != Game::SURF_TYPE_DEFAULT)
|
||||||
{
|
{
|
||||||
return (*Game::penetrationDepthTable)[weapDef->penetrateType][surfaceType];
|
return (*Game::penetrationDepthTable)[weapDef->penetrateType][surfaceType];
|
||||||
}
|
}
|
||||||
@ -42,16 +42,21 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bullet::BG_srand_Hk(unsigned int* pHoldrand)
|
||||||
|
{
|
||||||
|
*pHoldrand = static_cast<unsigned int>(std::rand());
|
||||||
|
}
|
||||||
|
|
||||||
Bullet::Bullet()
|
Bullet::Bullet()
|
||||||
{
|
{
|
||||||
BGSurfacePenetration = Dvar::Register<float>("bg_surfacePenetration", 0.0f,
|
BGSurfacePenetration = Dvar::Register<float>("bg_surfacePenetration", 0.0f,
|
||||||
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO,
|
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO, "Set to a value greater than 0 to override the surface penetration depth");
|
||||||
"Set to a value greater than 0 to override the surface penetration depth");
|
|
||||||
BGBulletRange = Game::Dvar_RegisterFloat("bg_bulletRange", 8192.0f,
|
BGBulletRange = Game::Dvar_RegisterFloat("bg_bulletRange", 8192.0f,
|
||||||
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO,
|
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO, "Max range used when calculating the bullet end position");
|
||||||
"Max range used when calculating the bullet end position");
|
|
||||||
|
|
||||||
Utils::Hook(0x4F6980, BG_GetSurfacePenetrationDepthStub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x4F6980, BG_GetSurfacePenetrationDepthStub, HOOK_JUMP).install()->quick();
|
||||||
Utils::Hook(0x440340, Bullet_FireStub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x440340, Bullet_FireStub, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
|
Utils::Hook(0x440368, BG_srand_Hk, HOOK_CALL).install()->quick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,7 @@ namespace Components
|
|||||||
static float BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType);
|
static float BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType);
|
||||||
|
|
||||||
static void Bullet_FireStub();
|
static void Bullet_FireStub();
|
||||||
|
|
||||||
|
static void BG_srand_Hk(unsigned int* pHoldrand);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,22 +10,22 @@ namespace Components
|
|||||||
signature.add({
|
signature.add({
|
||||||
"\x56\x8B\x00\x24\x0c\x85\xF6\x7F\x0E", "xx?xxxxxx", [](char* address)
|
"\x56\x8B\x00\x24\x0c\x85\xF6\x7F\x0E", "xx?xxxxxx", [](char* address)
|
||||||
{
|
{
|
||||||
Utils::Hook::Set<BYTE>(address, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(address, 0xC3);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
signature.process();
|
signature.process();
|
||||||
|
|
||||||
// Some more generic obfuscation (mov al, 1; retn)
|
// Some more generic obfuscation (mov al, 1; retn)
|
||||||
Utils::Hook::Set<DWORD>(0x471B20, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x471B20, 0xC301B0);
|
||||||
Utils::Hook::Set<DWORD>(0x43A070, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x43A070, 0xC301B0);
|
||||||
Utils::Hook::Set<DWORD>(0x4C8B30, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x4C8B30, 0xC301B0);
|
||||||
Utils::Hook::Set<DWORD>(0x469340, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x469340, 0xC301B0);
|
||||||
|
|
||||||
// Other checks
|
// Other checks
|
||||||
Utils::Hook::Set<DWORD>(0x401000, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x401000, 0xC301B0);
|
||||||
Utils::Hook::Set<DWORD>(0x45F8B0, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x45F8B0, 0xC301B0);
|
||||||
Utils::Hook::Set<DWORD>(0x46FAE0, 0xC301B0);
|
Utils::Hook::Set<std::uint32_t>(0x46FAE0, 0xC301B0);
|
||||||
|
|
||||||
// Removed in 159 SP binaries
|
// Removed in 159 SP binaries
|
||||||
Utils::Hook::Nop(0x46B173, 9);
|
Utils::Hook::Nop(0x46B173, 9);
|
||||||
@ -35,19 +35,21 @@ namespace Components
|
|||||||
// Disable some checks on certain game events
|
// Disable some checks on certain game events
|
||||||
Utils::Hook::Nop(0x43EC96, 9);
|
Utils::Hook::Nop(0x43EC96, 9);
|
||||||
Utils::Hook::Nop(0x4675C6, 9);
|
Utils::Hook::Nop(0x4675C6, 9);
|
||||||
|
Utils::Hook::Nop(0x405A36, 9);
|
||||||
|
|
||||||
// Random checks scattered throughout the binary
|
// Random checks scattered throughout the binary
|
||||||
Utils::Hook::Set<BYTE>(0x499F90, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x499F90, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x4FC700, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x4FC700, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x4C4170, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x4C4170, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x49E8C0, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x49E8C0, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x42DB00, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x42DB00, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x4F4CF0, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x4F4CF0, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x432180, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x432180, 0xC3);
|
||||||
Utils::Hook::Set<BYTE>(0x461930, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x461930, 0xC3);
|
||||||
|
Utils::Hook::Set<std::uint8_t>(0x430410, 0xC3);
|
||||||
|
|
||||||
// Used next to file system functions
|
// Used next to file system functions
|
||||||
Utils::Hook::Set<BYTE>(0x47BC00, 0xC3);
|
Utils::Hook::Set<std::uint8_t>(0x47BC00, 0xC3);
|
||||||
|
|
||||||
// Looking for stuff in the registry
|
// Looking for stuff in the registry
|
||||||
Utils::Hook::Nop(0x4826F8, 5);
|
Utils::Hook::Nop(0x4826F8, 5);
|
||||||
|
@ -29,23 +29,35 @@ namespace Components
|
|||||||
// Prevent callbacks from adding a new callback (would make the vector iterator invalid)
|
// Prevent callbacks from adding a new callback (would make the vector iterator invalid)
|
||||||
CanAddCallback = false;
|
CanAddCallback = false;
|
||||||
|
|
||||||
if (text[1] == '/')
|
// Chat messages sent through the console do not begin with \x15
|
||||||
|
auto msgIndex = 0;
|
||||||
|
if (text[msgIndex] == '\x15')
|
||||||
|
{
|
||||||
|
msgIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text[msgIndex] == '/')
|
||||||
{
|
{
|
||||||
SendChat = false;
|
SendChat = false;
|
||||||
text[1] = text[0];
|
|
||||||
|
if (msgIndex == 1)
|
||||||
|
{
|
||||||
|
// Overwrite / with \x15
|
||||||
|
text[msgIndex] = text[msgIndex - 1];
|
||||||
|
}
|
||||||
|
// Skip over the first character
|
||||||
++text;
|
++text;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMuted(player))
|
if (IsMuted(player))
|
||||||
{
|
{
|
||||||
SendChat = false;
|
SendChat = false;
|
||||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE,
|
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65));
|
||||||
Utils::String::VA("%c \"You are muted\"", 0x65));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& callback : SayCallbacks)
|
for (const auto& callback : SayCallbacks)
|
||||||
{
|
{
|
||||||
if (!ChatCallback(player, callback.getPos(), (text + 1), mode))
|
if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode))
|
||||||
{
|
{
|
||||||
SendChat = false;
|
SendChat = false;
|
||||||
}
|
}
|
||||||
@ -54,14 +66,13 @@ namespace Components
|
|||||||
if (sv_disableChat.get<bool>())
|
if (sv_disableChat.get<bool>())
|
||||||
{
|
{
|
||||||
SendChat = false;
|
SendChat = false;
|
||||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE,
|
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
||||||
Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRenderer::StripMaterialTextIcons(text, text, strlen(text) + 1);
|
TextRenderer::StripMaterialTextIcons(text, text, std::strlen(text) + 1);
|
||||||
|
|
||||||
Game::Scr_AddEntity(player);
|
Game::Scr_AddEntity(player);
|
||||||
Game::Scr_AddString(text + 1);
|
Game::Scr_AddString(text + msgIndex);
|
||||||
Game::Scr_NotifyLevel(Game::SL_GetString("say", 0), 2);
|
Game::Scr_NotifyLevel(Game::SL_GetString("say", 0), 2);
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
@ -285,9 +296,9 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Command::AddSV("muteClient", [](Command::Params* params)
|
Command::AddSV("muteClient", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
if (!(*Game::com_sv_running)->current.enabled)
|
if (!Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
Logger::Print("Server is not running.\n");
|
Logger::Print(Game::CON_CHANNEL_SERVER, "Server is not running.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,9 +319,9 @@ namespace Components
|
|||||||
|
|
||||||
Command::AddSV("unmute", [](Command::Params* params)
|
Command::AddSV("unmute", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
if (!(*Game::com_sv_running)->current.enabled)
|
if (!Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
Logger::Print("Server is not running.\n");
|
Logger::Print(Game::CON_CHANNEL_SERVER, "Server is not running.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,6 +356,12 @@ namespace Components
|
|||||||
|
|
||||||
Command::AddSV("say", [](Command::Params* params)
|
Command::AddSV("say", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
|
if (!Dedicated::IsRunning())
|
||||||
|
{
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SERVER, "Server is not running.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (params->size() < 2) return;
|
if (params->size() < 2) return;
|
||||||
|
|
||||||
auto message = params->join(1);
|
auto message = params->join(1);
|
||||||
@ -364,6 +381,12 @@ namespace Components
|
|||||||
|
|
||||||
Command::AddSV("tell", [](Command::Params* params)
|
Command::AddSV("tell", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
|
if (!Dedicated::IsRunning())
|
||||||
|
{
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SERVER, "Server is not running.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (params->size() < 3) return;
|
if (params->size() < 3) return;
|
||||||
|
|
||||||
const auto client = std::atoi(params->get(1));
|
const auto client = std::atoi(params->get(1));
|
||||||
@ -384,6 +407,12 @@ namespace Components
|
|||||||
|
|
||||||
Command::AddSV("sayraw", [](Command::Params* params)
|
Command::AddSV("sayraw", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
|
if (!Dedicated::IsRunning())
|
||||||
|
{
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SERVER, "Server is not running.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (params->size() < 2) return;
|
if (params->size() < 2) return;
|
||||||
|
|
||||||
auto message = params->join(1);
|
auto message = params->join(1);
|
||||||
@ -393,6 +422,12 @@ namespace Components
|
|||||||
|
|
||||||
Command::AddSV("tellraw", [](Command::Params* params)
|
Command::AddSV("tellraw", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
|
if (!Dedicated::IsRunning())
|
||||||
|
{
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SERVER, "Server is not running.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (params->size() < 3) return;
|
if (params->size() < 3) return;
|
||||||
|
|
||||||
const auto client = atoi(params->get(1));
|
const auto client = atoi(params->get(1));
|
||||||
|
@ -272,18 +272,5 @@ namespace Components
|
|||||||
// clanName in CG_Obituary
|
// clanName in CG_Obituary
|
||||||
Utils::Hook(0x586DD6, PlayerName::GetClientName, HOOK_CALL).install()->quick();
|
Utils::Hook(0x586DD6, PlayerName::GetClientName, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x586E2A, PlayerName::GetClientName, HOOK_CALL).install()->quick();
|
Utils::Hook(0x586E2A, PlayerName::GetClientName, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
Command::Add("statGet", [](Command::Params* params)
|
|
||||||
{
|
|
||||||
if (params->size() < 2)
|
|
||||||
{
|
|
||||||
Logger::PrintError(Game::CON_CHANNEL_SERVER, "statget usage: statget <index>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto index = std::atoi(params->get(1));
|
|
||||||
const auto stat = Game::LiveStorage_GetStat(0, index);
|
|
||||||
Logger::Print(Game::CON_CHANNEL_SYSTEM, "Stat {}: {}\n", index, stat);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,9 @@ namespace Components
|
|||||||
Command::ServerParams params;
|
Command::ServerParams params;
|
||||||
const auto command = Utils::String::ToLower(params.get(0));
|
const auto command = Utils::String::ToLower(params.get(0));
|
||||||
|
|
||||||
if (const auto got = HandlersSV.find(command); got != HandlersSV.end())
|
if (const auto itr = HandlersSV.find(command); itr != HandlersSV.end())
|
||||||
{
|
{
|
||||||
got->second(ent, ¶ms);
|
itr->second(ent, ¶ms);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,9 +157,9 @@ namespace Components
|
|||||||
ClientParams params;
|
ClientParams params;
|
||||||
const auto command = Utils::String::ToLower(params[0]);
|
const auto command = Utils::String::ToLower(params[0]);
|
||||||
|
|
||||||
if (const auto got = FunctionMap.find(command); got != FunctionMap.end())
|
if (const auto itr = FunctionMap.find(command); itr != FunctionMap.end())
|
||||||
{
|
{
|
||||||
got->second(¶ms);
|
itr->second(¶ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,9 +168,9 @@ namespace Components
|
|||||||
ServerParams params;
|
ServerParams params;
|
||||||
const auto command = Utils::String::ToLower(params[0]);
|
const auto command = Utils::String::ToLower(params[0]);
|
||||||
|
|
||||||
if (const auto got = FunctionMapSV.find(command); got != FunctionMapSV.end())
|
if (const auto itr = FunctionMapSV.find(command); itr != FunctionMapSV.end())
|
||||||
{
|
{
|
||||||
got->second(¶ms);
|
itr->second(¶ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace Components
|
|||||||
char workdir[MAX_PATH] = {0};
|
char workdir[MAX_PATH] = {0};
|
||||||
|
|
||||||
DWORD dwsize = MAX_PATH;
|
DWORD dwsize = MAX_PATH;
|
||||||
HMODULE hModule = GetModuleHandle(nullptr);
|
HMODULE hModule = GetModuleHandleA(nullptr);
|
||||||
|
|
||||||
if (hModule != nullptr)
|
if (hModule != nullptr)
|
||||||
{
|
{
|
||||||
@ -44,7 +44,7 @@ namespace Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char* endPtr = strstr(workdir, "iw4x.exe");
|
auto* endPtr = std::strstr(workdir, "iw4x.exe");
|
||||||
if (endPtr != nullptr)
|
if (endPtr != nullptr)
|
||||||
{
|
{
|
||||||
*endPtr = 0;
|
*endPtr = 0;
|
||||||
@ -183,7 +183,7 @@ namespace Components
|
|||||||
if (pos != std::string::npos)
|
if (pos != std::string::npos)
|
||||||
{
|
{
|
||||||
cmdLine = cmdLine.substr(pos + 7);
|
cmdLine = cmdLine.substr(pos + 7);
|
||||||
pos = cmdLine.find_first_of("/");
|
pos = cmdLine.find_first_of('/');
|
||||||
|
|
||||||
if (pos != std::string::npos)
|
if (pos != std::string::npos)
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
#include <STDInclude.hpp>
|
#include <STDInclude.hpp>
|
||||||
|
#undef MOUSE_MOVED
|
||||||
|
#include <curses.h>
|
||||||
|
|
||||||
|
#define REMOVE_HEADERBAR 1
|
||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
WINDOW* Console::OutputWindow;
|
static WINDOW* OutputWindow;
|
||||||
WINDOW* Console::InputWindow;
|
static WINDOW* InputWindow;
|
||||||
WINDOW* Console::InfoWindow;
|
static WINDOW* InfoWindow;
|
||||||
|
|
||||||
int Console::OutputTop = 0;
|
int Console::OutputTop = 0;
|
||||||
int Console::OutBuffer = 0;
|
int Console::OutBuffer = 0;
|
||||||
@ -20,6 +24,24 @@ namespace Components
|
|||||||
bool Console::HasConsole = false;
|
bool Console::HasConsole = false;
|
||||||
bool Console::SkipShutdown = false;
|
bool Console::SkipShutdown = false;
|
||||||
|
|
||||||
|
COLORREF Console::TextColor =
|
||||||
|
#if DEBUG
|
||||||
|
RGB(255, 200, 117);
|
||||||
|
#else
|
||||||
|
RGB(120, 237, 122);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
COLORREF Console::BackgroundColor =
|
||||||
|
#if DEBUG
|
||||||
|
RGB(35, 21, 0);
|
||||||
|
#else
|
||||||
|
RGB(25, 32, 25);
|
||||||
|
#endif
|
||||||
|
HBRUSH Console::ForegroundBrush = CreateSolidBrush(TextColor);
|
||||||
|
HBRUSH Console::BackgroundBrush = CreateSolidBrush(BackgroundColor);
|
||||||
|
|
||||||
|
HANDLE Console::CustomConsoleFont;
|
||||||
|
|
||||||
std::thread Console::ConsoleThread;
|
std::thread Console::ConsoleThread;
|
||||||
|
|
||||||
Game::SafeArea Console::OriginalSafeArea;
|
Game::SafeArea Console::OriginalSafeArea;
|
||||||
@ -59,9 +81,9 @@ namespace Components
|
|||||||
clientCount = Game::PartyHost_CountMembers(reinterpret_cast<Game::PartyData*>(0x1081C00));
|
clientCount = Game::PartyHost_CountMembers(reinterpret_cast<Game::PartyData*>(0x1081C00));
|
||||||
}
|
}
|
||||||
|
|
||||||
wclear(Console::InfoWindow);
|
wclear(InfoWindow);
|
||||||
wprintw(Console::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(Console::InfoWindow);
|
wnoutrefresh(InfoWindow);
|
||||||
}
|
}
|
||||||
else if (IsWindow(Console::GetWindow()) != FALSE)
|
else if (IsWindow(Console::GetWindow()) != FALSE)
|
||||||
{
|
{
|
||||||
@ -71,13 +93,13 @@ namespace Components
|
|||||||
|
|
||||||
void Console::ShowPrompt()
|
void Console::ShowPrompt()
|
||||||
{
|
{
|
||||||
wattron(Console::InputWindow, COLOR_PAIR(10) | A_BOLD);
|
wattron(InputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||||
wprintw(Console::InputWindow, "%s> ", VERSION);
|
wprintw(InputWindow, "%s> ", VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Console::RefreshOutput()
|
void Console::RefreshOutput()
|
||||||
{
|
{
|
||||||
prefresh(Console::OutputWindow, ((Console::OutputTop > 0) ? (Console::OutputTop - 1) : 0), 0, 1, 0, Console::Height - 2, Console::Width - 1);
|
prefresh(OutputWindow, ((Console::OutputTop > 0) ? (Console::OutputTop - 1) : 0), 0, 1, 0, Console::Height - 2, Console::Width - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Console::ScrollOutput(int amount)
|
void Console::ScrollOutput(int amount)
|
||||||
@ -110,12 +132,50 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Console::GetDpiScale(const HWND hWnd)
|
||||||
|
{
|
||||||
|
const auto user32 = Utils::Library("user32.dll");
|
||||||
|
const auto getDpiForWindow = user32.getProc<UINT(WINAPI*)(HWND)>("GetDpiForWindow");
|
||||||
|
const auto getDpiForMonitor = user32.getProc<HRESULT(WINAPI*)(HMONITOR, int, UINT*, UINT*)>("GetDpiForMonitor");
|
||||||
|
|
||||||
|
int dpi;
|
||||||
|
|
||||||
|
if (getDpiForWindow)
|
||||||
|
{
|
||||||
|
dpi = 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
dpi = 96;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HDC hDC = GetDC(hWnd);
|
||||||
|
INT ydpi = GetDeviceCaps(hDC, LOGPIXELSY);
|
||||||
|
ReleaseDC(NULL, hDC);
|
||||||
|
|
||||||
|
dpi = ydpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto unawareDpi = 96.0f;
|
||||||
|
return dpi / unawareDpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const char* Console::Input()
|
const char* Console::Input()
|
||||||
{
|
{
|
||||||
if (!Console::HasConsole)
|
if (!Console::HasConsole)
|
||||||
{
|
{
|
||||||
Console::ShowPrompt();
|
Console::ShowPrompt();
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
Console::HasConsole = true;
|
Console::HasConsole = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +186,7 @@ namespace Components
|
|||||||
Console::LastRefresh = currentTime;
|
Console::LastRefresh = currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
int c = wgetch(Console::InputWindow);
|
int c = wgetch(InputWindow);
|
||||||
|
|
||||||
if (c == ERR)
|
if (c == ERR)
|
||||||
{
|
{
|
||||||
@ -138,21 +198,21 @@ namespace Components
|
|||||||
case '\r':
|
case '\r':
|
||||||
case 459: // keypad enter
|
case 459: // keypad enter
|
||||||
{
|
{
|
||||||
wattron(Console::OutputWindow, COLOR_PAIR(10) | A_BOLD);
|
wattron(OutputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||||
wprintw(Console::OutputWindow, "%s", "]");
|
wprintw(OutputWindow, "%s", "]");
|
||||||
|
|
||||||
if (Console::LineBufferIndex)
|
if (Console::LineBufferIndex)
|
||||||
{
|
{
|
||||||
wprintw(Console::OutputWindow, "%s", Console::LineBuffer);
|
wprintw(OutputWindow, "%s", Console::LineBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
wprintw(Console::OutputWindow, "%s", "\n");
|
wprintw(OutputWindow, "%s", "\n");
|
||||||
wattroff(Console::OutputWindow, A_BOLD);
|
wattroff(OutputWindow, A_BOLD);
|
||||||
wclear(Console::InputWindow);
|
wclear(InputWindow);
|
||||||
|
|
||||||
Console::ShowPrompt();
|
Console::ShowPrompt();
|
||||||
|
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
|
|
||||||
Console::ScrollOutput(1);
|
Console::ScrollOutput(1);
|
||||||
Console::RefreshOutput();
|
Console::RefreshOutput();
|
||||||
@ -173,11 +233,11 @@ namespace Components
|
|||||||
Console::LineBuffer[0] = '\0';
|
Console::LineBuffer[0] = '\0';
|
||||||
Console::LineBufferIndex = 0;
|
Console::LineBufferIndex = 0;
|
||||||
|
|
||||||
wclear(Console::InputWindow);
|
wclear(InputWindow);
|
||||||
|
|
||||||
Console::ShowPrompt();
|
Console::ShowPrompt();
|
||||||
|
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 8: // backspace
|
case 8: // backspace
|
||||||
@ -187,8 +247,8 @@ namespace Components
|
|||||||
Console::LineBufferIndex--;
|
Console::LineBufferIndex--;
|
||||||
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
||||||
|
|
||||||
wprintw(Console::InputWindow, "%c %c", static_cast<char>(c), static_cast<char>(c));
|
wprintw(InputWindow, "%c %c", static_cast<char>(c), static_cast<char>(c));
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -206,10 +266,10 @@ namespace Components
|
|||||||
}
|
}
|
||||||
case KEY_UP:
|
case KEY_UP:
|
||||||
{
|
{
|
||||||
wclear(Console::InputWindow);
|
wclear(InputWindow);
|
||||||
Console::ShowPrompt();
|
Console::ShowPrompt();
|
||||||
wprintw(Console::InputWindow, "%s", Console::LineBuffer2);
|
wprintw(InputWindow, "%s", Console::LineBuffer2);
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
|
|
||||||
strcpy_s(Console::LineBuffer, Console::LineBuffer2);
|
strcpy_s(Console::LineBuffer, Console::LineBuffer2);
|
||||||
Console::LineBufferIndex = strlen(Console::LineBuffer);
|
Console::LineBufferIndex = strlen(Console::LineBuffer);
|
||||||
@ -223,8 +283,8 @@ namespace Components
|
|||||||
|
|
||||||
Console::LineBuffer[Console::LineBufferIndex++] = static_cast<char>(c);
|
Console::LineBuffer[Console::LineBufferIndex++] = static_cast<char>(c);
|
||||||
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
||||||
wprintw(Console::InputWindow, "%c", static_cast<char>(c));
|
wprintw(InputWindow, "%c", static_cast<char>(c));
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -236,17 +296,17 @@ namespace Components
|
|||||||
{
|
{
|
||||||
__try
|
__try
|
||||||
{
|
{
|
||||||
delwin(Console::OutputWindow);
|
delwin(OutputWindow);
|
||||||
delwin(Console::InputWindow);
|
delwin(InputWindow);
|
||||||
delwin(Console::InfoWindow);
|
delwin(InfoWindow);
|
||||||
endwin();
|
endwin();
|
||||||
delscreen(SP);
|
delscreen(SP);
|
||||||
}
|
}
|
||||||
__finally {}
|
__finally {}
|
||||||
|
|
||||||
Console::OutputWindow = nullptr;
|
OutputWindow = nullptr;
|
||||||
Console::InputWindow = nullptr;
|
InputWindow = nullptr;
|
||||||
Console::InfoWindow = nullptr;
|
InfoWindow = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Console::Create()
|
void Console::Create()
|
||||||
@ -273,15 +333,15 @@ namespace Components
|
|||||||
raw();
|
raw();
|
||||||
noecho();
|
noecho();
|
||||||
|
|
||||||
Console::OutputWindow = newpad(Console::Height - 1, Console::Width);
|
OutputWindow = newpad(Console::Height - 1, Console::Width);
|
||||||
Console::InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
|
InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
|
||||||
Console::InfoWindow = newwin(1, Console::Width, 0, 0);
|
InfoWindow = newwin(1, Console::Width, 0, 0);
|
||||||
|
|
||||||
scrollok(Console::OutputWindow, true);
|
scrollok(OutputWindow, true);
|
||||||
idlok(Console::OutputWindow, true);
|
idlok(OutputWindow, true);
|
||||||
scrollok(Console::InputWindow, true);
|
scrollok(InputWindow, true);
|
||||||
nodelay(Console::InputWindow, true);
|
nodelay(InputWindow, true);
|
||||||
keypad(Console::InputWindow, true);
|
keypad(InputWindow, true);
|
||||||
|
|
||||||
if (has_colors())
|
if (has_colors())
|
||||||
{
|
{
|
||||||
@ -298,10 +358,10 @@ namespace Components
|
|||||||
init_pair(10, COLOR_WHITE, COLOR_BLACK);
|
init_pair(10, COLOR_WHITE, COLOR_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
wbkgd(Console::InfoWindow, COLOR_PAIR(1));
|
wbkgd(InfoWindow, COLOR_PAIR(1));
|
||||||
|
|
||||||
wrefresh(Console::InfoWindow);
|
wrefresh(InfoWindow);
|
||||||
wrefresh(Console::InputWindow);
|
wrefresh(InputWindow);
|
||||||
|
|
||||||
Console::RefreshOutput();
|
Console::RefreshOutput();
|
||||||
}
|
}
|
||||||
@ -332,7 +392,7 @@ namespace Components
|
|||||||
|
|
||||||
void Console::Print(const char* message)
|
void Console::Print(const char* message)
|
||||||
{
|
{
|
||||||
if (!Console::OutputWindow) return;
|
if (!OutputWindow) return;
|
||||||
|
|
||||||
const char* p = message;
|
const char* p = message;
|
||||||
while (*p != '\0')
|
while (*p != '\0')
|
||||||
@ -346,34 +406,277 @@ namespace Components
|
|||||||
|
|
||||||
if (color < 9 && color > 0)
|
if (color < 9 && color > 0)
|
||||||
{
|
{
|
||||||
wattron(Console::OutputWindow, COLOR_PAIR(color + 2));
|
wattron(OutputWindow, COLOR_PAIR(color + 2));
|
||||||
++p;
|
++p;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waddch(Console::OutputWindow, *p);
|
waddch(OutputWindow, *p);
|
||||||
|
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
|
|
||||||
wattron(Console::OutputWindow, COLOR_PAIR(9));
|
wattron(OutputWindow, COLOR_PAIR(9));
|
||||||
|
|
||||||
// int currentTime = static_cast<int>(GetTickCount64()); // Make our compiler happy
|
|
||||||
//
|
|
||||||
// if (!Console::HasConsole)
|
|
||||||
// {
|
|
||||||
// Console::RefreshOutput();
|
|
||||||
// }
|
|
||||||
// else if ((currentTime - Console::LastRefresh) > 100)
|
|
||||||
// {
|
|
||||||
// Console::RefreshOutput();
|
|
||||||
// Console::LastRefresh = currentTime;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Console::RefreshOutput();
|
Console::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,
|
||||||
|
[[maybe_unused]] LPCSTR pszFaceName)
|
||||||
|
{
|
||||||
|
auto font = CreateFontA(
|
||||||
|
12,
|
||||||
|
cWidth,
|
||||||
|
cEscapement,
|
||||||
|
cOrientation,
|
||||||
|
700,
|
||||||
|
bItalic,
|
||||||
|
bUnderline,
|
||||||
|
bStrikeOut,
|
||||||
|
iCharSet,
|
||||||
|
OUT_RASTER_PRECIS,
|
||||||
|
iClipPrecision,
|
||||||
|
NONANTIALIASED_QUALITY,
|
||||||
|
0x31,
|
||||||
|
"Terminus (TTF)"); // Terminus (TTF)
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::GetWindowPos(HWND hWnd, int* x, int* y)
|
||||||
|
{
|
||||||
|
HWND hWndParent = GetParent(hWnd);
|
||||||
|
POINT p = { 0 };
|
||||||
|
|
||||||
|
MapWindowPoints(hWnd, hWndParent, &p, 1);
|
||||||
|
|
||||||
|
(*x) = p.x;
|
||||||
|
(*y) = p.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL CALLBACK Console::ResizeChildWindow(HWND hwndChild, LPARAM lParam)
|
||||||
|
{
|
||||||
|
auto id = GetWindowLong(hwndChild, GWL_ID);
|
||||||
|
bool isInputBox = id == INPUT_BOX;
|
||||||
|
bool isOutputBox = id == OUTPUT_BOX;
|
||||||
|
|
||||||
|
if (isInputBox || isOutputBox)
|
||||||
|
{
|
||||||
|
RECT newParentRect = *reinterpret_cast<LPRECT>(lParam);
|
||||||
|
|
||||||
|
RECT childRect;
|
||||||
|
|
||||||
|
if (GetWindowRect(hwndChild, &childRect))
|
||||||
|
{
|
||||||
|
|
||||||
|
int childX, childY;
|
||||||
|
|
||||||
|
GetWindowPos(hwndChild, &childX, &childY);
|
||||||
|
|
||||||
|
HWND parent = Utils::Hook::Get<HWND>(0x64A3288);
|
||||||
|
|
||||||
|
float 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!
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#ifdef REMOVE_HEADERBAR
|
||||||
|
margin = 10;
|
||||||
|
#endif
|
||||||
|
int newHeight = static_cast<int>((newParentRect.bottom - newParentRect.top) - 74 * scale - margin);
|
||||||
|
|
||||||
|
MoveWindow(hwndChild, newX, newY, newWidth, newHeight, TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead of clearing fully the console text whenever the 0x400's character is written, we
|
||||||
|
// clear it progressively when we run out of room by truncating the top line by line.
|
||||||
|
// A bit of trickery with SETREDRAW is required to avoid having the outputbox jump
|
||||||
|
// around whenever clearing occurs.
|
||||||
|
void Console::MakeRoomForText([[maybe_unused]] int addedCharacters)
|
||||||
|
{
|
||||||
|
constexpr unsigned int maxChars = 0x4000;
|
||||||
|
constexpr unsigned int maxAffectedChars = 0x100;
|
||||||
|
HWND outputBox = Utils::Hook::Get<HWND>(0x64A328C);
|
||||||
|
|
||||||
|
unsigned int totalChars;
|
||||||
|
unsigned int totalClearLength = 0;
|
||||||
|
|
||||||
|
char str[maxAffectedChars];
|
||||||
|
unsigned int fetchedCharacters = static_cast<unsigned int>(GetWindowText(outputBox, str, maxAffectedChars));
|
||||||
|
|
||||||
|
totalChars = GetWindowTextLengthA(outputBox);
|
||||||
|
|
||||||
|
while (totalChars - totalClearLength > maxChars)
|
||||||
|
{
|
||||||
|
unsigned int clearLength = maxAffectedChars; // Default to full clear
|
||||||
|
|
||||||
|
for (size_t i = 0; i < fetchedCharacters; i++)
|
||||||
|
{
|
||||||
|
if (str[i] == '\n')
|
||||||
|
{
|
||||||
|
// Shorter clear if I meet a linebreak
|
||||||
|
clearLength = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalClearLength += clearLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::Hook::Set(0x64A38B8, totalChars - totalClearLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __declspec(naked) Console::Sys_PrintStub()
|
||||||
|
{
|
||||||
|
__asm
|
||||||
|
{
|
||||||
|
pushad
|
||||||
|
push edi
|
||||||
|
call MakeRoomForText
|
||||||
|
pop edi
|
||||||
|
popad
|
||||||
|
|
||||||
|
// Go back to AppendText
|
||||||
|
push 0x4F57F8
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK Console::ConWndProc(HWND hWnd, UINT Msg, WPARAM wParam, unsigned int lParam)
|
||||||
|
{
|
||||||
|
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 !
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_CTLCOLORSTATIC:
|
||||||
|
case WM_CTLCOLOREDIT:
|
||||||
|
{
|
||||||
|
SetBkColor(reinterpret_cast<HDC>(wParam), BackgroundColor);
|
||||||
|
SetTextColor(reinterpret_cast<HDC>(wParam), TextColor);
|
||||||
|
return reinterpret_cast<LRESULT>(BackgroundBrush);
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM_SIZE:
|
||||||
|
RECT rect;
|
||||||
|
|
||||||
|
if (GetWindowRect(hWnd, &rect))
|
||||||
|
{
|
||||||
|
EnumChildWindows(hWnd, ResizeChildWindow, reinterpret_cast<LPARAM>(&rect));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall through to basegame
|
||||||
|
return Utils::Hook::Call<LRESULT CALLBACK(HWND, UINT, WPARAM, unsigned int)>(0x64DC50)(hWnd, Msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
ATOM CALLBACK Console::RegisterClassHook(WNDCLASSA* lpWndClass)
|
||||||
|
{
|
||||||
|
DeleteObject(lpWndClass->hbrBackground);
|
||||||
|
HBRUSH brush = CreateSolidBrush(BackgroundColor);
|
||||||
|
lpWndClass->hbrBackground = brush;
|
||||||
|
|
||||||
|
return RegisterClassA(lpWndClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
DWORD fontsInstalled;
|
||||||
|
CustomConsoleFont = AddFontMemResourceEx(const_cast<void*>(reinterpret_cast<const void*>(Font::Terminus::DATA)), Font::Terminus::LENGTH, 0, &fontsInstalled);
|
||||||
|
|
||||||
|
if (fontsInstalled > 0)
|
||||||
|
{
|
||||||
|
Utils::Hook::Nop(0x428A44, 6);
|
||||||
|
Utils::Hook(0x428A44, ReplaceFont, HOOK_CALL).install()->quick();
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::Hook::Nop(0x42892D, 6);
|
||||||
|
Utils::Hook(0x42892D, RegisterClassHook, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
|
Utils::Hook::Set(0x4288E6 + 4, &ConWndProc);
|
||||||
|
|
||||||
|
auto style = WS_CAPTION | WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
|
||||||
|
Utils::Hook::Set(0x42893F + 1, style);
|
||||||
|
Utils::Hook::Set(0x4289E2 + 1, style);
|
||||||
|
|
||||||
|
#ifdef REMOVE_HEADERBAR
|
||||||
|
// Remove that hideous header window -rox
|
||||||
|
Utils::Hook::Set(0x428A7C, static_cast<char>(0xEB));
|
||||||
|
Utils::Hook::Set(0X428AF1 + 1, static_cast<char>(10));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Never reset text
|
||||||
|
Utils::Hook::Nop(0x4F57DF, 0x4F57F6 - 0x4F57DF);
|
||||||
|
Utils::Hook(0x4F57DF, Console::Sys_PrintStub, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void Console::ConsoleRunner()
|
void Console::ConsoleRunner()
|
||||||
{
|
{
|
||||||
Console::SkipShutdown = false;
|
Console::SkipShutdown = false;
|
||||||
@ -475,7 +778,7 @@ namespace Components
|
|||||||
|
|
||||||
void Console::FreeNativeConsole()
|
void Console::FreeNativeConsole()
|
||||||
{
|
{
|
||||||
if (!Monitor::IsEnabled() && !Flags::HasFlag("stdout") && (!Dedicated::IsEnabled() || Flags::HasFlag("console")) && !Loader::IsPerformingUnitTests())
|
if (!Flags::HasFlag("stdout") && (!Dedicated::IsEnabled() || Flags::HasFlag("console")) && !Loader::IsPerformingUnitTests())
|
||||||
{
|
{
|
||||||
FreeConsole();
|
FreeConsole();
|
||||||
}
|
}
|
||||||
@ -588,14 +891,7 @@ namespace Components
|
|||||||
Utils::Hook(0x482AC3, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
Utils::Hook(0x482AC3, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// Modify console style
|
// Modify console style
|
||||||
Utils::Hook::Set<BYTE>(0x428A8E, 0); // Adjust logo Y pos
|
ApplyConsoleStyle();
|
||||||
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
|
|
||||||
|
|
||||||
// Don't resize the console
|
// Don't resize the console
|
||||||
Utils::Hook(0x64DC6B, 0x64DCC2, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x64DC6B, 0x64DCC2, HOOK_JUMP).install()->quick();
|
||||||
@ -613,16 +909,11 @@ namespace Components
|
|||||||
if (Loader::IsPerformingUnitTests()) return;
|
if (Loader::IsPerformingUnitTests()) return;
|
||||||
|
|
||||||
// External console
|
// External console
|
||||||
if (Flags::HasFlag("stdout") || Monitor::IsEnabled())
|
if (Flags::HasFlag("stdout"))
|
||||||
{
|
|
||||||
#ifndef DEBUG
|
|
||||||
if (!Monitor::IsEnabled())
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
Utils::Hook(0x4B2080, Console::StdOutPrint, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x4B2080, Console::StdOutPrint, HOOK_JUMP).install()->quick();
|
||||||
Utils::Hook(0x43D570, Console::StdOutError, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x43D570, Console::StdOutError, HOOK_JUMP).install()->quick();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) // ZoneBuilder uses the game's console, until the native one is adapted.
|
else if (Flags::HasFlag("console") || ZoneBuilder::IsEnabled()) // ZoneBuilder uses the game's console, until the native one is adapted.
|
||||||
{
|
{
|
||||||
Utils::Hook::Nop(0x60BB58, 11);
|
Utils::Hook::Nop(0x60BB58, 11);
|
||||||
@ -630,7 +921,7 @@ namespace Components
|
|||||||
// Redirect input (]command)
|
// Redirect input (]command)
|
||||||
Utils::Hook(0x47025A, 0x4F5770, HOOK_CALL).install()->quick();
|
Utils::Hook(0x47025A, 0x4F5770, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
Utils::Hook(0x60BB68, []()
|
Utils::Hook(0x60BB68, []
|
||||||
{
|
{
|
||||||
Console::ShowAsyncConsole();
|
Console::ShowAsyncConsole();
|
||||||
}, HOOK_CALL).install()->quick();
|
}, HOOK_CALL).install()->quick();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Terminus_4.49.1.ttf.hpp"
|
||||||
|
|
||||||
#define OUTPUT_HEIGHT 250
|
#define OUTPUT_HEIGHT 250
|
||||||
#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2))
|
#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2))
|
||||||
|
|
||||||
@ -20,10 +22,9 @@ namespace Components
|
|||||||
static void ShowAsyncConsole();
|
static void ShowAsyncConsole();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Text-based console stuff
|
|
||||||
static WINDOW* OutputWindow;
|
static constexpr int OUTPUT_BOX = 0x64;
|
||||||
static WINDOW* InputWindow;
|
static constexpr int INPUT_BOX = 0x65;
|
||||||
static WINDOW* InfoWindow;
|
|
||||||
|
|
||||||
static int Width;
|
static int Width;
|
||||||
static int Height;
|
static int Height;
|
||||||
@ -32,6 +33,13 @@ namespace Components
|
|||||||
static int OutBuffer;
|
static int OutBuffer;
|
||||||
static int LastRefresh;
|
static int LastRefresh;
|
||||||
|
|
||||||
|
static COLORREF TextColor;
|
||||||
|
static COLORREF BackgroundColor;
|
||||||
|
static HBRUSH ForegroundBrush;
|
||||||
|
static HBRUSH BackgroundBrush;
|
||||||
|
|
||||||
|
static HANDLE CustomConsoleFont;
|
||||||
|
|
||||||
static char LineBuffer[1024];
|
static char LineBuffer[1024];
|
||||||
static char LineBuffer2[1024];
|
static char LineBuffer2[1024];
|
||||||
static int LineBufferIndex;
|
static int LineBufferIndex;
|
||||||
@ -69,5 +77,29 @@ namespace Components
|
|||||||
static void AddConsoleCommand();
|
static void AddConsoleCommand();
|
||||||
|
|
||||||
static Game::dvar_t* RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, float max, unsigned __int16 flags, const char* description);
|
static Game::dvar_t* RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, float max, unsigned __int16 flags, const char* description);
|
||||||
|
|
||||||
|
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 void ApplyConsoleStyle();
|
||||||
|
static void GetWindowPos(HWND hWnd, int* x, int* y);
|
||||||
|
static void Sys_PrintStub();
|
||||||
|
static void MakeRoomForText(int addedCharacters);
|
||||||
|
static float GetDpiScale(const HWND hWnd);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <STDInclude.hpp>
|
#include <STDInclude.hpp>
|
||||||
|
#include "Game/Engine/ScopedCriticalSection.hpp"
|
||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
@ -264,11 +265,6 @@ namespace Components
|
|||||||
assert(0 && "a");
|
assert(0 && "a");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Debug::Cbuf_AddServerText_f_Hk()
|
|
||||||
{
|
|
||||||
assert(0 && "Cbuf_AddServerText_f was called.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Debug::Com_Bug_f(Command::Params* params)
|
void Debug::Com_Bug_f(Command::Params* params)
|
||||||
{
|
{
|
||||||
char newFileName[0x105]{};
|
char newFileName[0x105]{};
|
||||||
@ -293,7 +289,7 @@ namespace Components
|
|||||||
|
|
||||||
sprintf_s(newFileName, "%s_%s.log", bug, Game::Live_GetLocalClientName(0));
|
sprintf_s(newFileName, "%s_%s.log", bug, Game::Live_GetLocalClientName(0));
|
||||||
|
|
||||||
Game::Sys_EnterCriticalSection(Game::CRITSECT_CONSOLE);
|
Game::Engine::ScopedCriticalSection _(Game::CRITSECT_CONSOLE, Game::Engine::SCOPED_CRITSECT_NORMAL);
|
||||||
|
|
||||||
if (*Game::logfile)
|
if (*Game::logfile)
|
||||||
{
|
{
|
||||||
@ -306,8 +302,6 @@ namespace Components
|
|||||||
const auto result = CopyFileA(from_ospath, to_ospath, 0);
|
const auto result = CopyFileA(from_ospath, to_ospath, 0);
|
||||||
Game::Com_OpenLogFile();
|
Game::Com_OpenLogFile();
|
||||||
|
|
||||||
Game::Sys_LeaveCriticalSection(Game::CRITSECT_CONSOLE);
|
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
Logger::PrintError(1, "CopyFile failed({}) {} {}\n", GetLastError(), "console_mp.log", newFileName);
|
Logger::PrintError(1, "CopyFile failed({}) {} {}\n", GetLastError(), "console_mp.log", newFileName);
|
||||||
@ -341,7 +335,6 @@ namespace Components
|
|||||||
Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x49CB0A, CG_DrawDebugOverlays_Hk, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f);
|
Utils::Hook::Set<void(*)()>(0x60BCEA, Com_Assert_f);
|
||||||
Utils::Hook(Game::Cbuf_AddServerText_f, Cbuf_AddServerText_f_Hk, HOOK_JUMP).install()->quick();
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
Command::Add("bug", Com_Bug_f);
|
Command::Add("bug", Com_Bug_f);
|
||||||
|
@ -42,7 +42,6 @@ namespace Components
|
|||||||
static void CG_DrawDebugOverlays_Hk(int localClientNum);
|
static void CG_DrawDebugOverlays_Hk(int localClientNum);
|
||||||
|
|
||||||
static void Com_Assert_f();
|
static void Com_Assert_f();
|
||||||
static void Cbuf_AddServerText_f_Hk();
|
|
||||||
static void Com_Bug_f(Command::Params* params);
|
static void Com_Bug_f(Command::Params* params);
|
||||||
|
|
||||||
static void CL_InitDebugDvars();
|
static void CL_InitDebugDvars();
|
||||||
|
@ -19,6 +19,12 @@ namespace Components
|
|||||||
return flag.value();
|
return flag.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Dedicated::IsRunning()
|
||||||
|
{
|
||||||
|
assert(*Game::com_sv_running);
|
||||||
|
return *Game::com_sv_running && (*Game::com_sv_running)->current.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
void Dedicated::InitDedicatedServer()
|
void Dedicated::InitDedicatedServer()
|
||||||
{
|
{
|
||||||
static const char* fastfiles[7] =
|
static const char* fastfiles[7] =
|
||||||
@ -59,8 +65,7 @@ namespace Components
|
|||||||
Command::Execute("snaps 30");
|
Command::Execute("snaps 30");
|
||||||
Command::Execute("com_maxfps 125");
|
Command::Execute("com_maxfps 125");
|
||||||
|
|
||||||
// Process command line?
|
Game::Com_AddStartupCommands();
|
||||||
Utils::Hook::Call<void()>(0x60C3D0)();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__declspec(naked) void Dedicated::PostInitializationStub()
|
__declspec(naked) void Dedicated::PostInitializationStub()
|
||||||
@ -262,7 +267,7 @@ namespace Components
|
|||||||
|
|
||||||
Scheduler::Loop([]
|
Scheduler::Loop([]
|
||||||
{
|
{
|
||||||
if ((*Game::com_sv_running)->current.enabled)
|
if (Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
Dedicated::TransmitGuids();
|
Dedicated::TransmitGuids();
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ namespace Components
|
|||||||
static Dvar::Var COMLogFilter;
|
static Dvar::Var COMLogFilter;
|
||||||
|
|
||||||
static bool IsEnabled();
|
static bool IsEnabled();
|
||||||
|
static bool IsRunning();
|
||||||
|
|
||||||
static void Heartbeat();
|
static void Heartbeat();
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
#include <STDInclude.hpp>
|
#include <STDInclude.hpp>
|
||||||
#include "GSC/Script.hpp"
|
#include "GSC/Script.hpp"
|
||||||
|
|
||||||
|
#include <mongoose.h>
|
||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
mg_mgr Download::Mgr;
|
static mg_mgr Mgr;
|
||||||
|
|
||||||
Download::ClientDownload Download::CLDownload;
|
Download::ClientDownload Download::CLDownload;
|
||||||
|
|
||||||
std::thread Download::ServerThread;
|
std::thread Download::ServerThread;
|
||||||
@ -32,15 +35,15 @@ namespace Components
|
|||||||
|
|
||||||
if (needPassword)
|
if (needPassword)
|
||||||
{
|
{
|
||||||
std::string pass = Dvar::Var("password").get<std::string>();
|
const auto password = Dvar::Var("password").get<std::string>();
|
||||||
if (pass.empty())
|
if (password.empty())
|
||||||
{
|
{
|
||||||
// shouldn't ever happen but this is safe
|
// shouldn't ever happen but this is safe
|
||||||
Party::ConnectError("A password is required to connect to this server!");
|
Party::ConnectError("A password is required to connect to this server!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Download::CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(pass), "");
|
Download::CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
Download::CLDownload.running = true;
|
Download::CLDownload.running = true;
|
||||||
@ -61,32 +64,39 @@ namespace Components
|
|||||||
if (!download) return false;
|
if (!download) return false;
|
||||||
download->files.clear();
|
download->files.clear();
|
||||||
|
|
||||||
std::string error;
|
nlohmann::json listData;
|
||||||
nlohmann::json listData = nlohmann::json::parse(list);
|
try
|
||||||
|
{
|
||||||
if (!error.empty() || !listData.is_array())
|
listData = nlohmann::json::parse(list);
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!listData.is_array())
|
||||||
{
|
{
|
||||||
Logger::Print("Error: {}\n", error);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
download->totalBytes = 0;
|
download->totalBytes = 0;
|
||||||
nlohmann::json::array_t listDataArray = listData;
|
const nlohmann::json::array_t listDataArray = listData;
|
||||||
|
|
||||||
for (auto& file : listDataArray)
|
for (auto& file : listDataArray)
|
||||||
{
|
{
|
||||||
if (!file.is_object()) return false;
|
if (!file.is_object()) return false;
|
||||||
|
|
||||||
auto hash = file["hash"];
|
try
|
||||||
auto name = file["name"];
|
{
|
||||||
auto size = file["size"];
|
const auto hash = file.at("hash").get<std::string>();
|
||||||
|
const auto name = file.at("name").get<std::string>();
|
||||||
if (!hash.is_string() || !name.is_string() || !size.is_number()) return false;
|
const auto size = file.at("size").get<std::size_t>();
|
||||||
|
|
||||||
Download::ClientDownload::File fileEntry;
|
Download::ClientDownload::File fileEntry;
|
||||||
fileEntry.name = name.get<std::string>();
|
fileEntry.name = name;
|
||||||
fileEntry.hash = hash.get<std::string>();
|
fileEntry.hash = hash;
|
||||||
fileEntry.size = size.get<size_t>();
|
fileEntry.size = size;
|
||||||
|
|
||||||
if (!fileEntry.name.empty())
|
if (!fileEntry.name.empty())
|
||||||
{
|
{
|
||||||
@ -94,45 +104,16 @@ namespace Components
|
|||||||
download->totalBytes += fileEntry.size;
|
download->totalBytes += fileEntry.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (const nlohmann::json::exception& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Error: {}\n", ex.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::DownloadHandler(mg_connection *nc, int ev, void* ev_data)
|
|
||||||
{
|
|
||||||
http_message* hm = reinterpret_cast<http_message*>(ev_data);
|
|
||||||
Download::FileDownload* fDownload = reinterpret_cast<Download::FileDownload*>(nc->mgr->user_data);
|
|
||||||
|
|
||||||
if (ev == MG_EV_CONNECT)
|
|
||||||
{
|
|
||||||
if (hm->message.p)
|
|
||||||
{
|
|
||||||
fDownload->downloading = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev == MG_EV_CLOSE)
|
|
||||||
{
|
|
||||||
fDownload->downloading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev == MG_EV_RECV)
|
|
||||||
{
|
|
||||||
size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
|
|
||||||
Download::DownloadProgress(fDownload, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev == MG_EV_HTTP_REPLY)
|
|
||||||
{
|
|
||||||
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
|
|
||||||
fDownload->buffer = std::string(hm->body.p, hm->body.len);
|
|
||||||
fDownload->downloading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Download::DownloadFile(ClientDownload* download, unsigned int index)
|
bool Download::DownloadFile(ClientDownload* download, unsigned int index)
|
||||||
{
|
{
|
||||||
if (!download || download->files.size() <= index) return false;
|
if (!download || download->files.size() <= index) return false;
|
||||||
@ -214,21 +195,7 @@ namespace Components
|
|||||||
|
|
||||||
Utils::String::Replace(url, " ", "%20");
|
Utils::String::Replace(url, " ", "%20");
|
||||||
|
|
||||||
// Just a speedtest ;)
|
|
||||||
//download->totalBytes = 1048576000;
|
|
||||||
//url = "http://speed.hetzner.de/1GB.bin";
|
|
||||||
|
|
||||||
download->valid = true;
|
download->valid = true;
|
||||||
/*ZeroMemory(&download->mgr, sizeof download->mgr);
|
|
||||||
mg_mgr_init(&download->mgr, &fDownload);
|
|
||||||
mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr);
|
|
||||||
|
|
||||||
while (fDownload.downloading && !fDownload.download->terminateThread)
|
|
||||||
{
|
|
||||||
mg_mgr_poll(&download->mgr, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
mg_mgr_free(&download->mgr);*/
|
|
||||||
|
|
||||||
fDownload.downloading = true;
|
fDownload.downloading = true;
|
||||||
|
|
||||||
@ -374,32 +341,7 @@ namespace Components
|
|||||||
|
|
||||||
#pragma region Server
|
#pragma region Server
|
||||||
|
|
||||||
bool Download::IsClient(mg_connection *nc)
|
void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes)
|
||||||
{
|
|
||||||
return (Download::GetClient(nc) != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Game::client_t* Download::GetClient(mg_connection *nc)
|
|
||||||
{
|
|
||||||
Network::Address address(nc->sa.sa);
|
|
||||||
|
|
||||||
for (int i = 0; i < *Game::svs_clientCount; ++i)
|
|
||||||
{
|
|
||||||
Game::client_t* client = &Game::svs_clients[i];
|
|
||||||
|
|
||||||
if (client->header.state >= Game::CS_CONNECTED)
|
|
||||||
{
|
|
||||||
if (address.getIP().full == Network::Address(client->header.netchan.remoteAddress).getIP().full)
|
|
||||||
{
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::DownloadProgress(FileDownload* fDownload, size_t bytes)
|
|
||||||
{
|
{
|
||||||
fDownload->receivedBytes += bytes;
|
fDownload->receivedBytes += bytes;
|
||||||
fDownload->download->downBytes += bytes;
|
fDownload->download->downBytes += bytes;
|
||||||
@ -415,10 +357,10 @@ namespace Components
|
|||||||
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
|
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned int dlIndex, dlSize, dlProgress;
|
static std::uint32_t dlIndex, dlSize, dlProgress;
|
||||||
dlIndex = fDownload->index + 1;
|
dlIndex = fDownload->index + 1;
|
||||||
dlSize = fDownload->download->files.size();
|
dlSize = fDownload->download->files.size();
|
||||||
dlProgress = static_cast<unsigned int>(progress);
|
dlProgress = static_cast<std::uint32_t>(progress);
|
||||||
|
|
||||||
framePushed = true;
|
framePushed = true;
|
||||||
Scheduler::Once([]
|
Scheduler::Once([]
|
||||||
@ -462,294 +404,26 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Download::VerifyPassword(mg_connection *nc, http_message* message)
|
static std::string InfoHandler()
|
||||||
{
|
{
|
||||||
std::string g_password = Dvar::Var("g_password").get<std::string>();
|
const auto status = ServerInfo::GetInfo();
|
||||||
if (g_password.empty()) return true;
|
const auto host = ServerInfo::GetHostInfo();
|
||||||
|
|
||||||
// sha256 hashes are 64 chars long but we're gonna be safe here
|
std::unordered_map<std::string, nlohmann::json> info;
|
||||||
char buffer[128] = { 0 };
|
|
||||||
int passLen = mg_get_http_var(&message->query_string, "password", buffer, sizeof buffer);
|
|
||||||
|
|
||||||
if (passLen <= 0 || std::string(buffer, passLen) != Utils::Cryptography::SHA256::Compute(g_password, true))
|
|
||||||
{
|
|
||||||
mg_printf(nc, ("HTTP/1.1 403 Forbidden\r\n"s +
|
|
||||||
"Content-Type: text/html\r\n"s +
|
|
||||||
"Connection: close\r\n"s +
|
|
||||||
"\r\n"s +
|
|
||||||
((passLen == 0) ? "Password Required"s : "Invalid Password"s)).c_str());
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::Forbid(mg_connection *nc)
|
|
||||||
{
|
|
||||||
mg_printf(nc, "HTTP/1.1 403 Forbidden\r\n"
|
|
||||||
"Content-Type: text/html\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"403 - Forbidden");
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::ServerlistHandler(mg_connection* nc, int ev, void* /*ev_data*/)
|
|
||||||
{
|
|
||||||
// Only handle http requests
|
|
||||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
||||||
|
|
||||||
std::vector<nlohmann::json> servers;
|
|
||||||
|
|
||||||
// Build server list
|
|
||||||
for (auto& node : Node::GetNodes())
|
|
||||||
{
|
|
||||||
if (node.isValid())
|
|
||||||
{
|
|
||||||
servers.push_back(nlohmann::json{ node.to_json()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mg_printf(nc,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: application/json\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"Access-Control-Allow-Origin: *\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"%s", nlohmann::json(servers).dump().data());
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::MapHandler(mg_connection *nc, int ev, void* ev_data)
|
|
||||||
{
|
|
||||||
// Only handle http requests
|
|
||||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
||||||
|
|
||||||
if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
|
|
||||||
|
|
||||||
static std::string mapnamePre;
|
|
||||||
static nlohmann::json jsonList;
|
|
||||||
|
|
||||||
std::string mapname = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
|
||||||
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
|
|
||||||
{
|
|
||||||
mapnamePre.clear();
|
|
||||||
jsonList = std::vector<nlohmann::json>();
|
|
||||||
}
|
|
||||||
else if (!mapname.empty() && mapname != mapnamePre)
|
|
||||||
{
|
|
||||||
std::vector<nlohmann::json> fileList;
|
|
||||||
|
|
||||||
mapnamePre = mapname;
|
|
||||||
|
|
||||||
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\usermaps\\" + mapname;
|
|
||||||
|
|
||||||
for (int i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
|
||||||
{
|
|
||||||
std::string filename = path + "\\" + mapname + Maps::UserMapFiles[i];
|
|
||||||
if (Utils::IO::FileExists(filename))
|
|
||||||
{
|
|
||||||
std::map<std::string, nlohmann::json> file;
|
|
||||||
std::string fileBuffer = Utils::IO::ReadFile(filename);
|
|
||||||
|
|
||||||
file["name"] = mapname + Maps::UserMapFiles[i];
|
|
||||||
file["size"] = static_cast<int>(fileBuffer.size());
|
|
||||||
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
|
||||||
|
|
||||||
fileList.push_back(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonList = fileList;
|
|
||||||
}
|
|
||||||
|
|
||||||
mg_printf(nc,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: application/json\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"%s", jsonList.dump().data());
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::ListHandler(mg_connection* nc, int ev, void* ev_data)
|
|
||||||
{
|
|
||||||
// Only handle http requests
|
|
||||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
||||||
|
|
||||||
if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
|
|
||||||
|
|
||||||
// if (!Download::IsClient(nc))
|
|
||||||
// {
|
|
||||||
// Download::Forbid(nc);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
{
|
|
||||||
static std::string fsGamePre;
|
|
||||||
static nlohmann::json jsonList;
|
|
||||||
|
|
||||||
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
|
|
||||||
|
|
||||||
if (!fsGame.empty() && fsGame != fsGamePre)
|
|
||||||
{
|
|
||||||
std::vector<nlohmann::json> fileList;
|
|
||||||
|
|
||||||
fsGamePre = fsGame;
|
|
||||||
|
|
||||||
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + fsGame;
|
|
||||||
auto list = FileSystem::GetSysFileList(path, "iwd", false);
|
|
||||||
|
|
||||||
list.push_back("mod.ff");
|
|
||||||
|
|
||||||
for (auto i = list.begin(); i != list.end(); ++i)
|
|
||||||
{
|
|
||||||
std::string filename = path + "\\" + *i;
|
|
||||||
if (strstr(i->data(), "_svr_") == nullptr && Utils::IO::FileExists(filename))
|
|
||||||
{
|
|
||||||
std::map<std::string, nlohmann::json> file;
|
|
||||||
std::string fileBuffer = Utils::IO::ReadFile(filename);
|
|
||||||
|
|
||||||
file["name"] = *i;
|
|
||||||
file["size"] = static_cast<int>(fileBuffer.size());
|
|
||||||
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
|
||||||
|
|
||||||
fileList.push_back(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonList = fileList;
|
|
||||||
}
|
|
||||||
|
|
||||||
mg_printf(nc,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: application/json\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"%s", jsonList.dump().data());
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::FileHandler(mg_connection *nc, int ev, void *ev_data)
|
|
||||||
{
|
|
||||||
// Only handle http requests
|
|
||||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
||||||
|
|
||||||
http_message* message = reinterpret_cast<http_message*>(ev_data);
|
|
||||||
|
|
||||||
//if (!Download::VerifyPassword(nc, message)) return;
|
|
||||||
|
|
||||||
// if (!Download::IsClient(nc))
|
|
||||||
// {
|
|
||||||
// Download::Forbid(nc);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
{
|
|
||||||
std::string url(message->uri.p, message->uri.len);
|
|
||||||
Utils::String::Replace(url, "\\", "/");
|
|
||||||
|
|
||||||
if (url.size() >= 6)
|
|
||||||
{
|
|
||||||
url = url.substr(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::String::Replace(url, "%20", " ");
|
|
||||||
|
|
||||||
bool isMap = false;
|
|
||||||
if (Utils::String::StartsWith(url, "map/"))
|
|
||||||
{
|
|
||||||
isMap = true;
|
|
||||||
url = url.substr(4);
|
|
||||||
|
|
||||||
std::string mapname = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
|
||||||
|
|
||||||
bool isValidFile = false;
|
|
||||||
for (int i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
|
||||||
{
|
|
||||||
if (url == (mapname + Maps::UserMapFiles[i]))
|
|
||||||
{
|
|
||||||
isValidFile = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
|
|
||||||
{
|
|
||||||
Download::Forbid(nc);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
url = Utils::String::VA("usermaps\\%s\\%s", mapname.data(), url.data());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != nullptr)
|
|
||||||
{
|
|
||||||
Download::Forbid(nc);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string file;
|
|
||||||
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
|
|
||||||
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + (isMap ? "" : fsGame + "\\") + url;
|
|
||||||
|
|
||||||
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
|
|
||||||
{
|
|
||||||
mg_printf(nc,
|
|
||||||
"HTTP/1.1 404 Not Found\r\n"
|
|
||||||
"Content-Type: text/html\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"404 - Not Found %s", path.data());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mg_printf(nc,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: application/octet-stream\r\n"
|
|
||||||
"Content-Length: %d\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n", file.size());
|
|
||||||
|
|
||||||
mg_send(nc, file.data(), static_cast<int>(file.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::InfoHandler(mg_connection* nc, int ev, void* /*ev_data*/)
|
|
||||||
{
|
|
||||||
// Only handle http requests
|
|
||||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
||||||
|
|
||||||
//if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
|
|
||||||
|
|
||||||
Utils::InfoString status = ServerInfo::GetInfo();
|
|
||||||
Utils::InfoString host = ServerInfo::GetHostInfo();
|
|
||||||
|
|
||||||
std::map<std::string, nlohmann::json> info;
|
|
||||||
info["status"] = status.to_json();
|
info["status"] = status.to_json();
|
||||||
info["host"] = host.to_json();
|
info["host"] = host.to_json();
|
||||||
|
|
||||||
std::vector<nlohmann::json> players;
|
std::vector<nlohmann::json> players;
|
||||||
|
|
||||||
// Build player list
|
// Build player list
|
||||||
for (int i = 0; i < atoi(status.get("sv_maxclients").data()); ++i) // Maybe choose 18 here?
|
for (auto i = 0; i < Game::MAX_CLIENTS; ++i)
|
||||||
{
|
{
|
||||||
std::map<std::string, nlohmann::json> playerInfo;
|
std::unordered_map<std::string, nlohmann::json> playerInfo;
|
||||||
playerInfo["score"] = 0;
|
playerInfo["score"] = 0;
|
||||||
playerInfo["ping"] = 0;
|
playerInfo["ping"] = 0;
|
||||||
playerInfo["name"] = "";
|
playerInfo["name"] = "";
|
||||||
|
|
||||||
if ((*Game::com_sv_running)->current.enabled)
|
if (Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
||||||
|
|
||||||
@ -760,62 +434,174 @@ namespace Components
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Score and ping are irrelevant
|
// Score and ping are irrelevant
|
||||||
const char* namePtr = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
|
const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
|
||||||
if (!namePtr || !namePtr[0]) continue;
|
if (!name || *name == '\0') continue;
|
||||||
|
|
||||||
playerInfo["name"] = namePtr;
|
playerInfo["name"] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
players.emplace_back(playerInfo);
|
players.emplace_back(playerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
info["players"] = players;
|
info["players"] = players;
|
||||||
|
return {nlohmann::json(info).dump()};
|
||||||
mg_printf(nc,
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"Content-Type: application/json\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"Access-Control-Allow-Origin: *\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"%s", nlohmann::json(info).dump().data());
|
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::EventHandler(mg_connection *nc, int ev, void *ev_data)
|
static std::string ListHandler()
|
||||||
{
|
{
|
||||||
// Only handle http requests
|
static nlohmann::json jsonList;
|
||||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
static auto handled = false;
|
||||||
|
|
||||||
http_message* message = reinterpret_cast<http_message*>(ev_data);
|
const std::string fs_gameDirVar = (*Game::fs_gameDirVar)->current.string;
|
||||||
|
|
||||||
// if (message->uri.p, message->uri.len == "/"s)
|
if (!fs_gameDirVar.empty() && !handled)
|
||||||
// {
|
|
||||||
// mg_printf(nc,
|
|
||||||
// "HTTP/1.1 200 OK\r\n"
|
|
||||||
// "Content-Type: text/html\r\n"
|
|
||||||
// "Connection: close\r\n"
|
|
||||||
// "\r\n"
|
|
||||||
// "Hi fella!<br>You are%s connected to this server!", (Download::IsClient(nc) ? " " : " not"));
|
|
||||||
//
|
|
||||||
// Game::client_t* client = Download::GetClient(nc);
|
|
||||||
//
|
|
||||||
// if (client)
|
|
||||||
// {
|
|
||||||
// mg_printf(nc, "<br>Hello %s!", client->name);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
{
|
{
|
||||||
//std::string path = (Dvar::Var("fs_basepath").get<std::string>() + "\\" BASEGAME "\\html");
|
handled = true;
|
||||||
//mg_serve_http_opts opts = { 0 };
|
|
||||||
//opts.document_root = path.data();
|
|
||||||
//mg_serve_http(nc, message, opts);
|
|
||||||
|
|
||||||
|
std::vector<nlohmann::json> fileList;
|
||||||
|
|
||||||
|
const auto path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + fs_gameDirVar;
|
||||||
|
auto list = FileSystem::GetSysFileList(path, "iwd", false);
|
||||||
|
list.emplace_back("mod.ff");
|
||||||
|
|
||||||
|
for (const auto& file : list)
|
||||||
|
{
|
||||||
|
std::string filename = path + "\\" + file;
|
||||||
|
if (file.find("_svr_") == std::string::npos)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, nlohmann::json> jsonFileList;
|
||||||
|
std::string fileBuffer = Utils::IO::ReadFile(filename);
|
||||||
|
if (fileBuffer.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFileList["name"] = file;
|
||||||
|
jsonFileList["size"] = fileBuffer.size();
|
||||||
|
jsonFileList["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
||||||
|
|
||||||
|
fileList.emplace_back(jsonFileList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonList = fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {jsonList.dump()};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string MapHandler()
|
||||||
|
{
|
||||||
|
static std::string mapNamePre;
|
||||||
|
static nlohmann::json jsonList;
|
||||||
|
|
||||||
|
const auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
||||||
|
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
|
||||||
|
{
|
||||||
|
mapNamePre.clear();
|
||||||
|
jsonList = {};
|
||||||
|
}
|
||||||
|
else if (!mapName.empty() && mapName != mapNamePre)
|
||||||
|
{
|
||||||
|
std::vector<nlohmann::json> fileList;
|
||||||
|
|
||||||
|
mapNamePre = mapName;
|
||||||
|
|
||||||
|
const auto path = Dvar::Var("fs_basepath").get<std::string>() + "\\usermaps\\" + mapName;
|
||||||
|
|
||||||
|
for (auto i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
||||||
|
{
|
||||||
|
const auto filename = path + "\\" + mapName + Maps::UserMapFiles[i];
|
||||||
|
|
||||||
|
std::map<std::string, nlohmann::json> file;
|
||||||
|
std::string fileBuffer = Utils::IO::ReadFile(filename);
|
||||||
|
if (fileBuffer.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
file["name"] = mapName + Maps::UserMapFiles[i];
|
||||||
|
file["size"] = fileBuffer.size();
|
||||||
|
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
||||||
|
|
||||||
|
fileList.emplace_back(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonList = fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {jsonList.dump()};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FileHandler(mg_connection* c, const mg_http_message* hm)
|
||||||
|
{
|
||||||
|
std::string url(hm->uri.ptr, hm->uri.len);
|
||||||
|
|
||||||
|
Utils::String::Replace(url, "\\", "/");
|
||||||
|
|
||||||
|
// Strip /file
|
||||||
|
url = url.substr(6);
|
||||||
|
Utils::String::Replace(url, "%20", " ");
|
||||||
|
|
||||||
|
auto isMap = false;
|
||||||
|
if (url.starts_with("map/"))
|
||||||
|
{
|
||||||
|
isMap = true;
|
||||||
|
url = url.substr(4);
|
||||||
|
|
||||||
|
auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
||||||
|
auto isValidFile = false;
|
||||||
|
for (auto i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
||||||
|
{
|
||||||
|
if (url == (mapName + Maps::UserMapFiles[i]))
|
||||||
|
{
|
||||||
|
isValidFile = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
|
||||||
|
{
|
||||||
|
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
url = std::format("usermaps\\{}\\{}", mapName, url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos)
|
||||||
|
{
|
||||||
|
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n");
|
||||||
|
mg_printf(c, "%s", "Content-Type: application/octet-stream\r\n");
|
||||||
|
mg_printf(c, "Content-Length: %d\r\n", static_cast<int>(file.size()));
|
||||||
|
mg_printf(c, "%s", "Connection: close\r\n");
|
||||||
|
mg_printf(c, "%s", "\r\n");
|
||||||
|
mg_send(c, file.data(), file.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HTMLHandler(mg_connection* c, mg_http_message* hm)
|
||||||
|
{
|
||||||
|
auto url = "html" + std::string(hm->uri.ptr, hm->uri.len);
|
||||||
FileSystem::File file;
|
FileSystem::File file;
|
||||||
std::string url = "html" + std::string(message->uri.p, message->uri.len);
|
|
||||||
|
|
||||||
if (Utils::String::EndsWith(url, "/"))
|
if (url.ends_with("/"))
|
||||||
{
|
{
|
||||||
url.append("index.html");
|
url.append("index.html");
|
||||||
file = FileSystem::File(url);
|
file = FileSystem::File(url);
|
||||||
@ -823,7 +609,6 @@ namespace Components
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
file = FileSystem::File(url);
|
file = FileSystem::File(url);
|
||||||
|
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
{
|
{
|
||||||
url.append("/index.html");
|
url.append("/index.html");
|
||||||
@ -831,62 +616,72 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mimeType = Utils::GetMimeType(url);
|
const auto mimeType = Utils::GetMimeType(url);
|
||||||
|
|
||||||
if (file.exists())
|
if (file.exists())
|
||||||
{
|
{
|
||||||
std::string buffer = file.getBuffer();
|
mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n");
|
||||||
|
mg_printf(c, "Content-Type: %s\r\n", mimeType.data());
|
||||||
mg_printf(nc,
|
mg_printf(c, "Content-Length: %d\r\n", static_cast<int>(file.getBuffer().size()));
|
||||||
"HTTP/1.1 200 OK\r\n"
|
mg_printf(c, "%s", "Connection: close\r\n");
|
||||||
"Content-Type: %s\r\n"
|
mg_printf(c, "%s", "\r\n");
|
||||||
"Content-Length: %d\r\n"
|
mg_send(c, file.getBuffer().data(), file.getBuffer().size());
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n", mimeType.data(), buffer.size());
|
|
||||||
|
|
||||||
mg_send(nc, buffer.data(), static_cast<int>(buffer.size()));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mg_printf(nc,
|
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found");
|
||||||
"HTTP/1.1 404 Not Found\r\n"
|
|
||||||
"Content-Type: text/html\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"404 - Not Found");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
static void EventHandler(mg_connection* c, int ev, void* ev_data, [[maybe_unused]] void* fn_data)
|
||||||
|
{
|
||||||
|
if (ev != MG_EV_HTTP_MSG)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* hm = static_cast<mg_http_message*>(ev_data);
|
||||||
|
std::string url(hm->uri.ptr, hm->uri.len);
|
||||||
|
|
||||||
|
if (url.starts_with("/info"))
|
||||||
|
{
|
||||||
|
const auto reply = InfoHandler();
|
||||||
|
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
|
||||||
|
}
|
||||||
|
else if (url.starts_with("/list"))
|
||||||
|
{
|
||||||
|
const auto reply = ListHandler();
|
||||||
|
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
|
||||||
|
}
|
||||||
|
else if (url.starts_with("/map"))
|
||||||
|
{
|
||||||
|
const auto reply = MapHandler();
|
||||||
|
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
|
||||||
|
}
|
||||||
|
else if (url.starts_with("/file"))
|
||||||
|
{
|
||||||
|
FileHandler(c, hm);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HTMLHandler(c, hm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
Download::Download()
|
Download::Download()
|
||||||
{
|
{
|
||||||
if (Dedicated::IsEnabled() /*|| Dvar::Var("mod_force_download_server").get<bool>()*/)
|
if (Dedicated::IsEnabled())
|
||||||
{
|
{
|
||||||
ZeroMemory(&Download::Mgr, sizeof Download::Mgr);
|
mg_mgr_init(&Mgr);
|
||||||
mg_mgr_init(&Download::Mgr, nullptr);
|
|
||||||
|
|
||||||
Network::OnStart([]()
|
Network::OnStart([]
|
||||||
{
|
{
|
||||||
mg_connection* nc = mg_bind(&Download::Mgr, Utils::String::VA("%hu", Network::GetPort()), Download::EventHandler);
|
const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr);
|
||||||
|
if (!nc)
|
||||||
if (nc)
|
|
||||||
{
|
{
|
||||||
// Handle special requests
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n");
|
||||||
mg_register_http_endpoint(nc, "/info", Download::InfoHandler);
|
|
||||||
mg_register_http_endpoint(nc, "/list", Download::ListHandler);
|
|
||||||
mg_register_http_endpoint(nc, "/map", Download::MapHandler);
|
|
||||||
mg_register_http_endpoint(nc, "/file/", Download::FileHandler);
|
|
||||||
mg_register_http_endpoint(nc, "/serverlist", Download::ServerlistHandler);
|
|
||||||
|
|
||||||
mg_set_protocol_http_websocket(nc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::Print("Failed to bind TCP socket, moddownload won't work!\n");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -896,7 +691,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
while (!Download::Terminate)
|
while (!Download::Terminate)
|
||||||
{
|
{
|
||||||
mg_mgr_poll(&Download::Mgr, 100);
|
mg_mgr_poll(&Mgr, 100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -917,13 +712,8 @@ namespace Components
|
|||||||
|
|
||||||
Scheduler::Once([]
|
Scheduler::Once([]
|
||||||
{
|
{
|
||||||
Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_ARCHIVE, "Set to true to enable downloading maps/mods from an external server.");
|
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_ARCHIVE, "Set to the base url for the external map download.");
|
Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
|
||||||
|
|
||||||
// Force users to enable this because we don't want to accidentally turn everyone's pc into a http server into all their files again
|
|
||||||
// not saying we are but ya know... accidents happen
|
|
||||||
// by having it saved we force the user to enable it in config_mp because it only checks the dvar on startup to see if we should init download or not
|
|
||||||
Dvar::Register<bool>("mod_force_download_server", false, Game::DVAR_ARCHIVE, "Set to true to force the client to run the download server for mods (for mods in private matches).");
|
|
||||||
}, Scheduler::Pipeline::MAIN);
|
}, Scheduler::Pipeline::MAIN);
|
||||||
|
|
||||||
Script::AddFunction("HttpGet", Script::ShowDeprecationWarning);
|
Script::AddFunction("HttpGet", Script::ShowDeprecationWarning);
|
||||||
@ -934,7 +724,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
if (Download::ServerRunning)
|
if (Download::ServerRunning)
|
||||||
{
|
{
|
||||||
mg_mgr_free(&Download::Mgr);
|
mg_mgr_free(&Mgr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <mongoose.h>
|
|
||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
@ -26,24 +25,23 @@ namespace Components
|
|||||||
bool terminateThread;
|
bool terminateThread;
|
||||||
bool isMap;
|
bool isMap;
|
||||||
bool isPrivate;
|
bool isPrivate;
|
||||||
//mg_mgr mgr;
|
|
||||||
Network::Address target;
|
Network::Address target;
|
||||||
std::string hashedPassword;
|
std::string hashedPassword;
|
||||||
std::string mod;
|
std::string mod;
|
||||||
std::thread thread;
|
std::thread thread;
|
||||||
|
|
||||||
size_t totalBytes;
|
std::size_t totalBytes;
|
||||||
size_t downBytes;
|
std::size_t downBytes;
|
||||||
|
|
||||||
int lastTimeStamp;
|
int lastTimeStamp;
|
||||||
size_t timeStampBytes;
|
std::size_t timeStampBytes;
|
||||||
|
|
||||||
class File
|
class File
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string hash;
|
std::string hash;
|
||||||
size_t size;
|
std::size_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<File> files;
|
std::vector<File> files;
|
||||||
@ -64,7 +62,6 @@ namespace Components
|
|||||||
if (this->valid)
|
if (this->valid)
|
||||||
{
|
{
|
||||||
this->valid = false;
|
this->valid = false;
|
||||||
//mg_mgr_free(&(this->mgr));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -79,30 +76,15 @@ namespace Components
|
|||||||
bool downloading;
|
bool downloading;
|
||||||
unsigned int index;
|
unsigned int index;
|
||||||
std::string buffer;
|
std::string buffer;
|
||||||
size_t receivedBytes;
|
std::size_t receivedBytes;
|
||||||
};
|
};
|
||||||
|
|
||||||
static mg_mgr Mgr;
|
|
||||||
static ClientDownload CLDownload;
|
static ClientDownload CLDownload;
|
||||||
static std::thread ServerThread;
|
static std::thread ServerThread;
|
||||||
static bool Terminate;
|
static bool Terminate;
|
||||||
static bool ServerRunning;
|
static bool ServerRunning;
|
||||||
|
|
||||||
static void DownloadProgress(FileDownload* fDownload, size_t bytes);
|
static void DownloadProgress(FileDownload* fDownload, std::size_t bytes);
|
||||||
|
|
||||||
static bool VerifyPassword(mg_connection *nc, http_message* message);
|
|
||||||
|
|
||||||
static void EventHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
static void ListHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
static void MapHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
static void ServerlistHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
static void FileHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
static void InfoHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
static void DownloadHandler(mg_connection *nc, int ev, void *ev_data);
|
|
||||||
|
|
||||||
static bool IsClient(mg_connection *nc);
|
|
||||||
static Game::client_t* GetClient(mg_connection *nc);
|
|
||||||
static void Forbid(mg_connection *nc);
|
|
||||||
|
|
||||||
static void ModDownloader(ClientDownload* download);
|
static void ModDownloader(ClientDownload* download);
|
||||||
static bool ParseModList(ClientDownload* download, const std::string& list);
|
static bool ParseModList(ClientDownload* download, const std::string& list);
|
||||||
|
@ -4,6 +4,8 @@ namespace Components
|
|||||||
{
|
{
|
||||||
const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg";
|
const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg";
|
||||||
|
|
||||||
|
Dvar::Var Dvar::Name;
|
||||||
|
|
||||||
Dvar::Var::Var(const std::string& dvarName)
|
Dvar::Var::Var(const std::string& dvarName)
|
||||||
{
|
{
|
||||||
this->dvar_ = Game::Dvar_FindVar(dvarName.data());
|
this->dvar_ = Game::Dvar_FindVar(dvarName.data());
|
||||||
@ -11,8 +13,7 @@ namespace Components
|
|||||||
// If the dvar can't be found it will be registered as an empty string dvar
|
// If the dvar can't be found it will be registered as an empty string dvar
|
||||||
if (this->dvar_ == nullptr)
|
if (this->dvar_ == nullptr)
|
||||||
{
|
{
|
||||||
this->dvar_ = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "",
|
this->dvar_ = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", Game::DVAR_SOURCE_INTERNAL));
|
||||||
Game::DvarSetSource::DVAR_SOURCE_INTERNAL));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,8 +27,7 @@ namespace Components
|
|||||||
if (this->dvar_ == nullptr)
|
if (this->dvar_ == nullptr)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
if (this->dvar_->type == Game::DVAR_TYPE_STRING
|
if (this->dvar_->type == Game::DVAR_TYPE_STRING || this->dvar_->type == Game::DVAR_TYPE_ENUM)
|
||||||
|| this->dvar_->type == Game::DVAR_TYPE_ENUM)
|
|
||||||
{
|
{
|
||||||
if (this->dvar_->current.string != nullptr)
|
if (this->dvar_->current.string != nullptr)
|
||||||
return this->dvar_->current.string;
|
return this->dvar_->current.string;
|
||||||
@ -82,8 +82,8 @@ namespace Components
|
|||||||
if (this->dvar_ == nullptr)
|
if (this->dvar_ == nullptr)
|
||||||
return vector;
|
return vector;
|
||||||
|
|
||||||
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3
|
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)
|
this->dvar_->type == Game::DVAR_TYPE_FLOAT_4)
|
||||||
{
|
{
|
||||||
return this->dvar_->current.vector;
|
return this->dvar_->current.vector;
|
||||||
}
|
}
|
||||||
@ -186,37 +186,37 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Dvar::Flag flag, const char* description)
|
template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Flag flag, const char* description)
|
||||||
{
|
{
|
||||||
return Game::Dvar_RegisterBool(dvarName, value, flag.val, description);
|
return Game::Dvar_RegisterBool(dvarName, value, flag.val, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Dvar::Flag flag, const char* description)
|
template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Flag flag, const char* description)
|
||||||
{
|
{
|
||||||
return Game::Dvar_RegisterString(dvarName, value, flag.val, description);
|
return Game::Dvar_RegisterString(dvarName, value, flag.val, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Dvar::Flag flag, const char* description)
|
template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Flag flag, const char* description)
|
||||||
{
|
{
|
||||||
return Game::Dvar_RegisterInt(dvarName, value, min, max, flag.val, description);
|
return Game::Dvar_RegisterInt(dvarName, value, min, max, flag.val, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Dvar::Flag flag, const char* description)
|
template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Flag flag, const char* description)
|
||||||
{
|
{
|
||||||
return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description);
|
return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dvar::ResetDvarsValue()
|
void Dvar::ResetDvarsValue()
|
||||||
{
|
{
|
||||||
if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath))
|
if (!Utils::IO::FileExists(ArchiveDvarPath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Command::Execute("exec archivedvars.cfg", true);
|
Command::Execute("exec archivedvars.cfg", true);
|
||||||
// Clean up
|
// Clean up
|
||||||
Utils::IO::RemoveFile(Dvar::ArchiveDvarPath);
|
Utils::IO::RemoveFile(ArchiveDvarPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, unsigned __int16 flags, const char* description)
|
Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, std::uint16_t flags, const char* description)
|
||||||
{
|
{
|
||||||
// Name watcher
|
// Name watcher
|
||||||
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
||||||
@ -224,7 +224,7 @@ namespace Components
|
|||||||
Scheduler::Loop([]
|
Scheduler::Loop([]
|
||||||
{
|
{
|
||||||
static std::string lastValidName = "Unknown Soldier";
|
static std::string lastValidName = "Unknown Soldier";
|
||||||
auto name = Dvar::Var("name").get<std::string>();
|
auto name = Name.get<std::string>();
|
||||||
|
|
||||||
// Don't perform any checks if name didn't change
|
// Don't perform any checks if name didn't change
|
||||||
if (name == lastValidName) return;
|
if (name == lastValidName) return;
|
||||||
@ -232,8 +232,8 @@ namespace Components
|
|||||||
std::string saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(Utils::String::Trim(name)));
|
std::string saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(Utils::String::Trim(name)));
|
||||||
if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{'))
|
if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{'))
|
||||||
{
|
{
|
||||||
Logger::Print("Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name);
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name);
|
||||||
Dvar::Var("name").set(lastValidName);
|
Name.set(lastValidName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -249,18 +249,19 @@ namespace Components
|
|||||||
{
|
{
|
||||||
const char* steamName = Steam::Proxy::SteamFriends->GetPersonaName();
|
const char* steamName = Steam::Proxy::SteamFriends->GetPersonaName();
|
||||||
|
|
||||||
if (steamName && !std::string(steamName).empty())
|
if (steamName && *steamName != '\0')
|
||||||
{
|
{
|
||||||
username = steamName;
|
username = steamName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Dvar::Register<const char*>(name, username.data(), flags | Game::DVAR_ARCHIVE, description).get<Game::dvar_t*>();
|
Name = Register<const char*>(name, username.data(), flags | Game::DVAR_ARCHIVE, description);
|
||||||
|
return Name.get<Game::dvar_t*>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string)
|
void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string)
|
||||||
{
|
{
|
||||||
static const char* exceptions[] =
|
static std::array<const char*, 8> exceptions =
|
||||||
{
|
{
|
||||||
"ui_showEndOfGame",
|
"ui_showEndOfGame",
|
||||||
"systemlink",
|
"systemlink",
|
||||||
@ -272,21 +273,21 @@ namespace Components
|
|||||||
"ui_mptype",
|
"ui_mptype",
|
||||||
};
|
};
|
||||||
|
|
||||||
for (std::size_t i = 0; i < ARRAYSIZE(exceptions); ++i)
|
for (const auto& entry : exceptions)
|
||||||
{
|
{
|
||||||
if (Utils::String::ToLower(dvarName) == Utils::String::ToLower(exceptions[i]))
|
if (Utils::String::Compare(dvarName, entry))
|
||||||
{
|
{
|
||||||
Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DvarSetSource::DVAR_SOURCE_INTERNAL);
|
Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_INTERNAL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dvar::SetFromStringByNameExternal(dvarName, string);
|
SetFromStringByNameExternal(dvarName, string);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dvar::SetFromStringByNameExternal(const char* dvarName, const char* string)
|
void Dvar::SetFromStringByNameExternal(const char* dvarName, const char* string)
|
||||||
{
|
{
|
||||||
Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DvarSetSource::DVAR_SOURCE_EXTERNAL);
|
Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_EXTERNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Dvar::AreArchiveDvarsProtected()
|
bool Dvar::AreArchiveDvarsProtected()
|
||||||
@ -303,23 +304,21 @@ namespace Components
|
|||||||
|
|
||||||
void Dvar::SaveArchiveDvar(const Game::dvar_t* var)
|
void Dvar::SaveArchiveDvar(const Game::dvar_t* var)
|
||||||
{
|
{
|
||||||
if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath))
|
if (!Utils::IO::FileExists(ArchiveDvarPath))
|
||||||
{
|
{
|
||||||
Utils::IO::WriteFile(Dvar::ArchiveDvarPath,
|
Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n");
|
||||||
"// generated by IW4x, do not modify\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::IO::WriteFile(Dvar::ArchiveDvarPath,
|
Utils::IO::WriteFile(ArchiveDvarPath, Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true);
|
||||||
Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dvar::DvarSetFromStringByNameStub(const char* dvarName, const char* value)
|
void Dvar::DvarSetFromStringByName_Stub(const char* dvarName, const char* value)
|
||||||
{
|
{
|
||||||
// Save the dvar original value if it has the archive flag
|
// Save the dvar original value if it has the archive flag
|
||||||
const auto* dvar = Game::Dvar_FindVar(dvarName);
|
const auto* dvar = Game::Dvar_FindVar(dvarName);
|
||||||
if (dvar != nullptr && dvar->flags & Game::DVAR_ARCHIVE)
|
if (dvar != nullptr && dvar->flags & Game::DVAR_ARCHIVE)
|
||||||
{
|
{
|
||||||
if (Dvar::AreArchiveDvarsProtected())
|
if (AreArchiveDvarsProtected())
|
||||||
{
|
{
|
||||||
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvarName);
|
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvarName);
|
||||||
return;
|
return;
|
||||||
@ -328,7 +327,7 @@ namespace Components
|
|||||||
#ifdef DEBUG_DVARS
|
#ifdef DEBUG_DVARS
|
||||||
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Server is overriding saved dvar '{}'\n", dvarName);
|
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Server is overriding saved dvar '{}'\n", dvarName);
|
||||||
#endif
|
#endif
|
||||||
Dvar::SaveArchiveDvar(dvar);
|
SaveArchiveDvar(dvar);
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::Hook::Call<void(const char*, const char*)>(0x4F52E0)(dvarName, value);
|
Utils::Hook::Call<void(const char*, const char*)>(0x4F52E0)(dvarName, value);
|
||||||
@ -348,7 +347,7 @@ namespace Components
|
|||||||
pushad
|
pushad
|
||||||
|
|
||||||
push eax
|
push eax
|
||||||
call Dvar::OnRegisterVariant
|
call OnRegisterVariant
|
||||||
add esp, 0x4
|
add esp, 0x4
|
||||||
|
|
||||||
popad
|
popad
|
||||||
@ -362,28 +361,45 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* Dvar::Dvar_EnumToString_Stub(const Game::dvar_t* dvar)
|
||||||
|
{
|
||||||
|
assert(dvar);
|
||||||
|
assert(dvar->name);
|
||||||
|
assert(dvar->type == Game::DVAR_TYPE_ENUM);
|
||||||
|
assert(dvar->domain.enumeration.strings);
|
||||||
|
assert(dvar->current.integer >= 0 && dvar->current.integer < dvar->domain.enumeration.stringCount || dvar->current.integer == 0);
|
||||||
|
|
||||||
|
// Fix nullptr crash
|
||||||
|
if (!dvar || dvar->domain.enumeration.stringCount == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dvar->domain.enumeration.strings[dvar->current.integer];
|
||||||
|
}
|
||||||
|
|
||||||
Dvar::Dvar()
|
Dvar::Dvar()
|
||||||
{
|
{
|
||||||
// set flags of cg_drawFPS to archive
|
// set flags of cg_drawFPS to archive
|
||||||
Utils::Hook::Or<BYTE>(0x4F8F69, Game::DVAR_ARCHIVE);
|
Utils::Hook::Or<std::uint8_t>(0x4F8F69, Game::DVAR_ARCHIVE);
|
||||||
|
|
||||||
// un-cheat camera_thirdPersonCrosshairOffset and add archive flags
|
// un-cheat camera_thirdPersonCrosshairOffset and add archive flags
|
||||||
Utils::Hook::Xor<BYTE>(0x447B41, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
Utils::Hook::Xor<std::uint8_t>(0x447B41, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
||||||
|
|
||||||
// un-cheat cg_fov and add archive flags
|
// un-cheat cg_fov and add archive flags
|
||||||
Utils::Hook::Xor<BYTE>(0x4F8E35, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
Utils::Hook::Xor<std::uint8_t>(0x4F8E35, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
||||||
|
|
||||||
// un-cheat cg_fovscale and add archive flags
|
// un-cheat cg_fovscale and add archive flags
|
||||||
Utils::Hook::Xor<BYTE>(0x4F8E68, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
Utils::Hook::Xor<std::uint8_t>(0x4F8E68, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
||||||
|
|
||||||
// un-cheat cg_debugInfoCornerOffset and add archive flags
|
// un-cheat cg_debugInfoCornerOffset and add archive flags
|
||||||
Utils::Hook::Xor<BYTE>(0x4F8FC2, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
Utils::Hook::Xor<std::uint8_t>(0x4F8FC2, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE);
|
||||||
|
|
||||||
// remove archive flags for cg_hudchatposition
|
// remove archive flags for cg_hudchatposition
|
||||||
Utils::Hook::Xor<BYTE>(0x4F9992, Game::DVAR_ARCHIVE);
|
Utils::Hook::Xor<std::uint8_t>(0x4F9992, Game::DVAR_ARCHIVE);
|
||||||
|
|
||||||
// remove write protection from fs_game
|
// remove write protection from fs_game
|
||||||
Utils::Hook::Xor<DWORD>(0x6431EA, Game::DVAR_INIT);
|
Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT);
|
||||||
|
|
||||||
// set cg_fov max to 160.0
|
// set cg_fov max to 160.0
|
||||||
// because that's the max on SP
|
// because that's the max on SP
|
||||||
@ -395,57 +411,57 @@ namespace Components
|
|||||||
Utils::Hook::Set<float*>(0x408078, &volume);
|
Utils::Hook::Set<float*>(0x408078, &volume);
|
||||||
|
|
||||||
// Uncheat ui_showList
|
// Uncheat ui_showList
|
||||||
Utils::Hook::Xor<BYTE>(0x6310DC, Game::DVAR_CHEAT);
|
Utils::Hook::Xor<std::uint8_t>(0x6310DC, Game::DVAR_CHEAT);
|
||||||
|
|
||||||
// Uncheat ui_debugMode
|
// Uncheat ui_debugMode
|
||||||
Utils::Hook::Xor<BYTE>(0x6312DE, Game::DVAR_CHEAT);
|
Utils::Hook::Xor<std::uint8_t>(0x6312DE, Game::DVAR_CHEAT);
|
||||||
|
|
||||||
// Hook dvar 'name' registration
|
// Hook dvar 'name' registration
|
||||||
Utils::Hook(0x40531C, Dvar::Dvar_RegisterName, HOOK_CALL).install()->quick();
|
Utils::Hook(0x40531C, Dvar_RegisterName, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// un-cheat safeArea_* and add archive flags
|
// un-cheat safeArea_* and add archive flags
|
||||||
Utils::Hook::Xor<INT>(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal
|
Utils::Hook::Xor<std::uint32_t>(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal
|
||||||
Utils::Hook::Xor<INT>(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical
|
Utils::Hook::Xor<std::uint32_t>(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical
|
||||||
Utils::Hook::Xor<BYTE>(0x42E398, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_horizontal
|
Utils::Hook::Xor<std::uint8_t>(0x42E398, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_horizontal
|
||||||
Utils::Hook::Xor<BYTE>(0x42E3C4, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_vertical
|
Utils::Hook::Xor<std::uint8_t>(0x42E3C4, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_vertical
|
||||||
|
|
||||||
// Don't allow setting cheat protected dvars via menus
|
// Don't allow setting cheat protected dvars via menus
|
||||||
Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x63C897, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x63CA96, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x63CA96, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x63CDB5, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x63CDB5, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x635E47, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x635E47, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// Script_SetDvar
|
// Script_SetDvar
|
||||||
Utils::Hook(0x63444C, Dvar::SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x63444C, SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// Slider
|
// Slider
|
||||||
Utils::Hook(0x636159, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x636159, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x636189, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x636189, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x6364EA, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x6364EA, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
Utils::Hook(0x636207, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x636207, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x636608, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x636608, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
Utils::Hook(0x636695, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
Utils::Hook(0x636695, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// Entirely block setting cheat dvars internally without sv_cheats
|
|
||||||
//Utils::Hook(0x4F52EC, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
|
||||||
|
|
||||||
// Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server
|
// Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server
|
||||||
Utils::Hook(0x59386A, Dvar::DvarSetFromStringByNameStub, HOOK_CALL).install()->quick();
|
Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
// If the game closed abruptly, the dvars would not have been restored
|
// If the game closed abruptly, the dvars would not have been restored
|
||||||
Scheduler::Once(Dvar::ResetDvarsValue, Scheduler::Pipeline::MAIN);
|
Scheduler::Once(ResetDvarsValue, Scheduler::Pipeline::MAIN);
|
||||||
|
|
||||||
// Reset archive dvars when client leaves a server
|
// Reset archive dvars when client leaves a server
|
||||||
Events::OnSteamDisconnect(Dvar::ResetDvarsValue);
|
Events::OnSteamDisconnect(ResetDvarsValue);
|
||||||
|
|
||||||
// For debugging
|
// For debugging
|
||||||
Utils::Hook(0x6483FA, Dvar::Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x6483FA, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
||||||
Utils::Hook(0x648438, Dvar::Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x648438, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
|
// Fix crash
|
||||||
|
Utils::Hook(0x4B7120, Dvar_EnumToString_Stub, HOOK_JUMP).install()->quick();
|
||||||
}
|
}
|
||||||
|
|
||||||
Dvar::~Dvar()
|
Dvar::~Dvar()
|
||||||
{
|
{
|
||||||
Utils::IO::RemoveFile(Dvar::ArchiveDvarPath);
|
Utils::IO::RemoveFile(ArchiveDvarPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Flag(Game::DvarFlags flag) : val(flag) {}
|
Flag(Game::DvarFlags flag) : val(flag) {}
|
||||||
Flag(unsigned __int16 flag) : Flag(static_cast<Game::DvarFlags>(flag)) {}
|
Flag(std::uint16_t flag) : Flag(static_cast<Game::DvarFlags>(flag)) {}
|
||||||
|
|
||||||
Game::DvarFlags val;
|
Game::DvarFlags val;
|
||||||
};
|
};
|
||||||
@ -51,17 +51,20 @@ namespace Components
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static const char* ArchiveDvarPath;
|
static const char* ArchiveDvarPath;
|
||||||
|
static Var Name;
|
||||||
|
|
||||||
static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, unsigned __int16 flags, const char* description);
|
static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, std::uint16_t flags, const char* description);
|
||||||
|
|
||||||
static void SetFromStringByNameExternal(const char* dvar, const char* value);
|
static void SetFromStringByNameExternal(const char* dvarName, const char* string);
|
||||||
static void SetFromStringByNameSafeExternal(const char* dvar, const char* value);
|
static void SetFromStringByNameSafeExternal(const char* dvarName, const char* string);
|
||||||
|
|
||||||
static bool AreArchiveDvarsProtected();
|
static bool AreArchiveDvarsProtected();
|
||||||
static void SaveArchiveDvar(const Game::dvar_t* var);
|
static void SaveArchiveDvar(const Game::dvar_t* var);
|
||||||
static void DvarSetFromStringByNameStub(const char* dvarName, const char* value);
|
static void DvarSetFromStringByName_Stub(const char* dvarName, const char* value);
|
||||||
|
|
||||||
static void OnRegisterVariant(Game::dvar_t* dvar);
|
static void OnRegisterVariant(Game::dvar_t* dvar);
|
||||||
static void Dvar_RegisterVariant_Stub();
|
static void Dvar_RegisterVariant_Stub();
|
||||||
|
|
||||||
|
static const char* Dvar_EnumToString_Stub(const Game::dvar_t* dvar);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
#include <STDInclude.hpp>
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
void FrameTime::NetSleep(int msec)
|
|
||||||
{
|
|
||||||
if (msec < 0) msec = 0;
|
|
||||||
|
|
||||||
fd_set fdr;
|
|
||||||
FD_ZERO(&fdr);
|
|
||||||
|
|
||||||
SOCKET highestfd = INVALID_SOCKET;
|
|
||||||
if (*Game::ip_socket != INVALID_SOCKET)
|
|
||||||
{
|
|
||||||
FD_SET(*Game::ip_socket, &fdr);
|
|
||||||
highestfd = *Game::ip_socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highestfd == INVALID_SOCKET)
|
|
||||||
{
|
|
||||||
// windows ain't happy when select is called without valid FDs
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(msec));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeval timeout;
|
|
||||||
timeout.tv_sec = msec / 1000;
|
|
||||||
timeout.tv_usec = (msec % 1000) * 1000;
|
|
||||||
|
|
||||||
int retval = select(highestfd + 1, &fdr, nullptr, nullptr, &timeout);
|
|
||||||
|
|
||||||
if (retval == SOCKET_ERROR)
|
|
||||||
{
|
|
||||||
Logger::Warning(Game::CON_CHANNEL_SYSTEM, "Select() syscall failed: {}\n", Game::NET_ErrorString());
|
|
||||||
}
|
|
||||||
else if (retval > 0)
|
|
||||||
{
|
|
||||||
// process packets
|
|
||||||
if (Dvar::Var(0x1AD7934).get<bool>()) // com_sv_running
|
|
||||||
{
|
|
||||||
Utils::Hook::Call<void()>(0x458160)();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Utils::Hook::Call<void()>(0x49F0B0)();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FrameTime::SVFrameWaitFunc()
|
|
||||||
{
|
|
||||||
int sv_residualTime = *reinterpret_cast<int*>(0x2089E14);
|
|
||||||
|
|
||||||
if (sv_residualTime < 50)
|
|
||||||
{
|
|
||||||
FrameTime::NetSleep(50 - sv_residualTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void __declspec(naked) FrameTime::SVFrameWaitStub()
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
{
|
|
||||||
pushad
|
|
||||||
call FrameTime::SVFrameWaitFunc
|
|
||||||
popad
|
|
||||||
|
|
||||||
push 4CD420h
|
|
||||||
retn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int FrameTime::ComTimeVal(int minMsec)
|
|
||||||
{
|
|
||||||
int timeVal = Game::Sys_Milliseconds() - *reinterpret_cast<uint32_t*>(0x1AD8F3C); // com_frameTime
|
|
||||||
return (timeVal >= minMsec ? 0 : minMsec - timeVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t FrameTime::ComFrameWait(int minMsec)
|
|
||||||
{
|
|
||||||
int timeVal;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
timeVal = FrameTime::ComTimeVal(minMsec);
|
|
||||||
FrameTime::NetSleep(timeVal < 1 ? 0 : timeVal - 1);
|
|
||||||
} while (FrameTime::ComTimeVal(minMsec));
|
|
||||||
|
|
||||||
uint32_t lastTime = *Game::com_frameTime;
|
|
||||||
Utils::Hook::Call<void()>(0x43D140)(); // Com_EventLoop
|
|
||||||
*Game::com_frameTime = Game::Sys_Milliseconds();
|
|
||||||
|
|
||||||
return *Game::com_frameTime - lastTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
void __declspec(naked) FrameTime::ComFrameWaitStub()
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
{
|
|
||||||
push ecx
|
|
||||||
pushad
|
|
||||||
|
|
||||||
push edi
|
|
||||||
call FrameTime::ComFrameWait
|
|
||||||
add esp, 4
|
|
||||||
|
|
||||||
mov [esp + 20h], eax
|
|
||||||
popad
|
|
||||||
pop eax
|
|
||||||
mov ecx, eax
|
|
||||||
|
|
||||||
mov edx, 1AD7934h // com_sv_running
|
|
||||||
cmp byte ptr[edx + 10h], 0
|
|
||||||
|
|
||||||
push 47DDC1h
|
|
||||||
retn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameTime::FrameTime()
|
|
||||||
{
|
|
||||||
if (Dedicated::IsEnabled())
|
|
||||||
{
|
|
||||||
Utils::Hook(0x4BAAAD, FrameTime::SVFrameWaitStub, HOOK_CALL).install()->quick();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Utils::Hook(0x47DD80, FrameTime::ComFrameWaitStub, HOOK_JUMP).install()->quick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
class FrameTime : public Component
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
FrameTime();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void SVFrameWaitStub();
|
|
||||||
static void SVFrameWaitFunc();
|
|
||||||
|
|
||||||
static void NetSleep(int msec);
|
|
||||||
|
|
||||||
static int ComTimeVal(int minMsec);
|
|
||||||
static uint32_t ComFrameWait(int minMsec);
|
|
||||||
static void ComFrameWaitStub();
|
|
||||||
};
|
|
||||||
}
|
|
@ -555,7 +555,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Friends::LoggedOn = false;
|
Friends::LoggedOn = false;
|
||||||
|
|
||||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled())
|
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Friends::UIStreamFriendly = Dvar::Register<bool>("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI");
|
Friends::UIStreamFriendly = Dvar::Register<bool>("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI");
|
||||||
@ -720,7 +720,7 @@ namespace Components
|
|||||||
|
|
||||||
Friends::~Friends()
|
Friends::~Friends()
|
||||||
{
|
{
|
||||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) return;
|
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
||||||
|
|
||||||
Friends::StoreFriendsList();
|
Friends::StoreFriendsList();
|
||||||
|
|
||||||
|
@ -106,6 +106,31 @@ namespace Components
|
|||||||
|
|
||||||
Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists());
|
Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Script::AddFunction("FileRemove", [] // gsc: FileRemove(<filepath>)
|
||||||
|
{
|
||||||
|
const auto* path = Game::Scr_GetString(0);
|
||||||
|
|
||||||
|
if (path == nullptr)
|
||||||
|
{
|
||||||
|
Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
|
||||||
|
{
|
||||||
|
if (std::strstr(path, QueryStrings[i]) != nullptr)
|
||||||
|
{
|
||||||
|
Logger::Print("^1FileRemove: directory traversal is not allowed!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto p = "scriptdata" / std::filesystem::path(path);
|
||||||
|
const auto folder = p.parent_path().string();
|
||||||
|
const auto file = p.filename().string();
|
||||||
|
Game::Scr_AddInt(FileSystem::_DeleteFile(folder, file));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
IO::IO()
|
IO::IO()
|
||||||
|
@ -72,15 +72,15 @@ namespace Components
|
|||||||
const auto* op = Game::Scr_GetString(1);
|
const auto* op = Game::Scr_GetString(1);
|
||||||
const auto b = GetInt64Arg(2, true);
|
const auto b = GetInt64Arg(2, true);
|
||||||
|
|
||||||
if (const auto got = Operations.find(op); got != Operations.end())
|
if (const auto itr = Operations.find(op); itr != Operations.end())
|
||||||
{
|
{
|
||||||
Game::Scr_AddString(Utils::String::VA("%lld", got->second(a, b)));
|
Game::Scr_AddString(Utils::String::VA("%lld", itr->second(a, b)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto got = Comparisons.find(op); got != Comparisons.end())
|
if (const auto itr = Comparisons.find(op); itr != Comparisons.end())
|
||||||
{
|
{
|
||||||
Game::Scr_AddBool(got->second(a, b));
|
Game::Scr_AddBool(itr->second(a, b));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,14 @@ namespace Components
|
|||||||
std::vector<int> Script::ScriptMainHandles;
|
std::vector<int> Script::ScriptMainHandles;
|
||||||
std::vector<int> Script::ScriptInitHandles;
|
std::vector<int> Script::ScriptInitHandles;
|
||||||
|
|
||||||
|
void Script::ShowDeprecationWarning()
|
||||||
|
{
|
||||||
|
Toast::Show("cardicon_gumby", "WARNING!", "You are using deprecated HttpGet/HttpCancel GSC function.", 2048);
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SCRIPT, "*** DEPRECATION WARNING ***\n");
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Attempted to execute deprecated built-in HttpGet/HttpCancel! These functions have been deemed unsafe and are scheduled for removal. Please update your mod!\n");
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SCRIPT, "***************************\n");
|
||||||
|
}
|
||||||
|
|
||||||
void Script::FunctionError()
|
void Script::FunctionError()
|
||||||
{
|
{
|
||||||
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName);
|
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName);
|
||||||
@ -297,13 +305,11 @@ namespace Components
|
|||||||
{
|
{
|
||||||
if (pName != nullptr)
|
if (pName != nullptr)
|
||||||
{
|
{
|
||||||
const auto got = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName));
|
|
||||||
|
|
||||||
// If no function was found let's call game's function
|
// If no function was found let's call game's function
|
||||||
if (got != Script::CustomScrFunctions.end())
|
if (const auto itr = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrFunctions.end())
|
||||||
{
|
{
|
||||||
*type = got->second.type;
|
*type = itr->second.type;
|
||||||
return got->second.actionFunc;
|
return itr->second.actionFunc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -321,13 +327,11 @@ namespace Components
|
|||||||
{
|
{
|
||||||
if (pName != nullptr)
|
if (pName != nullptr)
|
||||||
{
|
{
|
||||||
const auto got = Script::CustomScrMethods.find(Utils::String::ToLower(*pName));
|
|
||||||
|
|
||||||
// If no method was found let's call game's function
|
// If no method was found let's call game's function
|
||||||
if (got != Script::CustomScrMethods.end())
|
if (const auto itr = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrMethods.end())
|
||||||
{
|
{
|
||||||
*type = got->second.type;
|
*type = itr->second.type;
|
||||||
return got->second.actionFunc;
|
return itr->second.actionFunc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -437,7 +441,7 @@ namespace Components
|
|||||||
|
|
||||||
const auto* value = &Game::scrVmPub->top[-index];
|
const auto* value = &Game::scrVmPub->top[-index];
|
||||||
|
|
||||||
if (value->type != Game::scrParamType_t::VAR_FUNCTION)
|
if (value->type != Game::VAR_FUNCTION)
|
||||||
{
|
{
|
||||||
Game::Scr_ParamError(static_cast<unsigned int>(index), "^1GetCodePosForParam: Expects a function as parameter!\n");
|
Game::Scr_ParamError(static_cast<unsigned int>(index), "^1GetCodePosForParam: Expects a function as parameter!\n");
|
||||||
return "";
|
return "";
|
||||||
@ -534,14 +538,6 @@ namespace Components
|
|||||||
return &Game::svs_clients[ent->s.number];
|
return &Game::svs_clients[ent->s.number];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Script::ShowDeprecationWarning()
|
|
||||||
{
|
|
||||||
Toast::Show("cardicon_gumby", "WARNING!", "You are using deprecated HttpGet/HttpCancel GSC function.", 2048);
|
|
||||||
Logger::Print(Game::CON_CHANNEL_SCRIPT, "*** DEPRECATION WARNING ***\n");
|
|
||||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Attempted to execute deprecated built-in HttpGet/HttpCancel! These functions have been deemed unsafe and are scheduled for removal. Please update your mod!\n");
|
|
||||||
Logger::Print(Game::CON_CHANNEL_SCRIPT, "***************************\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Script::AddFunctions()
|
void Script::AddFunctions()
|
||||||
{
|
{
|
||||||
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
|
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
|
||||||
|
@ -51,11 +51,9 @@ namespace Components
|
|||||||
if (classnum == Game::ClassNum::CLASS_NUM_ENTITY)
|
if (classnum == Game::ClassNum::CLASS_NUM_ENTITY)
|
||||||
{
|
{
|
||||||
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
||||||
|
if (const auto itr = CustomEntityFields.find(entity_offset); itr != CustomEntityFields.end())
|
||||||
const auto got =CustomEntityFields.find(entity_offset);
|
|
||||||
if (got != CustomEntityFields.end())
|
|
||||||
{
|
{
|
||||||
got->second.setter(&Game::g_entities[entnum], offset);
|
itr->second.setter(&Game::g_entities[entnum], offset);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,11 +66,9 @@ namespace Components
|
|||||||
void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset)
|
void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset)
|
||||||
{
|
{
|
||||||
const auto client_offset = static_cast<std::uint16_t>(offset);
|
const auto client_offset = static_cast<std::uint16_t>(offset);
|
||||||
|
if (const auto itr = CustomClientFields.find(client_offset); itr != CustomClientFields.end())
|
||||||
const auto got = CustomClientFields.find(client_offset);
|
|
||||||
if (got != CustomClientFields.end())
|
|
||||||
{
|
{
|
||||||
got->second.setter(client, &got->second);
|
itr->second.setter(client, &itr->second);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,12 +84,10 @@ namespace Components
|
|||||||
if (Game::g_entities[entnum].client != nullptr)
|
if (Game::g_entities[entnum].client != nullptr)
|
||||||
{
|
{
|
||||||
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
|
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
|
||||||
|
if (const auto itr = CustomClientFields.find(client_offset); itr != CustomClientFields.end())
|
||||||
const auto got =CustomClientFields.find(client_offset);
|
|
||||||
if (got != CustomClientFields.end())
|
|
||||||
{
|
{
|
||||||
// Game functions probably don't ever need to use the reference to client_fields_s...
|
// Game functions probably don't ever need to use the reference to client_fields_s...
|
||||||
got->second.getter(Game::g_entities[entnum].client, &got->second);
|
itr->second.getter(Game::g_entities[entnum].client, &itr->second);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,10 +96,9 @@ namespace Components
|
|||||||
// Regular entity offsets can be searched directly in our custom handler
|
// Regular entity offsets can be searched directly in our custom handler
|
||||||
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
||||||
|
|
||||||
const auto got = CustomEntityFields.find(entity_offset);
|
if (const auto itr = CustomEntityFields.find(entity_offset); itr != CustomEntityFields.end())
|
||||||
if (got != CustomEntityFields.end())
|
|
||||||
{
|
{
|
||||||
got->second.getter(&Game::g_entities[entnum], offset);
|
itr->second.getter(&Game::g_entities[entnum], offset);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +239,7 @@ namespace Components
|
|||||||
void ScriptExtension::AddMethods()
|
void ScriptExtension::AddMethods()
|
||||||
{
|
{
|
||||||
// ScriptExtension methods
|
// ScriptExtension methods
|
||||||
Script::AddMethod("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp()
|
Script::AddMethod("GetIp", [](const Game::scr_entref_t entref) // gsc: self GetIp()
|
||||||
{
|
{
|
||||||
const auto* ent = Game::GetPlayerEntity(entref);
|
const auto* ent = Game::GetPlayerEntity(entref);
|
||||||
const auto* client = Script::GetClient(ent);
|
const auto* client = Script::GetClient(ent);
|
||||||
@ -259,7 +252,7 @@ namespace Components
|
|||||||
Game::Scr_AddString(ip.data());
|
Game::Scr_AddString(ip.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
Script::AddMethod("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing()
|
Script::AddMethod("GetPing", [](const Game::scr_entref_t entref) // gsc: self GetPing()
|
||||||
{
|
{
|
||||||
const auto* ent = Game::GetPlayerEntity(entref);
|
const auto* ent = Game::GetPlayerEntity(entref);
|
||||||
const auto* client = Script::GetClient(ent);
|
const auto* client = Script::GetClient(ent);
|
||||||
@ -267,7 +260,7 @@ namespace Components
|
|||||||
Game::Scr_AddInt(client->ping);
|
Game::Scr_AddInt(client->ping);
|
||||||
});
|
});
|
||||||
|
|
||||||
Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing(<int>)
|
Script::AddMethod("SetPing", [](const Game::scr_entref_t entref) // gsc: self SetPing(<int>)
|
||||||
{
|
{
|
||||||
auto ping = Game::Scr_GetInt(0);
|
auto ping = Game::Scr_GetInt(0);
|
||||||
|
|
||||||
@ -306,7 +299,7 @@ namespace Components
|
|||||||
|
|
||||||
void ScriptExtension::AddEntityFields()
|
void ScriptExtension::AddEntityFields()
|
||||||
{
|
{
|
||||||
AddEntityField("entityflags", Game::fieldtype_t::F_INT,
|
AddEntityField("entityflags", Game::F_INT,
|
||||||
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
|
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
|
||||||
{
|
{
|
||||||
ent->flags = Game::Scr_GetInt(0);
|
ent->flags = Game::Scr_GetInt(0);
|
||||||
@ -319,7 +312,7 @@ namespace Components
|
|||||||
|
|
||||||
void ScriptExtension::AddClientFields()
|
void ScriptExtension::AddClientFields()
|
||||||
{
|
{
|
||||||
AddClientField("clientflags", Game::fieldtype_t::F_INT,
|
AddClientField("clientflags", Game::F_INT,
|
||||||
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
|
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
|
||||||
{
|
{
|
||||||
pSelf->flags = Game::Scr_GetInt(0);
|
pSelf->flags = Game::Scr_GetInt(0);
|
||||||
|
@ -54,7 +54,6 @@ namespace Components
|
|||||||
if (!Data.contains(key))
|
if (!Data.contains(key))
|
||||||
{
|
{
|
||||||
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
|
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& data = Data.at(key);
|
const auto& data = Data.at(key);
|
||||||
@ -91,7 +90,6 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Data.clear();
|
Data.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptStorage::ScriptStorage()
|
ScriptStorage::ScriptStorage()
|
||||||
|
@ -204,7 +204,7 @@ namespace Components
|
|||||||
|
|
||||||
IPCPipe::IPCPipe()
|
IPCPipe::IPCPipe()
|
||||||
{
|
{
|
||||||
if (Dedicated::IsEnabled() || Monitor::IsEnabled() || Loader::IsPerformingUnitTests() || ZoneBuilder::IsEnabled()) return;
|
if (Dedicated::IsEnabled() || Loader::IsPerformingUnitTests() || ZoneBuilder::IsEnabled()) return;
|
||||||
|
|
||||||
// Server pipe
|
// Server pipe
|
||||||
IPCPipe::ServerPipe.onConnect(IPCPipe::ConnectClient);
|
IPCPipe::ServerPipe.onConnect(IPCPipe::ConnectClient);
|
||||||
|
@ -5,25 +5,37 @@ namespace Components
|
|||||||
std::recursive_mutex Localization::LocalizeMutex;
|
std::recursive_mutex Localization::LocalizeMutex;
|
||||||
Dvar::Var Localization::UseLocalization;
|
Dvar::Var Localization::UseLocalization;
|
||||||
std::unordered_map<std::string, Game::LocalizeEntry*> Localization::LocalizeMap;
|
std::unordered_map<std::string, Game::LocalizeEntry*> Localization::LocalizeMap;
|
||||||
std::unordered_map<std::string, Game::LocalizeEntry*> Localization::TempLocalizeMap;
|
|
||||||
|
|
||||||
void Localization::Set(const std::string& key, const std::string& value)
|
std::optional<std::string> Localization::PrefixOverride;
|
||||||
|
std::function<void(Game::LocalizeEntry*)> Localization::ParseCallback;
|
||||||
|
|
||||||
|
void Localization::Set(const std::string& psLocalReference, const std::string& psNewString)
|
||||||
{
|
{
|
||||||
std::lock_guard _(Localization::LocalizeMutex);
|
std::lock_guard _(LocalizeMutex);
|
||||||
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
|
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
|
||||||
|
|
||||||
if (Localization::LocalizeMap.contains(key))
|
auto key = psLocalReference;
|
||||||
|
if (PrefixOverride.has_value())
|
||||||
{
|
{
|
||||||
Game::LocalizeEntry* entry = Localization::LocalizeMap[key];
|
key.insert(0, PrefixOverride.value());
|
||||||
|
}
|
||||||
|
|
||||||
char* newStaticValue = allocator->duplicateString(value);
|
if (LocalizeMap.contains(key))
|
||||||
|
{
|
||||||
|
auto* entry = LocalizeMap[key];
|
||||||
|
|
||||||
|
const auto* newStaticValue = allocator->duplicateString(psNewString);
|
||||||
if (!newStaticValue) return;
|
if (!newStaticValue) return;
|
||||||
|
|
||||||
if (entry->value) allocator->free(entry->value);
|
if (entry->value) allocator->free(entry->value);
|
||||||
entry->value = newStaticValue;
|
entry->value = newStaticValue;
|
||||||
|
|
||||||
|
SaveParseOutput(entry);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::LocalizeEntry* entry = allocator->allocate<Game::LocalizeEntry>();
|
auto* entry = allocator->allocate<Game::LocalizeEntry>();
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
|
|
||||||
entry->name = allocator->duplicateString(key);
|
entry->name = allocator->duplicateString(key);
|
||||||
@ -33,7 +45,7 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->value = allocator->duplicateString(value);
|
entry->value = allocator->duplicateString(psNewString);
|
||||||
if (!entry->value)
|
if (!entry->value)
|
||||||
{
|
{
|
||||||
allocator->free(entry->name);
|
allocator->free(entry->name);
|
||||||
@ -41,24 +53,23 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Localization::LocalizeMap[key] = entry;
|
SaveParseOutput(entry);
|
||||||
|
|
||||||
|
LocalizeMap[key] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Localization::Get(const char* key)
|
const char* Localization::Get(const char* key)
|
||||||
{
|
{
|
||||||
if (!Localization::UseLocalization.get<bool>()) return key;
|
if (!UseLocalization.get<bool>()) return key;
|
||||||
|
|
||||||
Game::LocalizeEntry* entry = nullptr;
|
Game::LocalizeEntry* entry = nullptr;
|
||||||
{
|
|
||||||
std::lock_guard _(Localization::LocalizeMutex);
|
|
||||||
|
|
||||||
if (Localization::TempLocalizeMap.contains(key))
|
|
||||||
{
|
{
|
||||||
entry = Localization::TempLocalizeMap[key];
|
std::lock_guard _(LocalizeMutex);
|
||||||
}
|
|
||||||
else if (Localization::LocalizeMap.contains(key))
|
if (LocalizeMap.contains(key))
|
||||||
{
|
{
|
||||||
entry = Localization::LocalizeMap[key];
|
entry = LocalizeMap[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,89 +86,21 @@ namespace Components
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Localization::SetTemp(const std::string& key, const std::string& value)
|
void __stdcall Localization::SetStringStub(const char* psLocalReference, const char* psNewString, [[maybe_unused]] int bSentenceIsEnglish)
|
||||||
{
|
{
|
||||||
std::lock_guard _(Localization::LocalizeMutex);
|
Set(psLocalReference, psNewString);
|
||||||
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
|
|
||||||
|
|
||||||
if (Localization::TempLocalizeMap.contains(key))
|
|
||||||
{
|
|
||||||
Game::LocalizeEntry* entry = Localization::TempLocalizeMap[key];
|
|
||||||
if (entry->value) allocator->free(entry->value);
|
|
||||||
entry->value = allocator->duplicateString(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Game::LocalizeEntry* entry = allocator->allocate<Game::LocalizeEntry>();
|
|
||||||
if (!entry) return;
|
|
||||||
|
|
||||||
entry->name = allocator->duplicateString(key);
|
|
||||||
if (!entry->name)
|
|
||||||
{
|
|
||||||
allocator->free(entry);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->value = allocator->duplicateString(value);
|
void Localization::ParseOutput(const std::function<void(Game::LocalizeEntry*)>& callback)
|
||||||
if (!entry->value)
|
|
||||||
{
|
{
|
||||||
allocator->free(entry->name);
|
ParseCallback = callback;
|
||||||
allocator->free(entry);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Localization::TempLocalizeMap[key] = entry;
|
void Localization::SaveParseOutput(Game::LocalizeEntry* asset)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Localization::ClearTemp()
|
|
||||||
{
|
{
|
||||||
std::lock_guard _(Localization::LocalizeMutex);
|
if (ParseCallback)
|
||||||
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
|
|
||||||
|
|
||||||
for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i)
|
|
||||||
{
|
{
|
||||||
if (i->second)
|
ParseCallback(asset);
|
||||||
{
|
|
||||||
if (i->second->name) allocator->free(i->second->name);
|
|
||||||
if (i->second->value) allocator->free(i->second->value);
|
|
||||||
allocator->free(i->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Localization::TempLocalizeMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void __stdcall Localization::SetStringStub(const char* key, const char* value, bool /*isEnglish*/)
|
|
||||||
{
|
|
||||||
Localization::Set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Localization::LoadLanguageStrings()
|
|
||||||
{
|
|
||||||
//if (ZoneBuilder::IsEnabled())
|
|
||||||
{
|
|
||||||
if (FileSystem::File(Utils::String::VA("localizedstrings/iw4x_%s.str", Game::Win_GetLanguage())).exists())
|
|
||||||
{
|
|
||||||
Game::SE_Load(Utils::String::VA("localizedstrings/iw4x_%s.str", Game::Win_GetLanguage()), 0);
|
|
||||||
}
|
|
||||||
else if (FileSystem::File("localizedstrings/iw4x_english.str").exists())
|
|
||||||
{
|
|
||||||
Game::SE_Load("localizedstrings/iw4x_english.str", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__declspec(naked) void Localization::SELoadLanguageStub()
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
{
|
|
||||||
pushad
|
|
||||||
call Localization::LoadLanguageStrings
|
|
||||||
popad
|
|
||||||
|
|
||||||
push 629E20h
|
|
||||||
retn
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +189,7 @@ namespace Components
|
|||||||
// I have no idea why, but the last 2 lines are invisible!
|
// I have no idea why, but the last 2 lines are invisible!
|
||||||
credits.append("-\n-");
|
credits.append("-\n-");
|
||||||
|
|
||||||
Localization::Set("IW4X_CREDITS", credits);
|
Set("IW4X_CREDITS", credits);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Localization::SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType)
|
const char* Localization::SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType)
|
||||||
@ -389,38 +332,31 @@ namespace Components
|
|||||||
|
|
||||||
Localization::Localization()
|
Localization::Localization()
|
||||||
{
|
{
|
||||||
Localization::SetCredits();
|
SetCredits();
|
||||||
|
|
||||||
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, [](Game::XAssetType, const std::string& filename)
|
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_LOCALIZE_ENTRY, [](Game::XAssetType, const std::string& name)
|
||||||
{
|
{
|
||||||
Game::XAssetHeader header = { nullptr };
|
Game::XAssetHeader header = { nullptr };
|
||||||
std::lock_guard _(Localization::LocalizeMutex);
|
std::lock_guard _(LocalizeMutex);
|
||||||
|
|
||||||
if (Localization::TempLocalizeMap.contains(filename))
|
if (const auto itr = LocalizeMap.find(name); itr != LocalizeMap.end())
|
||||||
{
|
{
|
||||||
header.localize = Localization::TempLocalizeMap[filename];
|
header.localize = itr->second;
|
||||||
}
|
|
||||||
else if (Localization::LocalizeMap.contains(filename))
|
|
||||||
{
|
|
||||||
header.localize = Localization::LocalizeMap[filename];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Resolving hook
|
// Resolving hook
|
||||||
Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x629B90, Get, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
// Set loading entry point
|
|
||||||
Utils::Hook(0x41D859, Localization::SELoadLanguageStub, HOOK_CALL).install()->quick();
|
|
||||||
|
|
||||||
// Overwrite SetString
|
// Overwrite SetString
|
||||||
Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick();
|
Utils::Hook(0x4CE5EE, SetStringStub, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
Utils::Hook(0x49D4A0, Localization::SEH_LocalizeTextMessageStub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x49D4A0, SEH_LocalizeTextMessageStub, HOOK_JUMP).install()->quick();
|
||||||
Utils::Hook::Nop(0x49D4A5, 1);
|
Utils::Hook::Nop(0x49D4A5, 1);
|
||||||
|
|
||||||
Localization::UseLocalization = Dvar::Register<bool>("ui_localize", true, Game::DVAR_NONE, "Use localization strings");
|
UseLocalization = Dvar::Register<bool>("ui_localize", true, Game::DVAR_NONE, "Use localization strings");
|
||||||
|
|
||||||
// Generate localized entries for custom classes above 10
|
// Generate localized entries for custom classes above 10
|
||||||
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/)
|
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/)
|
||||||
@ -434,9 +370,9 @@ namespace Components
|
|||||||
std::string key = Utils::String::VA("CLASS_SLOT%i", i);
|
std::string key = Utils::String::VA("CLASS_SLOT%i", i);
|
||||||
|
|
||||||
std::string value = asset.localize->value;
|
std::string value = asset.localize->value;
|
||||||
Utils::String::Replace(value, "1", Utils::String::VA("%i", i)); // Pretty ugly, but it should work
|
Utils::String::Replace(value, "1", std::to_string(i)); // Pretty ugly, but it should work
|
||||||
|
|
||||||
Localization::Set(key, value);
|
Set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -444,7 +380,6 @@ namespace Components
|
|||||||
|
|
||||||
Localization::~Localization()
|
Localization::~Localization()
|
||||||
{
|
{
|
||||||
Localization::ClearTemp();
|
LocalizeMap.clear();
|
||||||
Localization::LocalizeMap.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,21 +8,23 @@ namespace Components
|
|||||||
Localization();
|
Localization();
|
||||||
~Localization();
|
~Localization();
|
||||||
|
|
||||||
static void Set(const std::string& key, const std::string& value);
|
static void Set(const std::string& psLocalReference, const std::string& psNewString);
|
||||||
static const char* Get(const char* key);
|
static const char* Get(const char* key);
|
||||||
|
|
||||||
static void SetTemp(const std::string& key, const std::string& value);
|
static std::optional<std::string> PrefixOverride;
|
||||||
static void ClearTemp();
|
static void ParseOutput(const std::function<void(Game::LocalizeEntry*)>& callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static std::recursive_mutex LocalizeMutex;
|
static std::recursive_mutex LocalizeMutex;
|
||||||
static std::unordered_map<std::string, Game::LocalizeEntry*> LocalizeMap;
|
static std::unordered_map<std::string, Game::LocalizeEntry*> LocalizeMap;
|
||||||
static std::unordered_map<std::string, Game::LocalizeEntry*> TempLocalizeMap;
|
|
||||||
static Dvar::Var UseLocalization;
|
static Dvar::Var UseLocalization;
|
||||||
|
|
||||||
static void __stdcall SetStringStub(const char* key, const char* value, bool isEnglish);
|
static std::function<void(Game::LocalizeEntry*)> ParseCallback;
|
||||||
static void LoadLanguageStrings();
|
|
||||||
static void SELoadLanguageStub();
|
static void __stdcall SetStringStub(const char* psLocalReference, const char* psNewString, int bSentenceIsEnglish);
|
||||||
|
|
||||||
|
static void SaveParseOutput(Game::LocalizeEntry* asset);
|
||||||
|
|
||||||
static void SetCredits();
|
static void SetCredits();
|
||||||
|
|
||||||
static const char* SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType);
|
static const char* SEH_LocalizeTextMessageStub(const char* pszInputBuffer, const char* pszMessageType, Game::msgLocErrType_t errType);
|
||||||
|
@ -5,7 +5,8 @@ namespace Components
|
|||||||
std::mutex Logger::MessageMutex;
|
std::mutex Logger::MessageMutex;
|
||||||
std::vector<std::string> Logger::MessageQueue;
|
std::vector<std::string> Logger::MessageQueue;
|
||||||
std::vector<Network::Address> Logger::LoggingAddresses[2];
|
std::vector<Network::Address> Logger::LoggingAddresses[2];
|
||||||
void(*Logger::PipeCallback)(const std::string&) = nullptr;
|
|
||||||
|
std::function<void(const std::string&)> Logger::PipeCallback;
|
||||||
|
|
||||||
bool Logger::IsConsoleReady()
|
bool Logger::IsConsoleReady()
|
||||||
{
|
{
|
||||||
@ -106,21 +107,6 @@ namespace Components
|
|||||||
Logger::MessagePrint(channel, msg);
|
Logger::MessagePrint(channel, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logger::Flush()
|
|
||||||
{
|
|
||||||
// if (!Game::Sys_IsMainThread())
|
|
||||||
// {
|
|
||||||
// while (!Logger::MessageQueue.empty())
|
|
||||||
// {
|
|
||||||
// std::this_thread::sleep_for(10ms);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
{
|
|
||||||
Logger::Frame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Logger::Frame()
|
void Logger::Frame()
|
||||||
{
|
{
|
||||||
std::unique_lock _(Logger::MessageMutex);
|
std::unique_lock _(Logger::MessageMutex);
|
||||||
@ -138,7 +124,7 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logger::PipeOutput(void(*callback)(const std::string&))
|
void Logger::PipeOutput(const std::function<void(const std::string&)>& callback)
|
||||||
{
|
{
|
||||||
Logger::PipeCallback = callback;
|
Logger::PipeCallback = callback;
|
||||||
}
|
}
|
||||||
@ -177,9 +163,9 @@ namespace Components
|
|||||||
const auto time = Game::level->time / 1000;
|
const auto time = Game::level->time / 1000;
|
||||||
const auto len = _snprintf_s(string, _TRUNCATE, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, string2);
|
const auto len = _snprintf_s(string, _TRUNCATE, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, string2);
|
||||||
|
|
||||||
if (Game::level->logFile != nullptr)
|
if (Game::level->logFile)
|
||||||
{
|
{
|
||||||
Game::FS_Write(string, len, reinterpret_cast<int>(Game::level->logFile));
|
Game::FS_Write(string, len, Game::level->logFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow the network log to run even if logFile was not opened
|
// Allow the network log to run even if logFile was not opened
|
||||||
|
@ -12,9 +12,7 @@ namespace Components
|
|||||||
|
|
||||||
static void Print_Stub(int channel, const char* message, ...);
|
static void Print_Stub(int channel, const char* message, ...);
|
||||||
|
|
||||||
static void PipeOutput(void(*callback)(const std::string&));
|
static void PipeOutput(const std::function<void(const std::string&)>& callback);
|
||||||
|
|
||||||
static void Flush();
|
|
||||||
|
|
||||||
static void PrintInternal(int channel, std::string_view fmt, std::format_args&& args);
|
static void PrintInternal(int channel, std::string_view fmt, std::format_args&& args);
|
||||||
static void ErrorInternal(Game::errorParm_t error, std::string_view fmt, std::format_args&& args);
|
static void ErrorInternal(Game::errorParm_t error, std::string_view fmt, std::format_args&& args);
|
||||||
@ -96,10 +94,13 @@ namespace Components
|
|||||||
static std::mutex MessageMutex;
|
static std::mutex MessageMutex;
|
||||||
static std::vector<std::string> MessageQueue;
|
static std::vector<std::string> MessageQueue;
|
||||||
static std::vector<Network::Address> LoggingAddresses[2];
|
static std::vector<Network::Address> LoggingAddresses[2];
|
||||||
static void(*PipeCallback)(const std::string&);
|
|
||||||
|
static std::function<void(const std::string&)> PipeCallback;
|
||||||
|
|
||||||
static void MessagePrint(int channel, const std::string& msg);
|
static void MessagePrint(int channel, const std::string& msg);
|
||||||
|
|
||||||
static void Frame();
|
static void Frame();
|
||||||
|
|
||||||
static void G_LogPrintf_Hk(const char* fmt, ...);
|
static void G_LogPrintf_Hk(const char* fmt, ...);
|
||||||
static void PrintMessage_Stub();
|
static void PrintMessage_Stub();
|
||||||
static void PrintMessagePipe(const char* data);
|
static void PrintMessagePipe(const char* data);
|
||||||
|
@ -408,7 +408,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
std::string hash;
|
std::string hash;
|
||||||
|
|
||||||
for(int i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
||||||
{
|
{
|
||||||
std::string filePath = Utils::String::VA("usermaps/%s/%s%s", map.data(), map.data(), Maps::UserMapFiles[i]);
|
std::string filePath = Utils::String::VA("usermaps/%s/%s%s", map.data(), map.data(), Maps::UserMapFiles[i]);
|
||||||
if (Utils::IO::FileExists(filePath))
|
if (Utils::IO::FileExists(filePath))
|
||||||
|
@ -859,11 +859,6 @@ namespace Components
|
|||||||
Menus::Add("ui_mp/resetclass.menu");
|
Menus::Add("ui_mp/resetclass.menu");
|
||||||
Menus::Add("ui_mp/popup_customtitle.menu");
|
Menus::Add("ui_mp/popup_customtitle.menu");
|
||||||
Menus::Add("ui_mp/popup_customclan.menu");
|
Menus::Add("ui_mp/popup_customclan.menu");
|
||||||
|
|
||||||
Menus::Add("ui_mp/scriptmenus/callvote.menu");
|
|
||||||
Menus::Add("ui_mp/scriptmenus/changegametype.menu");
|
|
||||||
Menus::Add("ui_mp/scriptmenus/changemap.menu");
|
|
||||||
Menus::Add("ui_mp/scriptmenus/kickplayer.menu");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menus::~Menus()
|
Menus::~Menus()
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
#include <STDInclude.hpp>
|
|
||||||
#undef getch
|
|
||||||
#undef ungetch
|
|
||||||
#include <conio.h>
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
bool Monitor::IsEnabled()
|
|
||||||
{
|
|
||||||
static std::optional<bool> flag;
|
|
||||||
|
|
||||||
if (!flag.has_value())
|
|
||||||
{
|
|
||||||
flag.emplace(Flags::HasFlag("monitor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return flag.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
int __stdcall Monitor::EntryPoint(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/)
|
|
||||||
{
|
|
||||||
Utils::Hook::Call<void()>(0x4D8220)(); // Dvar_Init
|
|
||||||
Utils::Hook::Call<void()>(0x4D2280)(); // SL_Init
|
|
||||||
Utils::Hook::Call<void()>(0x47F390)(); // Swap_Init
|
|
||||||
Utils::Hook::Call<void()>(0x60AD10)(); // Com_InitDvars
|
|
||||||
Utils::Hook::Call<void()>(0x420830)(); // Com_InitHunkMemory
|
|
||||||
Utils::Hook::Call<void()>(0x4A62A0)(); // LargeLocalInit
|
|
||||||
Utils::Hook::Call<void(unsigned int)>(0x502580)(static_cast<unsigned int>(__rdtsc())); // Netchan_Init
|
|
||||||
Game::NET_Init();
|
|
||||||
|
|
||||||
Utils::Time::Interval interval;
|
|
||||||
while (!interval.elapsed(15s))
|
|
||||||
{
|
|
||||||
Utils::Hook::Call<void()>(0x49F0B0)(); // Com_ClientPacketEvent
|
|
||||||
//Session::RunFrame();
|
|
||||||
Node::RunFrame();
|
|
||||||
ServerList::Frame();
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(10ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto list = ServerList::GetList();
|
|
||||||
if (!list)
|
|
||||||
{
|
|
||||||
printf("1 IW4x player=0|server=0 Returned list was null\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int servers = list->size();
|
|
||||||
int players = 0;
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < list->size(); ++i)
|
|
||||||
{
|
|
||||||
players += list->at(i).clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("0 IW4x player=%d|server=%d Servers successfully parsed\n", players, servers);
|
|
||||||
|
|
||||||
Utils::Hook::Call<void()>(0x430630)(); // LargeLocalReset
|
|
||||||
Utils::Hook::Call<void()>(0x4A0460)(); // Hunk_ClearTempMemory
|
|
||||||
Utils::Hook::Call<void()>(0x4AB3A0)(); // Hunk_ClearTempMemoryHigh
|
|
||||||
Utils::Hook::Call<void()>(0x4B3AD0)(); // SL_Shutdown
|
|
||||||
Utils::Hook::Call<void()>(0x502C50)(); // Dvar_Shutdown
|
|
||||||
|
|
||||||
if (*Game::ip_socket && *Game::ip_socket != INVALID_SOCKET)
|
|
||||||
{
|
|
||||||
closesocket(*Game::ip_socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
WSACleanup();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Monitor::Monitor()
|
|
||||||
{
|
|
||||||
if (!Monitor::IsEnabled()) return;
|
|
||||||
|
|
||||||
Utils::Hook(0x4513DA, Monitor::EntryPoint, HOOK_JUMP).install()->quick();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
class Monitor : public Component
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Monitor();
|
|
||||||
|
|
||||||
static bool IsEnabled();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int __stdcall EntryPoint(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd);
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
#include <STDInclude.hpp>
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
std::unordered_map<std::string, const char*> MusicalTalent::SoundAliasList;
|
|
||||||
|
|
||||||
void MusicalTalent::Replace(const std::string& sound, const char* file)
|
|
||||||
{
|
|
||||||
MusicalTalent::SoundAliasList[Utils::String::ToLower(sound)] = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
Game::XAssetHeader MusicalTalent::ModifyAliases(Game::XAssetType type, const std::string& filename)
|
|
||||||
{
|
|
||||||
Game::XAssetHeader header = { nullptr };
|
|
||||||
|
|
||||||
if (MusicalTalent::SoundAliasList.find(Utils::String::ToLower(filename)) != MusicalTalent::SoundAliasList.end())
|
|
||||||
{
|
|
||||||
Game::snd_alias_list_t* aliases = Game::DB_FindXAssetHeader(type, filename.data()).sound;
|
|
||||||
|
|
||||||
if (aliases && aliases->count > 0 && aliases->head && aliases->head->soundFile)
|
|
||||||
{
|
|
||||||
if (aliases->head->soundFile->type == Game::snd_alias_type_t::SAT_STREAMED)
|
|
||||||
{
|
|
||||||
aliases->head->soundFile->u.streamSnd.filename.info.raw.name = MusicalTalent::SoundAliasList[Utils::String::ToLower(filename)];
|
|
||||||
}
|
|
||||||
|
|
||||||
header.sound = aliases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
MusicalTalent::MusicalTalent()
|
|
||||||
{
|
|
||||||
if (ZoneBuilder::IsEnabled() || Dedicated::IsEnabled()) return;
|
|
||||||
|
|
||||||
AssetHandler::OnFind(Game::XAssetType::ASSET_TYPE_SOUND, MusicalTalent::ModifyAliases);
|
|
||||||
|
|
||||||
MusicalTalent::Replace("music_mainmenu_mp", "hz_t_menumusic.mp3");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
class MusicalTalent : public Component
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MusicalTalent();
|
|
||||||
|
|
||||||
static void Replace(const std::string& sound, const char* file);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::unordered_map<std::string, const char*> SoundAliasList;
|
|
||||||
static Game::XAssetHeader ModifyAliases(Game::XAssetType type, const std::string& filename);
|
|
||||||
};
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ namespace Components
|
|||||||
Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
|
Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
|
||||||
// Packet interception
|
// Packet interception
|
||||||
std::unordered_map<std::string, Network::NetworkCallback> Network::CL_Callbacks;
|
std::unordered_map<std::string, Network::NetworkCallback> Network::CL_Callbacks;
|
||||||
std::unordered_map<std::string, Network::NetworkCallback> Network::SV_Callbacks;
|
|
||||||
|
|
||||||
Network::Address::Address(const std::string& addrString)
|
Network::Address::Address(const std::string& addrString)
|
||||||
{
|
{
|
||||||
@ -17,7 +16,7 @@ namespace Components
|
|||||||
Game::SockadrToNetadr(addr, &this->address);
|
Game::SockadrToNetadr(addr, &this->address);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Network::Address::operator==(const Network::Address& obj) const
|
bool Network::Address::operator==(const Address& obj) const
|
||||||
{
|
{
|
||||||
return Game::NET_CompareAdr(this->address, obj.address);
|
return Game::NET_CompareAdr(this->address, obj.address);
|
||||||
}
|
}
|
||||||
@ -151,13 +150,11 @@ namespace Components
|
|||||||
|
|
||||||
void Network::Send(Game::netsrc_t type, Address target, const std::string& data)
|
void Network::Send(Game::netsrc_t type, Address target, const std::string& data)
|
||||||
{
|
{
|
||||||
// NET_OutOfBandPrint only supports non-binary data!
|
// Do not use NET_OutOfBandPrint. It only supports non-binary data!
|
||||||
//Game::NET_OutOfBandPrint(type, *target.Get(), data.data());
|
|
||||||
|
|
||||||
std::string rawData;
|
std::string rawData;
|
||||||
rawData.append("\xFF\xFF\xFF\xFF", 4);
|
rawData.append("\xFF\xFF\xFF\xFF", 4);
|
||||||
rawData.append(data);
|
rawData.append(data);
|
||||||
//rawData.append("\0", 1);
|
|
||||||
|
|
||||||
SendRaw(type, target, rawData);
|
SendRaw(type, target, rawData);
|
||||||
}
|
}
|
||||||
@ -171,8 +168,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
if (!target.isValid()) return;
|
if (!target.isValid()) return;
|
||||||
|
|
||||||
// NET_OutOfBandData doesn't seem to work properly
|
// NET_OutOfBandData doesn't seem to work properly. Do not use it
|
||||||
//Game::NET_OutOfBandData(type, *target.Get(), data.data(), data.size());
|
|
||||||
Game::Sys_SendPacket(type, data.size(), data.data(), *target.get());
|
Game::Sys_SendPacket(type, data.size(), data.data(), *target.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,11 +278,6 @@ namespace Components
|
|||||||
CL_Callbacks[Utils::String::ToLower(command)] = callback;
|
CL_Callbacks[Utils::String::ToLower(command)] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::OnServerPacket(const std::string& command, const NetworkCallback& callback)
|
|
||||||
{
|
|
||||||
SV_Callbacks[Utils::String::ToLower(command)] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Network::CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
|
bool Network::CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
|
||||||
{
|
{
|
||||||
const auto command_ = Utils::String::ToLower(command);
|
const auto command_ = Utils::String::ToLower(command);
|
||||||
@ -300,25 +291,7 @@ namespace Components
|
|||||||
|
|
||||||
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
|
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
|
||||||
|
|
||||||
Address address_ = address;
|
auto address_ = Address(address);
|
||||||
handler->second(address_, data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Network::SV_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message)
|
|
||||||
{
|
|
||||||
const auto command_ = Utils::String::ToLower(command);
|
|
||||||
const auto handler = SV_Callbacks.find(command_);
|
|
||||||
|
|
||||||
const auto offset = command_.size() + 5;
|
|
||||||
if (static_cast<std::size_t>(message->cursize) < offset || handler == SV_Callbacks.end())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
|
|
||||||
|
|
||||||
Address address_ = address;
|
|
||||||
handler->second(address_, data);
|
handler->second(address_, data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -354,37 +327,6 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__declspec(naked) void Network::SV_HandleCommandStub()
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
{
|
|
||||||
lea eax, [esp + 0x408]
|
|
||||||
|
|
||||||
pushad
|
|
||||||
|
|
||||||
push esi // msg
|
|
||||||
push edi // command name
|
|
||||||
push eax // netadr_t pointer
|
|
||||||
call SV_HandleCommand
|
|
||||||
add esp, 0xC
|
|
||||||
|
|
||||||
test al, al
|
|
||||||
|
|
||||||
popad
|
|
||||||
|
|
||||||
jz unhandled
|
|
||||||
|
|
||||||
// Exit SV_ConnectionlessPacket
|
|
||||||
push 0x6267EB
|
|
||||||
retn
|
|
||||||
|
|
||||||
unhandled:
|
|
||||||
// Proceed
|
|
||||||
push 0x6266E0
|
|
||||||
retn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Network::Network()
|
Network::Network()
|
||||||
{
|
{
|
||||||
AssertSize(Game::netadr_t, 20);
|
AssertSize(Game::netadr_t, 20);
|
||||||
@ -420,7 +362,6 @@ namespace Components
|
|||||||
|
|
||||||
// Handle client packets
|
// Handle client packets
|
||||||
Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick();
|
||||||
Utils::Hook(0x6266CA, SV_HandleCommandStub, HOOK_JUMP).install()->quick();
|
|
||||||
|
|
||||||
// Disable unused OOB packets handlers just to be sure
|
// Disable unused OOB packets handlers just to be sure
|
||||||
Utils::Hook::Set<BYTE>(0x5AA5B6, 0xEB); // CL_SteamServerAuth
|
Utils::Hook::Set<BYTE>(0x5AA5B6, 0xEB); // CL_SteamServerAuth
|
||||||
|
@ -8,7 +8,7 @@ namespace Components
|
|||||||
class Address
|
class Address
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Address() { setType(Game::netadrtype_t::NA_BAD); }
|
Address() { setType(Game::NA_BAD); }
|
||||||
Address(const std::string& addrString);
|
Address(const std::string& addrString);
|
||||||
Address(sockaddr* addr);
|
Address(sockaddr* addr);
|
||||||
Address(sockaddr addr) : Address(&addr) {}
|
Address(sockaddr addr) : Address(&addr) {}
|
||||||
@ -73,12 +73,10 @@ namespace Components
|
|||||||
static void BroadcastAll(const std::string& data);
|
static void BroadcastAll(const std::string& data);
|
||||||
|
|
||||||
static void OnClientPacket(const std::string& command, const NetworkCallback& callback);
|
static void OnClientPacket(const std::string& command, const NetworkCallback& callback);
|
||||||
static void OnServerPacket(const std::string& command, const NetworkCallback& callback);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Utils::Signal<CallbackRaw> StartupSignal;
|
static Utils::Signal<CallbackRaw> StartupSignal;
|
||||||
static std::unordered_map<std::string, NetworkCallback> CL_Callbacks;
|
static std::unordered_map<std::string, NetworkCallback> CL_Callbacks;
|
||||||
static std::unordered_map<std::string, NetworkCallback> SV_Callbacks;
|
|
||||||
|
|
||||||
static void NetworkStart();
|
static void NetworkStart();
|
||||||
static void NetworkStartStub();
|
static void NetworkStartStub();
|
||||||
@ -88,10 +86,8 @@ namespace Components
|
|||||||
static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg);
|
static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg);
|
||||||
|
|
||||||
static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
|
static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
|
||||||
static bool SV_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
|
|
||||||
|
|
||||||
static void CL_HandleCommandStub();
|
static void CL_HandleCommandStub();
|
||||||
static void SV_HandleCommandStub();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +50,6 @@ namespace Components
|
|||||||
this->lastRequest.reset();
|
this->lastRequest.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json Node::Entry::to_json() const
|
|
||||||
{
|
|
||||||
return this->address.getString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Node::LoadNodeRemotePreset()
|
void Node::LoadNodeRemotePreset()
|
||||||
{
|
{
|
||||||
std::string nodes = Utils::Cache::GetFile("/iw4/nodes.txt");
|
std::string nodes = Utils::Cache::GetFile("/iw4/nodes.txt");
|
||||||
@ -73,18 +68,10 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Proto::Node::List list;
|
Proto::Node::List list;
|
||||||
|
|
||||||
if (Monitor::IsEnabled())
|
|
||||||
{
|
|
||||||
std::string nodes = Utils::IO::ReadFile("players/nodes_default.dat");
|
|
||||||
if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FileSystem::File defaultNodes("nodes_default.dat");
|
FileSystem::File defaultNodes("nodes_default.dat");
|
||||||
if (!defaultNodes.exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.getBuffer()))) return;
|
if (!defaultNodes.exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.getBuffer()))) return;
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < list.nodes_size(); ++i)
|
for (auto i = 0; i < list.nodes_size(); ++i)
|
||||||
{
|
{
|
||||||
const std::string& addr = list.nodes(i);
|
const std::string& addr = list.nodes(i);
|
||||||
|
|
||||||
@ -171,7 +158,7 @@ namespace Components
|
|||||||
|
|
||||||
if (!Dedicated::IsEnabled())
|
if (!Dedicated::IsEnabled())
|
||||||
{
|
{
|
||||||
if (ServerList::useMasterServer) return; // don't run node frame if master server is active
|
if (ServerList::UseMasterServer) return; // don't run node frame if master server is active
|
||||||
|
|
||||||
if (*Game::clcState > 0)
|
if (*Game::clcState > 0)
|
||||||
{
|
{
|
||||||
@ -251,7 +238,7 @@ namespace Components
|
|||||||
|
|
||||||
if (list.isnode() && (!list.port() || list.port() == address.getPort()))
|
if (list.isnode() && (!list.port() || list.port() == address.getPort()))
|
||||||
{
|
{
|
||||||
if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && !ServerList::useMasterServer && list.protocol() == PROTOCOL)
|
if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && !ServerList::UseMasterServer && list.protocol() == PROTOCOL)
|
||||||
{
|
{
|
||||||
Logger::Debug("Inserting {} into the serverlist", address.getString());
|
Logger::Debug("Inserting {} into the serverlist", address.getString());
|
||||||
ServerList::InsertRequest(address);
|
ServerList::InsertRequest(address);
|
||||||
@ -361,8 +348,7 @@ namespace Components
|
|||||||
Node::LoadNodes();
|
Node::LoadNodes();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Monitor::IsEnabled()) Network::OnStart(loadNodes);
|
Scheduler::OnGameInitialized(loadNodes, Scheduler::Pipeline::MAIN);
|
||||||
else Scheduler::OnGameInitialized(loadNodes, Scheduler::Pipeline::MAIN);
|
|
||||||
|
|
||||||
Network::OnStart([]
|
Network::OnStart([]
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,6 @@ namespace Components
|
|||||||
void sendRequest();
|
void sendRequest();
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
nlohmann::json to_json() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Node();
|
Node();
|
||||||
|
@ -65,7 +65,6 @@ namespace Components
|
|||||||
|
|
||||||
void Party::ConnectError(const std::string& message)
|
void Party::ConnectError(const std::string& message)
|
||||||
{
|
{
|
||||||
Localization::ClearTemp();
|
|
||||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||||
Dvar::Var("partyend_reason").set(message);
|
Dvar::Var("partyend_reason").set(message);
|
||||||
Command::Execute("openmenu menu_xboxlive_partyended");
|
Command::Execute("openmenu menu_xboxlive_partyended");
|
||||||
@ -139,7 +138,7 @@ namespace Components
|
|||||||
|
|
||||||
bool Party::IsInLobby()
|
bool Party::IsInLobby()
|
||||||
{
|
{
|
||||||
return (!(*Game::com_sv_running)->current.enabled && PartyEnable.get<bool>() && Dvar::Var("party_host").get<bool>());
|
return (!Dedicated::IsRunning() && PartyEnable.get<bool>() && Dvar::Var("party_host").get<bool>());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Party::IsInUserMapLobby()
|
bool Party::IsInUserMapLobby()
|
||||||
@ -312,7 +311,7 @@ namespace Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Basic info handler
|
// Basic info handler
|
||||||
Network::OnServerPacket("getInfo", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
Network::OnClientPacket("getInfo", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||||
{
|
{
|
||||||
int botCount = 0;
|
int botCount = 0;
|
||||||
int clientCount = 0;
|
int clientCount = 0;
|
||||||
@ -352,7 +351,7 @@ namespace Components
|
|||||||
info.set("isPrivate", (Dvar::Var("g_password").get<std::string>().size() ? "1" : "0"));
|
info.set("isPrivate", (Dvar::Var("g_password").get<std::string>().size() ? "1" : "0"));
|
||||||
info.set("hc", (Dvar::Var("g_hardcore").get<bool>() ? "1" : "0"));
|
info.set("hc", (Dvar::Var("g_hardcore").get<bool>() ? "1" : "0"));
|
||||||
info.set("securityLevel", Utils::String::VA("%i", Dvar::Var("sv_securityLevel").get<int>()));
|
info.set("securityLevel", Utils::String::VA("%i", Dvar::Var("sv_securityLevel").get<int>()));
|
||||||
info.set("sv_running", ((*Game::com_sv_running)->current.enabled ? "1" : "0"));
|
info.set("sv_running", (Dedicated::IsRunning() ? "1" : "0"));
|
||||||
info.set("aimAssist", (Gamepad::sv_allowAimAssist.get<bool>() ? "1" : "0"));
|
info.set("aimAssist", (Gamepad::sv_allowAimAssist.get<bool>() ? "1" : "0"));
|
||||||
info.set("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0"));
|
info.set("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0"));
|
||||||
|
|
||||||
|
@ -332,6 +332,9 @@ namespace Components
|
|||||||
// Fix crash as nullptr goes unchecked
|
// Fix crash as nullptr goes unchecked
|
||||||
Utils::Hook(0x437CAD, QuickPatch::SND_GetAliasOffset_Stub, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x437CAD, QuickPatch::SND_GetAliasOffset_Stub, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
|
// Make VA thread safe
|
||||||
|
Utils::Hook(0x4785B0, Utils::String::VA, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
// protocol version (workaround for hacks)
|
// protocol version (workaround for hacks)
|
||||||
Utils::Hook::Set<int>(0x4FB501, PROTOCOL);
|
Utils::Hook::Set<int>(0x4FB501, PROTOCOL);
|
||||||
|
|
||||||
@ -446,11 +449,6 @@ namespace Components
|
|||||||
// default sv_pure to 0
|
// default sv_pure to 0
|
||||||
Utils::Hook::Set<BYTE>(0x4D3A74, 0);
|
Utils::Hook::Set<BYTE>(0x4D3A74, 0);
|
||||||
|
|
||||||
// Force debug logging
|
|
||||||
Utils::Hook::Set<BYTE>(0x60AE4A, 1);
|
|
||||||
//Utils::Hook::Nop(0x60AE49, 8);
|
|
||||||
//Utils::Hook::Set<BYTE>(0x6FF53C, 0);
|
|
||||||
|
|
||||||
// remove activeAction execution (exploit in mods)
|
// remove activeAction execution (exploit in mods)
|
||||||
Utils::Hook::Set<BYTE>(0x5A1D43, 0xEB);
|
Utils::Hook::Set<BYTE>(0x5A1D43, 0xEB);
|
||||||
|
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
RCon::Container RCon::BackdoorContainer;
|
RCon::Container RCon::RconContainer;
|
||||||
Utils::Cryptography::ECC::Key RCon::BackdoorKey;
|
Utils::Cryptography::ECC::Key RCon::RconKey;
|
||||||
|
|
||||||
std::string RCon::Password;
|
std::string RCon::Password;
|
||||||
|
|
||||||
Dvar::Var RCon::RconPassword;
|
Dvar::Var RCon::RconPassword;
|
||||||
Dvar::Var RCon::RconLogRequests;
|
Dvar::Var RCon::RconLogRequests;
|
||||||
|
|
||||||
RCon::RCon()
|
void RCon::AddCommands()
|
||||||
{
|
{
|
||||||
Command::Add("rcon", [](Command::Params* params)
|
Command::Add("rcon", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
@ -20,17 +20,22 @@ namespace Components
|
|||||||
if (std::strcmp(operation, "login") == 0)
|
if (std::strcmp(operation, "login") == 0)
|
||||||
{
|
{
|
||||||
if (params->size() < 3) return;
|
if (params->size() < 3) return;
|
||||||
RCon::Password = params->get(2);
|
Password = params->get(2);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (std::strcmp(operation, "logout") == 0)
|
|
||||||
|
if (std::strcmp(operation, "logout") == 0)
|
||||||
{
|
{
|
||||||
RCon::Password.clear();
|
Password.clear();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
|
||||||
auto addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
|
if (Password.empty())
|
||||||
if (!RCon::Password.empty())
|
|
||||||
{
|
{
|
||||||
|
Logger::Print("You need to be logged in and connected to a server!\n");
|
||||||
|
}
|
||||||
|
|
||||||
Network::Address target(addr);
|
Network::Address target(addr);
|
||||||
if (!target.isValid() || target.getIP().full == 0)
|
if (!target.isValid() || target.getIP().full == 0)
|
||||||
{
|
{
|
||||||
@ -39,24 +44,61 @@ namespace Components
|
|||||||
|
|
||||||
if (target.isValid())
|
if (target.isValid())
|
||||||
{
|
{
|
||||||
Network::SendCommand(target, "rcon", RCon::Password + " " + params->join(1));
|
Network::SendCommand(target, "rcon", Password + " " + params->join(1));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::Print("You are connected to an invalid server\n");
|
Logger::Print("You are connected to an invalid server\n");
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::Print("You need to be logged in and connected to a server!\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Dedicated::IsEnabled()) return;
|
Command::Add("remoteCommand", [](Command::Params* params)
|
||||||
|
{
|
||||||
|
if (params->size() < 2) return;
|
||||||
|
|
||||||
|
RconContainer.command = params->get(1);
|
||||||
|
|
||||||
|
auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
|
||||||
|
Network::Address target(addr);
|
||||||
|
if (!target.isValid() || target.getIP().full == 0)
|
||||||
|
{
|
||||||
|
target = Party::Target();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.isValid())
|
||||||
|
{
|
||||||
|
Network::SendCommand(target, "rconRequest");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RCon::RCon()
|
||||||
|
{
|
||||||
|
AddCommands();
|
||||||
|
|
||||||
|
if (!Dedicated::IsEnabled())
|
||||||
|
{
|
||||||
|
Network::OnClientPacket("rconAuthorization", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||||
|
{
|
||||||
|
if (RconContainer.command.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& key = CryptoKey::Get();
|
||||||
|
const auto signedMsg = Utils::Cryptography::ECC::SignMessage(key, data);
|
||||||
|
|
||||||
|
Proto::RCon::Command rconExec;
|
||||||
|
rconExec.set_command(RconContainer.command);
|
||||||
|
rconExec.set_signature(signedMsg);
|
||||||
|
|
||||||
|
Network::SendCommand(address, "rconExecute", rconExec.SerializeAsString());
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Load public key
|
// Load public key
|
||||||
static uint8_t publicKey[] =
|
static std::uint8_t publicKey[] =
|
||||||
{
|
{
|
||||||
0x04, 0x01, 0x9D, 0x18, 0x7F, 0x57, 0xD8, 0x95, 0x4C, 0xEE, 0xD0, 0x21,
|
0x04, 0x01, 0x9D, 0x18, 0x7F, 0x57, 0xD8, 0x95, 0x4C, 0xEE, 0xD0, 0x21,
|
||||||
0xB5, 0x00, 0x53, 0xEC, 0xEB, 0x54, 0x7C, 0x4C, 0x37, 0x18, 0x53, 0x89,
|
0xB5, 0x00, 0x53, 0xEC, 0xEB, 0x54, 0x7C, 0x4C, 0x37, 0x18, 0x53, 0x89,
|
||||||
@ -72,17 +114,17 @@ namespace Components
|
|||||||
0x08
|
0x08
|
||||||
};
|
};
|
||||||
|
|
||||||
RCon::BackdoorKey.set(std::string(reinterpret_cast<char*>(publicKey), sizeof(publicKey)));
|
RconKey.set(std::string(reinterpret_cast<char*>(publicKey), sizeof(publicKey)));
|
||||||
|
|
||||||
RCon::BackdoorContainer.timestamp = 0;
|
RconContainer.timestamp = 0;
|
||||||
|
|
||||||
Scheduler::Once([]
|
Scheduler::Once([]
|
||||||
{
|
{
|
||||||
RCon::RconPassword = Dvar::Register<const char*>("rcon_password", "", Game::DVAR_NONE, "The password for rcon");
|
RconPassword = Dvar::Register<const char*>("rcon_password", "", Game::DVAR_NONE, "The password for rcon");
|
||||||
RCon::RconLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in the output log");
|
RconLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in the output log");
|
||||||
}, Scheduler::Pipeline::MAIN);
|
}, Scheduler::Pipeline::MAIN);
|
||||||
|
|
||||||
Network::OnServerPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
Network::OnClientPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||||
{
|
{
|
||||||
std::string data_ = data;
|
std::string data_ = data;
|
||||||
|
|
||||||
@ -104,7 +146,7 @@ namespace Components
|
|||||||
password.erase(password.begin());
|
password.erase(password.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto svPassword = RCon::RconPassword.get<std::string>();
|
const auto svPassword = RconPassword.get<std::string>();
|
||||||
|
|
||||||
if (svPassword.empty())
|
if (svPassword.empty())
|
||||||
{
|
{
|
||||||
@ -112,13 +154,17 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (svPassword == password)
|
if (svPassword != password)
|
||||||
{
|
{
|
||||||
|
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string outputBuffer;
|
static std::string outputBuffer;
|
||||||
outputBuffer.clear();
|
outputBuffer.clear();
|
||||||
|
|
||||||
#ifndef DEBUG
|
#ifndef DEBUG
|
||||||
if (RCon::RconLogRequests.get<bool>())
|
if (RconLogRequests.get<bool>())
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
||||||
@ -135,46 +181,96 @@ namespace Components
|
|||||||
|
|
||||||
Network::SendCommand(address, "print", outputBuffer);
|
Network::SendCommand(address, "print", outputBuffer);
|
||||||
outputBuffer.clear();
|
outputBuffer.clear();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Network::OnServerPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||||
{
|
{
|
||||||
RCon::BackdoorContainer.address = address;
|
RconContainer.address = address;
|
||||||
RCon::BackdoorContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
|
RconContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
|
||||||
RCon::BackdoorContainer.timestamp = Game::Sys_Milliseconds();
|
RconContainer.timestamp = Game::Sys_Milliseconds();
|
||||||
|
|
||||||
Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge);
|
Network::SendCommand(address, "rconAuthorization", RconContainer.challenge);
|
||||||
});
|
});
|
||||||
|
|
||||||
Network::OnServerPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
Network::OnClientPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||||
{
|
{
|
||||||
if (address != RCon::BackdoorContainer.address) return; // Invalid IP
|
if (address != RconContainer.address) return; // Invalid IP
|
||||||
if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout
|
if (!RconContainer.timestamp || (Game::Sys_Milliseconds() - RconContainer.timestamp) > (1000 * 10)) return; // Timeout
|
||||||
RCon::BackdoorContainer.timestamp = 0;
|
|
||||||
|
|
||||||
Proto::RCon::Command command;
|
RconContainer.timestamp = 0;
|
||||||
command.ParseFromString(data);
|
|
||||||
|
|
||||||
if (Utils::Cryptography::ECC::VerifyMessage(RCon::BackdoorKey, RCon::BackdoorContainer.challenge, command.signature()))
|
Proto::RCon::Command rconExec;
|
||||||
|
rconExec.ParseFromString(data);
|
||||||
|
|
||||||
|
if (!Utils::Cryptography::ECC::VerifyMessage(RconKey, RconContainer.challenge, rconExec.signature()))
|
||||||
{
|
{
|
||||||
RCon::BackdoorContainer.output.clear();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RconContainer.output.clear();
|
||||||
Logger::PipeOutput([](const std::string& output)
|
Logger::PipeOutput([](const std::string& output)
|
||||||
{
|
{
|
||||||
RCon::BackdoorContainer.output.append(output);
|
RconContainer.output.append(output);
|
||||||
});
|
});
|
||||||
|
|
||||||
Command::Execute(command.commands(), true);
|
Command::Execute(rconExec.command(), true);
|
||||||
|
|
||||||
Logger::PipeOutput(nullptr);
|
Logger::PipeOutput(nullptr);
|
||||||
|
|
||||||
Network::SendCommand(address, "print", RCon::BackdoorContainer.output);
|
Network::SendCommand(address, "print", RconContainer.output);
|
||||||
RCon::BackdoorContainer.output.clear();
|
RconContainer.output.clear();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RCon::CryptoKey::LoadKey(Utils::Cryptography::ECC::Key& key)
|
||||||
|
{
|
||||||
|
std::string data;
|
||||||
|
if (!Utils::IO::ReadFile("./private.key", &data))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
key.deserialize(data);
|
||||||
|
return key.isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::Cryptography::ECC::Key RCon::CryptoKey::GenerateKey()
|
||||||
|
{
|
||||||
|
auto key = Utils::Cryptography::ECC::GenerateKey(512);
|
||||||
|
if (!key.isValid())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to generate server key!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Utils::IO::WriteFile("./private.key", key.serialize()))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to write server key!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::Cryptography::ECC::Key RCon::CryptoKey::LoadOrGenerateKey()
|
||||||
|
{
|
||||||
|
Utils::Cryptography::ECC::Key key;
|
||||||
|
if (LoadKey(key))
|
||||||
|
{
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::Cryptography::ECC::Key RCon::CryptoKey::GetKeyInternal()
|
||||||
|
{
|
||||||
|
auto key = LoadOrGenerateKey();
|
||||||
|
Utils::IO::WriteFile("./public.key", key.getPublicKey());
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Utils::Cryptography::ECC::Key& RCon::CryptoKey::Get()
|
||||||
|
{
|
||||||
|
static auto key = GetKeyInternal();
|
||||||
|
return key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,30 @@ namespace Components
|
|||||||
public:
|
public:
|
||||||
int timestamp;
|
int timestamp;
|
||||||
std::string output;
|
std::string output;
|
||||||
|
std::string command;
|
||||||
std::string challenge;
|
std::string challenge;
|
||||||
Network::Address address;
|
Network::Address address;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hue hue backdoor
|
class CryptoKey
|
||||||
static Container BackdoorContainer;
|
{
|
||||||
static Utils::Cryptography::ECC::Key BackdoorKey;
|
public:
|
||||||
|
static const Utils::Cryptography::ECC::Key& Get();
|
||||||
|
private:
|
||||||
|
static bool LoadKey(Utils::Cryptography::ECC::Key& key);
|
||||||
|
static Utils::Cryptography::ECC::Key GenerateKey();
|
||||||
|
static Utils::Cryptography::ECC::Key LoadOrGenerateKey();
|
||||||
|
static Utils::Cryptography::ECC::Key GetKeyInternal();
|
||||||
|
};
|
||||||
|
|
||||||
|
static Container RconContainer;
|
||||||
|
static Utils::Cryptography::ECC::Key RconKey;
|
||||||
|
|
||||||
// For sr0's fucking rcon command
|
|
||||||
// Son of a bitch! Annoying me day and night with that shit...
|
|
||||||
static std::string Password;
|
static std::string Password;
|
||||||
|
|
||||||
static Dvar::Var RconPassword;
|
static Dvar::Var RconPassword;
|
||||||
static Dvar::Var RconLogRequests;
|
static Dvar::Var RconLogRequests;
|
||||||
|
|
||||||
|
static void AddCommands();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ namespace Components
|
|||||||
if ((fileSize + 1) <= size)
|
if ((fileSize + 1) <= size)
|
||||||
{
|
{
|
||||||
Game::FS_Read(buf, fileSize, fileHandle);
|
Game::FS_Read(buf, fileSize, fileHandle);
|
||||||
buf[fileSize] = 0;
|
buf[fileSize] = '\0';
|
||||||
Game::FS_FCloseFile(fileHandle);
|
Game::FS_FCloseFile(fileHandle);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
@ -62,6 +62,59 @@ namespace Components
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char* RawFiles::Com_LoadInfoString_LoadObj(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer)
|
||||||
|
{
|
||||||
|
auto fileHandle = 0;
|
||||||
|
|
||||||
|
const auto fileLen = Game::FS_FOpenFileByMode(fileName, &fileHandle, Game::FS_READ);
|
||||||
|
if (fileLen < 0)
|
||||||
|
{
|
||||||
|
Logger::Debug("Could not load {} [{}] as rawfile", fileDesc, fileName);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto identLen = static_cast<int>(std::strlen(ident));
|
||||||
|
Game::FS_Read(loadBuffer, identLen, fileHandle);
|
||||||
|
loadBuffer[identLen] = '\0';
|
||||||
|
|
||||||
|
if (std::strncmp(loadBuffer, ident, identLen) != 0)
|
||||||
|
{
|
||||||
|
Game::Com_Error(Game::ERR_DROP, "\x15" "File [%s] is not a %s\n", fileName, fileDesc);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fileLen - identLen) >= 0x4000)
|
||||||
|
{
|
||||||
|
Game::Com_Error(Game::ERR_DROP, "\x15" "File [%s] is too long of a %s to parse\n", fileName, fileDesc);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game::FS_Read(loadBuffer, fileLen - identLen, fileHandle);
|
||||||
|
loadBuffer[fileLen - identLen] = '\0';
|
||||||
|
Game::FS_FCloseFile(fileHandle);
|
||||||
|
|
||||||
|
return loadBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* RawFiles::Com_LoadInfoString_Hk(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer)
|
||||||
|
{
|
||||||
|
const char* buffer;
|
||||||
|
|
||||||
|
buffer = Com_LoadInfoString_LoadObj(fileName, fileDesc, ident, loadBuffer);
|
||||||
|
if (!buffer)
|
||||||
|
{
|
||||||
|
buffer = Game::Com_LoadInfoString_FastFile(fileName, fileDesc, ident, loadBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Game::Info_Validate(buffer))
|
||||||
|
{
|
||||||
|
Game::Com_Error(Game::ERR_DROP, "\x15" "File [%s] is not a valid %s\n", fileName, fileDesc);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
RawFiles::RawFiles()
|
RawFiles::RawFiles()
|
||||||
{
|
{
|
||||||
// remove fs_game check for moddable rawfiles - allows non-fs_game to modify rawfiles
|
// remove fs_game check for moddable rawfiles - allows non-fs_game to modify rawfiles
|
||||||
@ -69,6 +122,7 @@ namespace Components
|
|||||||
|
|
||||||
Utils::Hook(0x4DA0D0, ReadRawFile, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x4DA0D0, ReadRawFile, HOOK_JUMP).install()->quick();
|
||||||
Utils::Hook(0x631640, GetMenuBuffer, HOOK_JUMP).install()->quick();
|
Utils::Hook(0x631640, GetMenuBuffer, HOOK_JUMP).install()->quick();
|
||||||
|
Utils::Hook(0x463500, Com_LoadInfoString_Hk, HOOK_JUMP).install()->quick();
|
||||||
|
|
||||||
Command::Add("dumpraw", [](Command::Params* params)
|
Command::Add("dumpraw", [](Command::Params* params)
|
||||||
{
|
{
|
||||||
|
@ -11,5 +11,7 @@ namespace Components
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static char* GetMenuBuffer(const char* filename);
|
static char* GetMenuBuffer(const char* filename);
|
||||||
|
static char* Com_LoadInfoString_LoadObj(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer);
|
||||||
|
static const char* Com_LoadInfoString_Hk(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
std::unordered_map<std::int32_t, std::function<bool(Command::Params*)>> ServerCommands::Commands;
|
std::unordered_map<std::int32_t, ServerCommands::serverCommandHandler> ServerCommands::Commands;
|
||||||
|
|
||||||
void ServerCommands::OnCommand(std::int32_t cmd, std::function<bool(Command::Params*)> callback)
|
void ServerCommands::OnCommand(std::int32_t cmd, const serverCommandHandler& callback)
|
||||||
{
|
{
|
||||||
ServerCommands::Commands.insert_or_assign(cmd, std::move(callback));
|
Commands.insert_or_assign(cmd, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerCommands::OnServerCommand()
|
bool ServerCommands::OnServerCommand()
|
||||||
{
|
{
|
||||||
Command::ClientParams params;
|
Command::ClientParams params;
|
||||||
|
|
||||||
for (const auto& [id, callback] : ServerCommands::Commands)
|
for (const auto& [id, callback] : Commands)
|
||||||
{
|
{
|
||||||
if (params.size() >= 1)
|
if (params.size() >= 1)
|
||||||
{
|
{
|
||||||
@ -33,7 +33,10 @@ namespace Components
|
|||||||
{
|
{
|
||||||
push eax
|
push eax
|
||||||
pushad
|
pushad
|
||||||
|
|
||||||
|
// Missing localClientNum!
|
||||||
call ServerCommands::OnServerCommand
|
call ServerCommands::OnServerCommand
|
||||||
|
|
||||||
mov [esp + 20h], eax
|
mov [esp + 20h], eax
|
||||||
popad
|
popad
|
||||||
pop eax
|
pop eax
|
||||||
@ -63,7 +66,7 @@ namespace Components
|
|||||||
ServerCommands::ServerCommands()
|
ServerCommands::ServerCommands()
|
||||||
{
|
{
|
||||||
// Server command receive hook
|
// Server command receive hook
|
||||||
Utils::Hook(0x59449F, ServerCommands::CG_DeployServerCommand_Stub).install()->quick();
|
Utils::Hook(0x59449F, CG_DeployServerCommand_Stub).install()->quick();
|
||||||
Utils::Hook::Nop(0x5944A4, 6);
|
Utils::Hook::Nop(0x5944A4, 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,11 @@ namespace Components
|
|||||||
public:
|
public:
|
||||||
ServerCommands();
|
ServerCommands();
|
||||||
|
|
||||||
static void OnCommand(std::int32_t cmd, std::function<bool(Command::Params*)> callback);
|
using serverCommandHandler = std::function<bool(Command::Params*)>;
|
||||||
|
static void OnCommand(std::int32_t cmd, const serverCommandHandler& callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static std::unordered_map<std::int32_t, std::function<bool(Command::Params*)>> Commands;
|
static std::unordered_map<std::int32_t, serverCommandHandler> Commands;
|
||||||
|
|
||||||
static bool OnServerCommand();
|
static bool OnServerCommand();
|
||||||
static void CG_DeployServerCommand_Stub();
|
static void CG_DeployServerCommand_Stub();
|
||||||
|
@ -118,7 +118,6 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Utils::InfoString info;
|
Utils::InfoString info;
|
||||||
|
|
||||||
// TODO: Possibly add all Dvar starting with _
|
|
||||||
info.set("admin", Dvar::Var("_Admin").get<const char*>());
|
info.set("admin", Dvar::Var("_Admin").get<const char*>());
|
||||||
info.set("website", Dvar::Var("_Website").get<const char*>());
|
info.set("website", Dvar::Var("_Website").get<const char*>());
|
||||||
info.set("email", Dvar::Var("_Email").get<const char*>());
|
info.set("email", Dvar::Var("_Email").get<const char*>());
|
||||||
@ -141,6 +140,7 @@ namespace Components
|
|||||||
info.set("sv_maxclients", Utils::String::VA("%i", maxClientCount));
|
info.set("sv_maxclients", Utils::String::VA("%i", maxClientCount));
|
||||||
info.set("protocol", Utils::String::VA("%i", PROTOCOL));
|
info.set("protocol", Utils::String::VA("%i", PROTOCOL));
|
||||||
info.set("shortversion", SHORTVERSION);
|
info.set("shortversion", SHORTVERSION);
|
||||||
|
info.set("version", (*Game::version)->current.string);
|
||||||
info.set("mapname", (*Game::sv_mapname)->current.string);
|
info.set("mapname", (*Game::sv_mapname)->current.string);
|
||||||
info.set("isPrivate", (Dvar::Var("g_password").get<std::string>().empty() ? "0" : "1"));
|
info.set("isPrivate", (Dvar::Var("g_password").get<std::string>().empty() ? "0" : "1"));
|
||||||
info.set("checksum", Utils::String::VA("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(Utils::String::VA("%u", Game::Sys_Milliseconds()))));
|
info.set("checksum", Utils::String::VA("%X", Utils::Cryptography::JenkinsOneAtATime::Compute(Utils::String::VA("%u", Game::Sys_Milliseconds()))));
|
||||||
@ -162,7 +162,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
info.set("matchtype", "1");
|
info.set("matchtype", "1");
|
||||||
}
|
}
|
||||||
else if ((*Game::com_sv_running)->current.enabled) // Match hosting
|
else if (Dedicated::IsRunning()) // Match hosting
|
||||||
{
|
{
|
||||||
info.set("matchtype", "2");
|
info.set("matchtype", "2");
|
||||||
}
|
}
|
||||||
@ -193,24 +193,24 @@ namespace Components
|
|||||||
// Add uifeeder
|
// Add uifeeder
|
||||||
UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer);
|
UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer);
|
||||||
|
|
||||||
Network::OnServerPacket("getStatus", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
Network::OnClientPacket("getStatus", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||||
{
|
{
|
||||||
std::string playerList;
|
std::string playerList;
|
||||||
|
|
||||||
Utils::InfoString info = ServerInfo::GetInfo();
|
Utils::InfoString info = ServerInfo::GetInfo();
|
||||||
info.set("challenge", Utils::ParseChallenge(data));
|
info.set("challenge", Utils::ParseChallenge(data));
|
||||||
|
|
||||||
for (auto i = 0; i < atoi(info.get("sv_maxclients").data()); ++i) // Maybe choose 18 here?
|
for (std::size_t i = 0; i < Game::MAX_CLIENTS; ++i)
|
||||||
{
|
{
|
||||||
auto score = 0;
|
auto score = 0;
|
||||||
auto ping = 0;
|
auto ping = 0;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
if ((*Game::com_sv_running)->current.enabled)
|
if (Dedicated::IsRunning())
|
||||||
{
|
{
|
||||||
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
||||||
|
|
||||||
score = Game::SV_GameClientNum_Score(i);
|
score = Game::SV_GameClientNum_Score(static_cast<int>(i));
|
||||||
ping = Game::svs_clients[i].ping;
|
ping = Game::svs_clients[i].ping;
|
||||||
name = Game::svs_clients[i].name;
|
name = Game::svs_clients[i].name;
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace Components
|
|||||||
Dvar::Var ServerList::NETServerQueryLimit;
|
Dvar::Var ServerList::NETServerQueryLimit;
|
||||||
Dvar::Var ServerList::NETServerFrames;
|
Dvar::Var ServerList::NETServerFrames;
|
||||||
|
|
||||||
bool ServerList::useMasterServer = true;
|
bool ServerList::UseMasterServer = true;
|
||||||
|
|
||||||
std::vector<ServerList::ServerInfo>* ServerList::GetList()
|
std::vector<ServerList::ServerInfo>* ServerList::GetList()
|
||||||
{
|
{
|
||||||
@ -51,7 +51,7 @@ namespace Components
|
|||||||
|
|
||||||
bool ServerList::IsOnlineList()
|
bool ServerList::IsOnlineList()
|
||||||
{
|
{
|
||||||
return (Monitor::IsEnabled() || Dvar::Var("ui_netSource").get<int>() == 1);
|
return (Dvar::Var("ui_netSource").get<int>() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int ServerList::GetServerCount()
|
unsigned int ServerList::GetServerCount()
|
||||||
@ -299,13 +299,13 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Logger::Print("Could not resolve address for {}:{}", masterServerName, masterPort);
|
Logger::Print("Could not resolve address for {}:{}", masterServerName, masterPort);
|
||||||
Toast::Show("cardicon_headshot", "^1Error", Utils::String::VA("Could not resolve address for %s:%u", masterServerName, masterPort), 5000);
|
Toast::Show("cardicon_headshot", "^1Error", Utils::String::VA("Could not resolve address for %s:%u", masterServerName, masterPort), 5000);
|
||||||
useMasterServer = false;
|
UseMasterServer = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000);
|
Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000);
|
||||||
|
|
||||||
useMasterServer = true;
|
UseMasterServer = true;
|
||||||
|
|
||||||
ServerList::RefreshContainer.awatingList = true;
|
ServerList::RefreshContainer.awatingList = true;
|
||||||
ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds();
|
ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds();
|
||||||
@ -327,7 +327,17 @@ namespace Components
|
|||||||
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
|
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
|
||||||
if (!parseData.empty())
|
if (!parseData.empty())
|
||||||
{
|
{
|
||||||
const nlohmann::json object = nlohmann::json::parse(parseData);
|
nlohmann::json object;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object = nlohmann::json::parse(parseData);
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!object.is_array())
|
if (!object.is_array())
|
||||||
{
|
{
|
||||||
Logger::Print("Favourites storage file is invalid!\n");
|
Logger::Print("Favourites storage file is invalid!\n");
|
||||||
@ -363,7 +373,16 @@ namespace Components
|
|||||||
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
|
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
|
||||||
if (!parseData.empty())
|
if (!parseData.empty())
|
||||||
{
|
{
|
||||||
const nlohmann::json object = nlohmann::json::parse(parseData);
|
nlohmann::json object;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object = nlohmann::json::parse(parseData);
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!object.is_array())
|
if (!object.is_array())
|
||||||
{
|
{
|
||||||
@ -409,7 +428,17 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nlohmann::json object = nlohmann::json::parse(parseData);
|
nlohmann::json object;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object = nlohmann::json::parse(parseData);
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!object.is_array())
|
if (!object.is_array())
|
||||||
{
|
{
|
||||||
Logger::Print("Favourites storage file is invalid!\n");
|
Logger::Print("Favourites storage file is invalid!\n");
|
||||||
@ -675,7 +704,7 @@ namespace Components
|
|||||||
Logger::Print("We haven't received a response from the master within {} seconds!\n", (Game::Sys_Milliseconds() - ServerList::RefreshContainer.awaitTime) / 1000);
|
Logger::Print("We haven't received a response from the master within {} seconds!\n", (Game::Sys_Milliseconds() - ServerList::RefreshContainer.awaitTime) / 1000);
|
||||||
Toast::Show("cardicon_headshot", "^1Error", "Failed to reach master server, using node servers instead.", 5000);
|
Toast::Show("cardicon_headshot", "^1Error", "Failed to reach master server, using node servers instead.", 5000);
|
||||||
|
|
||||||
useMasterServer = false;
|
UseMasterServer = false;
|
||||||
Node::Synchronize();
|
Node::Synchronize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ namespace Components
|
|||||||
static void UpdateVisibleInfo();
|
static void UpdateVisibleInfo();
|
||||||
|
|
||||||
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
|
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
|
||||||
static bool useMasterServer;
|
static bool UseMasterServer;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum Column
|
enum Column
|
||||||
|
@ -20,7 +20,7 @@ namespace Components
|
|||||||
|
|
||||||
Console::FreeNativeConsole();
|
Console::FreeNativeConsole();
|
||||||
|
|
||||||
if (Loader::IsPerformingUnitTests() || Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) return;
|
if (Loader::IsPerformingUnitTests() || Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
||||||
|
|
||||||
Singleton::FirstInstance = (CreateMutexA(nullptr, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS);
|
Singleton::FirstInstance = (CreateMutexA(nullptr, FALSE, "iw4x_mutex") && GetLastError() != ERROR_ALREADY_EXISTS);
|
||||||
|
|
||||||
|
@ -4,27 +4,29 @@ namespace Components
|
|||||||
{
|
{
|
||||||
int SlowMotion::Delay = 0;
|
int SlowMotion::Delay = 0;
|
||||||
|
|
||||||
void SlowMotion::ApplySlowMotion(int timePassed)
|
const Game::dvar_t* SlowMotion::cg_drawDisconnect;
|
||||||
|
|
||||||
|
void SlowMotion::Com_UpdateSlowMotion(int msec)
|
||||||
{
|
{
|
||||||
if (SlowMotion::Delay <= 0)
|
if (Delay <= 0)
|
||||||
{
|
{
|
||||||
Utils::Hook::Call<void(int)>(0x60B2D0)(timePassed);
|
Game::Com_UpdateSlowMotion(msec);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SlowMotion::Delay -= timePassed;
|
Delay -= msec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__declspec(naked) void SlowMotion::ApplySlowMotionStub()
|
__declspec(naked) void SlowMotion::Com_UpdateSlowMotion_Stub()
|
||||||
{
|
{
|
||||||
__asm
|
__asm
|
||||||
{
|
{
|
||||||
pushad
|
pushad
|
||||||
|
|
||||||
push [esp + 24h]
|
push [esp + 0x20 + 0x4]
|
||||||
call SlowMotion::ApplySlowMotion
|
call Com_UpdateSlowMotion
|
||||||
add esp, 4h
|
add esp, 0x4
|
||||||
|
|
||||||
popad
|
popad
|
||||||
|
|
||||||
@ -32,23 +34,23 @@ namespace Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlowMotion::SetSlowMotion()
|
void SlowMotion::ScrCmd_SetSlowMotion_Stub()
|
||||||
{
|
{
|
||||||
int duration = 1000;
|
auto duration = 1000;
|
||||||
float start = Game::Scr_GetFloat(0);
|
auto start = Game::Scr_GetFloat(0);
|
||||||
float end = 1.0f;
|
auto end = 1.0f;
|
||||||
|
|
||||||
if (Game::Scr_GetNumParam() >= 2u)
|
if (Game::Scr_GetNumParam() >= 2)
|
||||||
{
|
{
|
||||||
end = Game::Scr_GetFloat(1);
|
end = Game::Scr_GetFloat(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Game::Scr_GetNumParam() >= 3u)
|
if (Game::Scr_GetNumParam() >= 3)
|
||||||
{
|
{
|
||||||
duration = static_cast<int>(Game::Scr_GetFloat(2) * 1000.0f);
|
duration = static_cast<int>(Game::Scr_GetFloat(2) * 1000.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
int delay = 0;
|
auto delay = 0;
|
||||||
|
|
||||||
if (start > end)
|
if (start > end)
|
||||||
{
|
{
|
||||||
@ -65,30 +67,32 @@ namespace Components
|
|||||||
duration = duration - delay;
|
duration = duration - delay;
|
||||||
|
|
||||||
Game::Com_SetSlowMotion(start, end, duration);
|
Game::Com_SetSlowMotion(start, end, duration);
|
||||||
SlowMotion::Delay = delay;
|
Delay = delay;
|
||||||
|
|
||||||
// set snapshot num to 1 behind (T6 does this, why shouldn't we?)
|
// Set snapshot num to 1 behind (T6 does this, why shouldn't we?)
|
||||||
for (int i = 0; i < *Game::svs_clientCount; ++i)
|
for (auto i = 0; i < *Game::svs_clientCount; ++i)
|
||||||
{
|
{
|
||||||
Game::svs_clients[i].nextSnapshotTime = *Game::svs_time - 1;
|
Game::svs_clients[i].nextSnapshotTime = *Game::svs_time - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlowMotion::DrawConnectionInterruptedStub(int /*a1*/)
|
void SlowMotion::CG_DrawDisconnect_Stub(const int localClientNum)
|
||||||
{
|
{
|
||||||
// if (!*reinterpret_cast<bool*>(0x1AD8ED0) && !*reinterpret_cast<bool*>(0x1AD8EEC) && !*reinterpret_cast<int*>(0x1AD78F8))
|
if (cg_drawDisconnect->current.enabled)
|
||||||
// {
|
{
|
||||||
// Utils::Hook::Call<void(int)>(0x454A70)(a1);
|
Game::CG_DrawDisconnect(localClientNum);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SlowMotion::SlowMotion()
|
SlowMotion::SlowMotion()
|
||||||
{
|
{
|
||||||
SlowMotion::Delay = 0;
|
cg_drawDisconnect = Game::Dvar_RegisterBool("cg_drawDisconnect", false, Game::DVAR_NONE, "Draw connection interrupted");
|
||||||
Utils::Hook(0x5F5FF2, SlowMotion::SetSlowMotion, HOOK_JUMP).install()->quick();
|
|
||||||
Utils::Hook(0x60B38A, SlowMotion::ApplySlowMotionStub, HOOK_CALL).install()->quick();
|
|
||||||
|
|
||||||
Utils::Hook(0x4A54ED, SlowMotion::DrawConnectionInterruptedStub, HOOK_CALL).install()->quick();
|
Delay = 0;
|
||||||
Utils::Hook(0x4A54FB, SlowMotion::DrawConnectionInterruptedStub, HOOK_CALL).install()->quick();
|
Utils::Hook(0x5F5FF2, ScrCmd_SetSlowMotion_Stub, HOOK_JUMP).install()->quick();
|
||||||
|
Utils::Hook(0x60B38A, Com_UpdateSlowMotion_Stub, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
|
Utils::Hook(0x4A54ED, CG_DrawDisconnect_Stub, HOOK_CALL).install()->quick();
|
||||||
|
Utils::Hook(0x4A54FB, CG_DrawDisconnect_Stub, HOOK_CALL).install()->quick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,13 @@ namespace Components
|
|||||||
private:
|
private:
|
||||||
static int Delay;
|
static int Delay;
|
||||||
|
|
||||||
static void SetSlowMotion();
|
static const Game::dvar_t* cg_drawDisconnect;
|
||||||
static void ApplySlowMotion(int timePassed);
|
|
||||||
static void ApplySlowMotionStub();
|
|
||||||
|
|
||||||
static void DrawConnectionInterruptedStub(int a1);
|
static void Com_UpdateSlowMotion(int timePassed);
|
||||||
|
static void Com_UpdateSlowMotion_Stub();
|
||||||
|
|
||||||
|
static void ScrCmd_SetSlowMotion_Stub();
|
||||||
|
|
||||||
|
static void CG_DrawDisconnect_Stub(int localClientNum);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
#include <STDInclude.hpp>
|
#include <STDInclude.hpp>
|
||||||
|
#include "GSC/Script.hpp"
|
||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
int64_t* Stats::GetStatsID()
|
std::int64_t* Stats::GetStatsID()
|
||||||
{
|
{
|
||||||
static int64_t id = 0x110000100001337;
|
static std::int64_t id = 0x110000100001337;
|
||||||
return &id;
|
return &id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ namespace Components
|
|||||||
|
|
||||||
void Stats::UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
void Stats::UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
||||||
{
|
{
|
||||||
Stats::SendStats();
|
SendStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length)
|
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length)
|
||||||
@ -73,18 +74,64 @@ namespace Components
|
|||||||
return Utils::Hook::Call<int(char*, const char*, const char*, size_t)>(0x426450)(dest, folder, buffer, length);
|
return Utils::Hook::Call<int(char*, const char*, const char*, size_t)>(0x426450)(dest, folder, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Stats::AddScriptFunctions()
|
||||||
|
{
|
||||||
|
Script::AddMethod("GetStat", [](const Game::scr_entref_t entref)
|
||||||
|
{
|
||||||
|
const auto* ent = Game::GetPlayerEntity(entref);
|
||||||
|
const auto index = Game::Scr_GetInt(0);
|
||||||
|
|
||||||
|
if (index < 0 || index > 3499)
|
||||||
|
{
|
||||||
|
Game::Scr_ParamError(0, Utils::String::VA("GetStat: invalid index %i", index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->client->sess.connected <= Game::CON_DISCONNECTED)
|
||||||
|
{
|
||||||
|
Game::Scr_Error("GetStat: called on a disconnected player");
|
||||||
|
}
|
||||||
|
|
||||||
|
Game::Scr_AddInt(Game::SV_GetClientStat(ent->s.number, index));
|
||||||
|
});
|
||||||
|
|
||||||
|
Script::AddMethod("SetStat", [](const Game::scr_entref_t entref)
|
||||||
|
{
|
||||||
|
const auto* ent = Game::GetPlayerEntity(entref);
|
||||||
|
|
||||||
|
const auto iNumParms = Game::Scr_GetNumParam();
|
||||||
|
if (iNumParms != 2)
|
||||||
|
{
|
||||||
|
Game::Scr_Error(Utils::String::VA("GetStat: takes 2 arguments, got %u.\n", iNumParms));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto index = Game::Scr_GetInt(0);
|
||||||
|
if (index < 0 || index > 3499)
|
||||||
|
{
|
||||||
|
Game::Scr_ParamError(0, Utils::String::VA("setstat: invalid index %i", index));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto value = Game::Scr_GetInt(1);
|
||||||
|
if (index < 2000 && (value < 0 || value > 255))
|
||||||
|
{
|
||||||
|
Game::Scr_ParamError(1, Utils::String::VA("setstat: index %i is a byte value, and you're trying to set it to %i", index, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Game::SV_SetClientStat(ent->s.number, index, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Stats::Stats()
|
Stats::Stats()
|
||||||
{
|
{
|
||||||
// This UIScript should be added in the onClose code of the cac_popup menu,
|
// This UIScript should be added in the onClose code of the cac_popup menu,
|
||||||
// so everytime the create-a-class window is closed, and a client is connected
|
// so everytime the create-a-class window is closed, and a client is connected
|
||||||
// to a server, the stats data of the client will be reuploaded to the server.
|
// to a server, the stats data of the client will be reuploaded to the server.
|
||||||
// allowing the player to change their classes while connected to a server.
|
// allowing the player to change their classes while connected to a server.
|
||||||
UIScript::Add("UpdateClasses", Stats::UpdateClasses);
|
UIScript::Add("UpdateClasses", UpdateClasses);
|
||||||
|
|
||||||
// Allow playerdata to be changed while connected to a server
|
// Allow playerdata to be changed while connected to a server
|
||||||
Utils::Hook::Set<BYTE>(0x4376FD, 0xEB);
|
Utils::Hook::Set<BYTE>(0x4376FD, 0xEB);
|
||||||
|
|
||||||
// ToDo: Allow playerdata changes in setPlayerData UI script.
|
// TODO: Allow playerdata changes in setPlayerData UI script.
|
||||||
|
|
||||||
// Rename stat file
|
// Rename stat file
|
||||||
Utils::Hook::SetString(0x71C048, "iw4x.stat");
|
Utils::Hook::SetString(0x71C048, "iw4x.stat");
|
||||||
@ -92,8 +139,9 @@ namespace Components
|
|||||||
// Patch stats steamid
|
// Patch stats steamid
|
||||||
Utils::Hook::Nop(0x682EBF, 20);
|
Utils::Hook::Nop(0x682EBF, 20);
|
||||||
Utils::Hook::Nop(0x6830B1, 20);
|
Utils::Hook::Nop(0x6830B1, 20);
|
||||||
Utils::Hook(0x682EBF, Stats::GetStatsID, HOOK_CALL).install()->quick();
|
|
||||||
Utils::Hook(0x6830B1, Stats::GetStatsID, HOOK_CALL).install()->quick();
|
Utils::Hook(0x682EBF, GetStatsID, HOOK_CALL).install()->quick();
|
||||||
|
Utils::Hook(0x6830B1, GetStatsID, HOOK_CALL).install()->quick();
|
||||||
//Utils::Hook::Set<BYTE>(0x68323A, 0xEB);
|
//Utils::Hook::Set<BYTE>(0x68323A, 0xEB);
|
||||||
|
|
||||||
// Never use remote stat saving
|
// Never use remote stat saving
|
||||||
@ -103,6 +151,34 @@ namespace Components
|
|||||||
Utils::Hook::Nop(0x402CE6, 2);
|
Utils::Hook::Nop(0x402CE6, 2);
|
||||||
|
|
||||||
// Write stats to mod folder if a mod is loaded
|
// Write stats to mod folder if a mod is loaded
|
||||||
Utils::Hook(0x682F7B, Stats::SaveStats, HOOK_CALL).install()->quick();
|
Utils::Hook(0x682F7B, SaveStats, HOOK_CALL).install()->quick();
|
||||||
|
|
||||||
|
AddScriptFunctions();
|
||||||
|
|
||||||
|
// Skip silly Com_Error (LiveStorage_SetStat)
|
||||||
|
Utils::Hook::Set<BYTE>(0x4CC5F9, 0xEB);
|
||||||
|
|
||||||
|
// 'M' Seems to be used on Xbox only for parsing platform specific ranks
|
||||||
|
ServerCommands::OnCommand('M', [](Command::Params* params)
|
||||||
|
{
|
||||||
|
const auto* arg1 = params->get(1);
|
||||||
|
const auto* arg2 = params->get(2);
|
||||||
|
|
||||||
|
Game::LiveStorage_SetStat(Game::CL_ControllerIndexFromClientNum(0), std::atoi(arg1), std::atoi(arg2));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Command::Add("statGet", []([[maybe_unused]] Command::Params* params)
|
||||||
|
{
|
||||||
|
if (params->size() < 2)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_SERVER, "statget usage: statget <index>\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto index = std::atoi(params->get(1));
|
||||||
|
const auto stat = Game::LiveStorage_GetStat(0, index);
|
||||||
|
Logger::Print(Game::CON_CHANNEL_SYSTEM, "Stat {}: {}\n", index, stat);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,12 @@ namespace Components
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static void UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
|
static void UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
|
||||||
|
|
||||||
static void SendStats();
|
static void SendStats();
|
||||||
static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length);
|
static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length);
|
||||||
|
|
||||||
static int64_t* GetStatsID();
|
static std::int64_t* GetStatsID();
|
||||||
|
|
||||||
|
static void AddScriptFunctions();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Utils::Memory::Allocator StructuredData::MemAllocator;
|
Utils::Memory::Allocator StructuredData::MemAllocator;
|
||||||
|
|
||||||
const char* StructuredData::EnumTranslation[ENUM_MAX] =
|
const char* StructuredData::EnumTranslation[COUNT] =
|
||||||
{
|
{
|
||||||
"features",
|
"features",
|
||||||
"weapons",
|
"weapons",
|
||||||
@ -23,7 +23,7 @@ namespace Components
|
|||||||
|
|
||||||
void StructuredData::PatchPlayerDataEnum(Game::StructuredDataDef* data, StructuredData::PlayerDataType type, std::vector<std::string>& entries)
|
void StructuredData::PatchPlayerDataEnum(Game::StructuredDataDef* data, StructuredData::PlayerDataType type, std::vector<std::string>& entries)
|
||||||
{
|
{
|
||||||
if (!data || type >= StructuredData::PlayerDataType::ENUM_MAX) return;
|
if (!data || type >= StructuredData::PlayerDataType::COUNT) return;
|
||||||
|
|
||||||
Game::StructuredDataEnum* dataEnum = &data->enums[type];
|
Game::StructuredDataEnum* dataEnum = &data->enums[type];
|
||||||
|
|
||||||
@ -192,8 +192,8 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<int, std::vector<std::vector<std::string>>> patchDefinitions;
|
std::unordered_map<int, std::vector<std::vector<std::string>>> patchDefinitions;
|
||||||
std::map<int, std::unordered_map<std::string, std::string>> otherPatchDefinitions;
|
std::unordered_map<int, std::unordered_map<std::string, std::string>> otherPatchDefinitions;
|
||||||
|
|
||||||
// First check if all versions are present
|
// First check if all versions are present
|
||||||
for (int i = 156;; ++i)
|
for (int i = 156;; ++i)
|
||||||
@ -205,12 +205,14 @@ namespace Components
|
|||||||
std::vector<std::vector<std::string>> enumContainer;
|
std::vector<std::vector<std::string>> enumContainer;
|
||||||
std::unordered_map<std::string, std::string> otherPatches;
|
std::unordered_map<std::string, std::string> otherPatches;
|
||||||
|
|
||||||
std::string errors;
|
nlohmann::json defData;
|
||||||
nlohmann::json defData = nlohmann::json::parse(definition.getBuffer());
|
try
|
||||||
|
|
||||||
if (!errors.empty())
|
|
||||||
{
|
{
|
||||||
Logger::Error(Game::ERR_FATAL, "Parsing patch file '{}' for PlayerDataDef version {} failed: {}", definition.getName(), i, errors);
|
defData = nlohmann::json::parse(definition.getBuffer());
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +222,7 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::size_t pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType)
|
for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType)
|
||||||
{
|
{
|
||||||
auto enumData = defData[StructuredData::EnumTranslation[pType]];
|
auto enumData = defData[StructuredData::EnumTranslation[pType]];
|
||||||
|
|
||||||
@ -228,7 +230,7 @@ namespace Components
|
|||||||
|
|
||||||
if (enumData.is_array())
|
if (enumData.is_array())
|
||||||
{
|
{
|
||||||
for (auto rawEntry : enumData)
|
for (const auto& rawEntry : enumData)
|
||||||
{
|
{
|
||||||
if (rawEntry.is_string())
|
if (rawEntry.is_string())
|
||||||
{
|
{
|
||||||
@ -261,7 +263,7 @@ namespace Components
|
|||||||
if (patchDefinitions.empty()) return;
|
if (patchDefinitions.empty()) return;
|
||||||
|
|
||||||
// Reallocate the definition
|
// Reallocate the definition
|
||||||
Game::StructuredDataDef* newData = StructuredData::MemAllocator.allocateArray<Game::StructuredDataDef>(data->defCount + patchDefinitions.size());
|
auto* newData = StructuredData::MemAllocator.allocateArray<Game::StructuredDataDef>(data->defCount + patchDefinitions.size());
|
||||||
std::memcpy(&newData[patchDefinitions.size()], data->defs, sizeof Game::StructuredDataDef * data->defCount);
|
std::memcpy(&newData[patchDefinitions.size()], data->defs, sizeof Game::StructuredDataDef * data->defCount);
|
||||||
|
|
||||||
// Prepare the buffers
|
// Prepare the buffers
|
||||||
@ -271,7 +273,7 @@ namespace Components
|
|||||||
newData[i].version = (patchDefinitions.size() - i) + 155;
|
newData[i].version = (patchDefinitions.size() - i) + 155;
|
||||||
|
|
||||||
// Reallocate the enum array
|
// Reallocate the enum array
|
||||||
Game::StructuredDataEnum* newEnums = StructuredData::MemAllocator.allocateArray<Game::StructuredDataEnum>(data->defs->enumCount);
|
auto* newEnums = StructuredData::MemAllocator.allocateArray<Game::StructuredDataEnum>(data->defs->enumCount);
|
||||||
std::memcpy(newEnums, data->defs->enums, sizeof Game::StructuredDataEnum * data->defs->enumCount);
|
std::memcpy(newEnums, data->defs->enums, sizeof Game::StructuredDataEnum * data->defs->enumCount);
|
||||||
newData[i].enums = newEnums;
|
newData[i].enums = newEnums;
|
||||||
}
|
}
|
||||||
@ -292,14 +294,14 @@ namespace Components
|
|||||||
auto otherData = otherPatchDefinitions[newData[i].version];
|
auto otherData = otherPatchDefinitions[newData[i].version];
|
||||||
|
|
||||||
// Invalid patch data
|
// Invalid patch data
|
||||||
if (patchData.size() != StructuredData::PlayerDataType::ENUM_MAX)
|
if (patchData.size() != StructuredData::PlayerDataType::COUNT)
|
||||||
{
|
{
|
||||||
Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} wasn't parsed correctly!", newData[i].version);
|
Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} wasn't parsed correctly!", newData[i].version);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the patch data
|
// Apply the patch data
|
||||||
for (unsigned int pType = 0; pType < StructuredData::PlayerDataType::ENUM_MAX; ++pType)
|
for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType)
|
||||||
{
|
{
|
||||||
if (!patchData[pType].empty())
|
if (!patchData[pType].empty())
|
||||||
{
|
{
|
||||||
|
@ -7,20 +7,21 @@ namespace Components
|
|||||||
public:
|
public:
|
||||||
enum PlayerDataType
|
enum PlayerDataType
|
||||||
{
|
{
|
||||||
ENUM_FEATURES,
|
FEATURES,
|
||||||
ENUM_WEAPONS,
|
WEAPONS,
|
||||||
ENUM_ATTACHEMENTS,
|
ATTACHEMENTS,
|
||||||
ENUM_CHALLENGES,
|
CHALLENGES,
|
||||||
ENUM_CAMOS,
|
CAMOS,
|
||||||
ENUM_PERKS,
|
PERKS,
|
||||||
ENUM_KILLSTREAKS,
|
KILLSTREAKS,
|
||||||
ENUM_ACCOLADES,
|
ACCOLADES,
|
||||||
ENUM_CARDICONS,
|
CARDICONS,
|
||||||
ENUM_CARDTITLES,
|
CARDTITLES,
|
||||||
ENUM_CARDNAMEPLATES,
|
CARDNAMEPLATES,
|
||||||
ENUM_TEAMS,
|
TEAMS,
|
||||||
ENUM_GAMETYPES,
|
GAMETYPES,
|
||||||
ENUM_MAX
|
|
||||||
|
COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
StructuredData();
|
StructuredData();
|
||||||
@ -34,6 +35,6 @@ namespace Components
|
|||||||
static void PatchCustomClassLimit(Game::StructuredDataDef* data, int count);
|
static void PatchCustomClassLimit(Game::StructuredDataDef* data, int count);
|
||||||
static Utils::Memory::Allocator MemAllocator;
|
static Utils::Memory::Allocator MemAllocator;
|
||||||
|
|
||||||
static const char* EnumTranslation[ENUM_MAX];
|
static const char* EnumTranslation[COUNT];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1260,7 +1260,7 @@ namespace Components
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextRenderer::StripColors(const char* in, char* out, size_t max)
|
void TextRenderer::StripColors(const char* in, char* out, std::size_t max)
|
||||||
{
|
{
|
||||||
if (!in || !out) return;
|
if (!in || !out) return;
|
||||||
|
|
||||||
@ -1287,12 +1287,12 @@ namespace Components
|
|||||||
|
|
||||||
std::string TextRenderer::StripColors(const std::string& in)
|
std::string TextRenderer::StripColors(const std::string& in)
|
||||||
{
|
{
|
||||||
char buffer[1024] = {0}; // 1024 is a lucky number in the engine
|
char buffer[1024]{}; // 1024 is a lucky number in the engine
|
||||||
StripColors(in.data(), buffer, sizeof(buffer));
|
StripColors(in.data(), buffer, sizeof(buffer));
|
||||||
return std::string(buffer);
|
return std::string(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextRenderer::StripMaterialTextIcons(const char* in, char* out, size_t max)
|
void TextRenderer::StripMaterialTextIcons(const char* in, char* out, std::size_t max)
|
||||||
{
|
{
|
||||||
if (!in || !out) return;
|
if (!in || !out) return;
|
||||||
|
|
||||||
@ -1339,7 +1339,7 @@ namespace Components
|
|||||||
return std::string(buffer);
|
return std::string(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextRenderer::StripAllTextIcons(const char* in, char* out, size_t max)
|
void TextRenderer::StripAllTextIcons(const char* in, char* out, std::size_t max)
|
||||||
{
|
{
|
||||||
if (!in || !out) return;
|
if (!in || !out) return;
|
||||||
|
|
||||||
|
@ -202,11 +202,11 @@ namespace Components
|
|||||||
static void DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow);
|
static void DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow);
|
||||||
static int R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font);
|
static int R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font);
|
||||||
static unsigned int ColorIndex(char index);
|
static unsigned int ColorIndex(char index);
|
||||||
static void StripColors(const char* in, char* out, size_t max);
|
static void StripColors(const char* in, char* out, std::size_t max);
|
||||||
static std::string StripColors(const std::string& in);
|
static std::string StripColors(const std::string& in);
|
||||||
static void StripMaterialTextIcons(const char* in, char* out, size_t max);
|
static void StripMaterialTextIcons(const char* in, char* out, std::size_t max);
|
||||||
static std::string StripMaterialTextIcons(const std::string& in);
|
static std::string StripMaterialTextIcons(const std::string& in);
|
||||||
static void StripAllTextIcons(const char* in, char* out, size_t max);
|
static void StripAllTextIcons(const char* in, char* out, std::size_t max);
|
||||||
static std::string StripAllTextIcons(const std::string& in);
|
static std::string StripAllTextIcons(const std::string& in);
|
||||||
|
|
||||||
static bool IsFontIcon(const char*& text, FontIconInfo& fontIcon);
|
static bool IsFontIcon(const char*& text, FontIconInfo& fontIcon);
|
||||||
|
@ -18,7 +18,7 @@ namespace Components
|
|||||||
|
|
||||||
void Theatre::RecordGamestateStub()
|
void Theatre::RecordGamestateStub()
|
||||||
{
|
{
|
||||||
int sequence = (*Game::serverMessageSequence - 1);
|
const auto sequence = (*Game::serverMessageSequence - 1);
|
||||||
Game::FS_WriteToDemo(&sequence, 4, *Game::demoFile);
|
Game::FS_WriteToDemo(&sequence, 4, *Game::demoFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,8 +56,8 @@ namespace Components
|
|||||||
Game::MSG_WriteData(&buf, &Theatre::BaselineSnapshot[Theatre::BaselineSnapshotMsgOff], Theatre::BaselineSnapshotMsgLen - Theatre::BaselineSnapshotMsgOff);
|
Game::MSG_WriteData(&buf, &Theatre::BaselineSnapshot[Theatre::BaselineSnapshotMsgOff], Theatre::BaselineSnapshotMsgLen - Theatre::BaselineSnapshotMsgOff);
|
||||||
Game::MSG_WriteByte(&buf, 6);
|
Game::MSG_WriteByte(&buf, 6);
|
||||||
|
|
||||||
int compressedSize = Game::MSG_WriteBitsCompress(false, reinterpret_cast<char*>(buf.data), cmpData, buf.cursize);
|
const auto compressedSize = Game::MSG_WriteBitsCompress(false, reinterpret_cast<char*>(buf.data), cmpData, buf.cursize);
|
||||||
int fileCompressedSize = compressedSize + 4;
|
const auto fileCompressedSize = compressedSize + 4;
|
||||||
|
|
||||||
int byte8 = 8;
|
int byte8 = 8;
|
||||||
char byte0 = 0;
|
char byte0 = 0;
|
||||||
@ -67,9 +67,9 @@ namespace Components
|
|||||||
Game::FS_WriteToDemo(&fileCompressedSize, 4, *Game::demoFile);
|
Game::FS_WriteToDemo(&fileCompressedSize, 4, *Game::demoFile);
|
||||||
Game::FS_WriteToDemo(&byte8, 4, *Game::demoFile);
|
Game::FS_WriteToDemo(&byte8, 4, *Game::demoFile);
|
||||||
|
|
||||||
for (int i = 0; i < compressedSize; i += 1024)
|
for (auto i = 0; i < compressedSize; i += 1024)
|
||||||
{
|
{
|
||||||
int size = std::min(compressedSize - i, 1024);
|
const auto size = std::min(compressedSize - i, 1024);
|
||||||
|
|
||||||
if (i + size >= sizeof(cmpData))
|
if (i + size >= sizeof(cmpData))
|
||||||
{
|
{
|
||||||
@ -186,7 +186,7 @@ namespace Components
|
|||||||
Theatre::CurrentSelection = 0;
|
Theatre::CurrentSelection = 0;
|
||||||
Theatre::Demos.clear();
|
Theatre::Demos.clear();
|
||||||
|
|
||||||
auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
const auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
||||||
|
|
||||||
for (auto demo : demos)
|
for (auto demo : demos)
|
||||||
{
|
{
|
||||||
@ -194,11 +194,17 @@ namespace Components
|
|||||||
|
|
||||||
if (meta.exists())
|
if (meta.exists())
|
||||||
{
|
{
|
||||||
std::string error;
|
nlohmann::json metaObject;
|
||||||
nlohmann::json metaObject = nlohmann::json::parse(meta.getBuffer());
|
try
|
||||||
|
|
||||||
if (metaObject.is_object())
|
|
||||||
{
|
{
|
||||||
|
metaObject = nlohmann::json::parse(meta.getBuffer());
|
||||||
|
}
|
||||||
|
catch (const nlohmann::json::parse_error& ex)
|
||||||
|
{
|
||||||
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Theatre::DemoInfo demoInfo;
|
Theatre::DemoInfo demoInfo;
|
||||||
demoInfo.name = demo.substr(0, demo.find_last_of("."));
|
demoInfo.name = demo.substr(0, demo.find_last_of("."));
|
||||||
demoInfo.author = metaObject["author"].get<std::string>();
|
demoInfo.author = metaObject["author"].get<std::string>();
|
||||||
@ -211,10 +217,9 @@ namespace Components
|
|||||||
Theatre::Demos.push_back(demoInfo);
|
Theatre::Demos.push_back(demoInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Reverse, latest demo first!
|
// Reverse, latest demo first!
|
||||||
std::reverse(Theatre::Demos.begin(), Theatre::Demos.end());
|
std::ranges::reverse(Theatre::Demos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Theatre::DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
void Theatre::DeleteDemo([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
#include <STDInclude.hpp>
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
__declspec(naked) void Threading::FrameEpilogueStub()
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
{
|
|
||||||
pop edi
|
|
||||||
pop esi
|
|
||||||
pop ebx
|
|
||||||
mov esp, ebp
|
|
||||||
pop ebp
|
|
||||||
retn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__declspec(naked) void Threading::PacketEventStub()
|
|
||||||
{
|
|
||||||
__asm
|
|
||||||
{
|
|
||||||
mov eax, 49F0B0h
|
|
||||||
call eax
|
|
||||||
mov eax, 458160h
|
|
||||||
jmp eax
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Threading::Threading()
|
|
||||||
{
|
|
||||||
// remove starting of server thread from Com_Init_Try_Block_Function
|
|
||||||
Utils::Hook::Nop(0x60BEC0, 5);
|
|
||||||
|
|
||||||
// make server thread function jump to per-frame stuff
|
|
||||||
Utils::Hook(0x627049, 0x6271CE, HOOK_JUMP).install()->quick();
|
|
||||||
|
|
||||||
// make SV_WaitServer insta-return
|
|
||||||
Utils::Hook::Set<BYTE>(0x4256F0, 0xC3);
|
|
||||||
|
|
||||||
// dvar setting function, unknown stuff related to server thread sync
|
|
||||||
Utils::Hook::Set<BYTE>(0x647781, 0xEB);
|
|
||||||
|
|
||||||
Utils::Hook(0x627695, 0x627040, HOOK_CALL).install()->quick();
|
|
||||||
Utils::Hook(0x43D1C7, Threading::PacketEventStub, HOOK_JUMP).install()->quick();
|
|
||||||
Utils::Hook(0x6272E3, Threading::FrameEpilogueStub, HOOK_JUMP).install()->quick();
|
|
||||||
|
|
||||||
// Make VA thread safe
|
|
||||||
Utils::Hook(0x4785B0, Utils::String::VA, HOOK_JUMP).install()->quick();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Components
|
|
||||||
{
|
|
||||||
class Threading : public Component
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Threading();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void FrameEpilogueStub();
|
|
||||||
static void PacketEventStub();
|
|
||||||
};
|
|
||||||
}
|
|
@ -144,7 +144,7 @@ namespace Components
|
|||||||
|
|
||||||
Toast::Toast()
|
Toast::Toast()
|
||||||
{
|
{
|
||||||
if (Dedicated::IsEnabled() || Monitor::IsEnabled() || ZoneBuilder::IsEnabled())
|
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -61,10 +61,10 @@ namespace Components
|
|||||||
|
|
||||||
bool UIScript::RunMenuScript(const char* name, const char** args)
|
bool UIScript::RunMenuScript(const char* name, const char** args)
|
||||||
{
|
{
|
||||||
if (const auto got = UIScript::UIScripts.find(name); got != UIScript::UIScripts.end())
|
if (const auto itr = UIScript::UIScripts.find(name); itr != UIScript::UIScripts.end())
|
||||||
{
|
{
|
||||||
const auto* info = UIScript::UI_GetClientInfo(0);
|
const auto* info = UIScript::UI_GetClientInfo(0);
|
||||||
got->second(UIScript::Token(args), info);
|
itr->second(UIScript::Token(args), info);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,8 +242,8 @@ namespace Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto got = VoteCommands.find(params->get(1));
|
const auto itr = VoteCommands.find(params->get(1));
|
||||||
if (got == VoteCommands.end())
|
if (itr == VoteCommands.end())
|
||||||
{
|
{
|
||||||
Game::SV_GameSendServerCommand(ent - Game::g_entities, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_INVALIDVOTESTRING\"", 0x65));
|
Game::SV_GameSendServerCommand(ent - Game::g_entities, Game::SV_CMD_CAN_IGNORE, VA("%c \"GAME_INVALIDVOTESTRING\"", 0x65));
|
||||||
Game::SV_GameSendServerCommand(ent - Game::g_entities, Game::SV_CMD_CAN_IGNORE, VA(CallVoteDesc, 0x65));
|
Game::SV_GameSendServerCommand(ent - Game::g_entities, Game::SV_CMD_CAN_IGNORE, VA(CallVoteDesc, 0x65));
|
||||||
@ -256,7 +256,7 @@ namespace Components
|
|||||||
Game::Cbuf_AddText(0, VA("%s\n", Game::level->voteString));
|
Game::Cbuf_AddText(0, VA("%s\n", Game::level->voteString));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto shouldDisplay = got->second(ent, params);
|
const auto shouldDisplay = itr->second(ent, params);
|
||||||
if (shouldDisplay)
|
if (shouldDisplay)
|
||||||
{
|
{
|
||||||
DisplayVote(ent);
|
DisplayVote(ent);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include <STDInclude.hpp>
|
#include <STDInclude.hpp>
|
||||||
|
#include "AssetInterfaces/ILocalizeEntry.hpp"
|
||||||
|
|
||||||
namespace Components
|
namespace Components
|
||||||
{
|
{
|
||||||
@ -76,7 +77,6 @@ namespace Components
|
|||||||
Game::DB_LoadXAssets(&info, 1, true);
|
Game::DB_LoadXAssets(&info, 1, true);
|
||||||
|
|
||||||
AssetHandler::ClearTemporaryAssets();
|
AssetHandler::ClearTemporaryAssets();
|
||||||
Localization::ClearTemp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::Stream* ZoneBuilder::Zone::getBuffer()
|
Utils::Stream* ZoneBuilder::Zone::getBuffer()
|
||||||
@ -147,23 +147,28 @@ namespace Components
|
|||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < this->dataMap.getRows(); ++i)
|
for (std::size_t i = 0; i < this->dataMap.getRows(); ++i)
|
||||||
{
|
{
|
||||||
if (this->dataMap.getElementAt(i, 0) != "require")
|
if (this->dataMap.getElementAt(i, 0) == "require"s)
|
||||||
{
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->dataMap.getElementAt(i, 0) == "localize"s)
|
||||||
|
{
|
||||||
|
const auto filename = this->dataMap.getElementAt(i, 1);
|
||||||
|
FileSystem::File file(std::format("localizedstrings/{}.str", filename));
|
||||||
|
if (file.exists())
|
||||||
|
{
|
||||||
|
Assets::ILocalizeEntry::ParseLocalizedStringsFile(this, filename, file.getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this->dataMap.getColumns(i) > 2)
|
if (this->dataMap.getColumns(i) > 2)
|
||||||
{
|
{
|
||||||
if (this->dataMap.getElementAt(i, 0) == "localize")
|
auto oldName = this->dataMap.getElementAt(i, 1);
|
||||||
{
|
auto newName = this->dataMap.getElementAt(i, 2);
|
||||||
std::string stringOverride = this->dataMap.getElementAt(i, 2);
|
auto typeName = this->dataMap.getElementAt(i, 0);
|
||||||
Utils::String::Replace(stringOverride, "\\n", "\n");
|
auto type = Game::DB_GetXAssetNameType(typeName.data());
|
||||||
|
|
||||||
Localization::SetTemp(this->dataMap.getElementAt(i, 1), stringOverride);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string oldName = this->dataMap.getElementAt(i, 1);
|
|
||||||
std::string newName = this->dataMap.getElementAt(i, 2);
|
|
||||||
std::string typeName = this->dataMap.getElementAt(i, 0).data();
|
|
||||||
Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data());
|
|
||||||
|
|
||||||
if (type < Game::XAssetType::ASSET_TYPE_COUNT && type >= 0)
|
if (type < Game::XAssetType::ASSET_TYPE_COUNT && type >= 0)
|
||||||
{
|
{
|
||||||
@ -171,9 +176,7 @@ namespace Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::Error(Game::ERR_FATAL, "Unable to rename '{}' to '{}' as the asset type '{}' is invalid!",
|
Logger::Error(Game::ERR_FATAL, "Unable to rename '{}' to '{}' as the asset type '{}' is invalid!", oldName, newName, typeName);
|
||||||
oldName, newName, typeName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +184,7 @@ namespace Components
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -206,10 +209,9 @@ namespace Components
|
|||||||
{
|
{
|
||||||
Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data());
|
Game::XAssetType type = Game::DB_GetXAssetNameType(typeName.data());
|
||||||
|
|
||||||
if (name.find(" ", 0) != std::string::npos)
|
if (name.find(' ', 0) != std::string::npos)
|
||||||
{
|
{
|
||||||
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER,
|
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Asset with name '{}' contains spaces. Check your zone source file to ensure this is correct!\n", name);
|
||||||
"Asset with name '{}' contains spaces. Check your zone source file to ensure this is correct!\n", name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize name for empty assets
|
// Sanitize name for empty assets
|
||||||
@ -420,8 +422,7 @@ namespace Components
|
|||||||
Utils::IO::WriteFile(outFile, outBuffer);
|
Utils::IO::WriteFile(outFile, outBuffer);
|
||||||
|
|
||||||
Logger::Print("done.\n");
|
Logger::Print("done.\n");
|
||||||
Logger::Print("Zone '{}' written with {} assets and {} script strings\n",
|
Logger::Print("Zone '{}' written with {} assets and {} script strings\n", outFile, (this->aliasList.size() + this->loadedAssets.size()), this->scriptStrings.size());
|
||||||
outFile, (this->aliasList.size() + this->loadedAssets.size()), this->scriptStrings.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneBuilder::Zone::saveData()
|
void ZoneBuilder::Zone::saveData()
|
||||||
@ -452,7 +453,7 @@ namespace Components
|
|||||||
// That's the reason why the count is incremented by 1, if scriptStrings are available.
|
// That's the reason why the count is incremented by 1, if scriptStrings are available.
|
||||||
|
|
||||||
// Write ScriptString pointer table
|
// Write ScriptString pointer table
|
||||||
for (size_t i = 0; i < this->scriptStrings.size(); ++i)
|
for (std::size_t i = 0; i < this->scriptStrings.size(); ++i)
|
||||||
{
|
{
|
||||||
this->buffer.saveMax(4);
|
this->buffer.saveMax(4);
|
||||||
}
|
}
|
||||||
@ -618,6 +619,11 @@ namespace Components
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZoneBuilder::Zone::addRawAsset(Game::XAssetType type, void* ptr)
|
||||||
|
{
|
||||||
|
this->loadedAssets.push_back({type, {ptr}});
|
||||||
|
}
|
||||||
|
|
||||||
// Remap a scriptString to it's corresponding value in the local scriptString table.
|
// Remap a scriptString to it's corresponding value in the local scriptString table.
|
||||||
void ZoneBuilder::Zone::mapScriptString(unsigned short* gameIndex)
|
void ZoneBuilder::Zone::mapScriptString(unsigned short* gameIndex)
|
||||||
{
|
{
|
||||||
@ -914,17 +920,17 @@ namespace Components
|
|||||||
frames++;
|
frames++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once CppUnreachableCode
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneBuilder::HandleError(Game::errorParm_t code, const char* fmt, ...)
|
void ZoneBuilder::HandleError(Game::errorParm_t code, const char* fmt, ...)
|
||||||
{
|
{
|
||||||
char buffer[4096] = {0};
|
char buffer[0x1000]{};
|
||||||
va_list args;
|
va_list ap;
|
||||||
va_start(args, fmt);
|
|
||||||
_vsnprintf_s(buffer, _TRUNCATE, fmt, args);
|
va_start(ap, fmt);
|
||||||
va_end(args);
|
vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
if (!Flags::HasFlag("stdout"))
|
if (!Flags::HasFlag("stdout"))
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user