commit
26069a993d
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
lfs: false
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
uses: microsoft/setup-msbuild@v1.1.3
|
||||
|
||||
- name: Generate project files
|
||||
run: tools/premake5 vs2022
|
||||
@ -48,20 +48,13 @@ jobs:
|
||||
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=Win32 build/iw4x.sln
|
||||
|
||||
- name: Upload ${{matrix.configuration}} binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: ${{matrix.configuration}} binaries
|
||||
path: |
|
||||
build/bin/Win32/${{matrix.configuration}}/iw4x.dll
|
||||
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:
|
||||
name: Deploy artifacts
|
||||
needs: build
|
||||
@ -77,16 +70,10 @@ jobs:
|
||||
run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Release binaries
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Release binaries
|
||||
|
||||
# - name: Download Release data artifacts
|
||||
# uses: actions/download-artifact@v2
|
||||
# with:
|
||||
# name: Release data artifacts
|
||||
# path: data
|
||||
|
||||
# Set up committer info and GPG key
|
||||
- name: Install SSH key
|
||||
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"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Normalize version
|
||||
id: normalize_version
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check out files
|
||||
if: github.event.pull_request.merged
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: false
|
||||
lfs: false
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -17,7 +17,7 @@
|
||||
[submodule "deps/mongoose"]
|
||||
path = deps/mongoose
|
||||
url = https://github.com/cesanta/mongoose.git
|
||||
branch = dev
|
||||
branch = master
|
||||
[submodule "deps/protobuf"]
|
||||
path = deps/protobuf
|
||||
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/).
|
||||
|
||||
## [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
|
||||
|
||||
### 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)
|
||||
- `HttpGet`& `HttpCancel` are disabled for security reasons (#449)
|
||||
- 'g_allowVote' is a replicated Dvar (#457)
|
||||
- `g_allowVote` is a replicated Dvar (#457)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -36,7 +36,6 @@
|
||||
| `-bigminidumps` | Include all code sections from 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. |
|
||||
| `-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. |
|
||||
| `-version` | Print IW4x build info on startup. |
|
||||
| `-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
|
||||
{
|
||||
path.join(mongoose.source, "*.c"),
|
||||
path.join(mongoose.source, "*.h"),
|
||||
path.join(mongoose.source, "mongoose.c"),
|
||||
path.join(mongoose.source, "mongoose.h"),
|
||||
}
|
||||
|
||||
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 MapDump());
|
||||
Loader::Register(new ModList());
|
||||
Loader::Register(new Monitor());
|
||||
Loader::Register(new Network());
|
||||
Loader::Register(new Session());
|
||||
Loader::Register(new Theatre());
|
||||
@ -72,11 +71,9 @@ namespace Components
|
||||
Loader::Register(new Dedicated());
|
||||
Loader::Register(new Discovery());
|
||||
Loader::Register(new FastFiles());
|
||||
Loader::Register(new FrameTime());
|
||||
Loader::Register(new Gametypes());
|
||||
Loader::Register(new Materials());
|
||||
Loader::Register(new Scheduler());
|
||||
Loader::Register(new Threading());
|
||||
Loader::Register(new CardTitles());
|
||||
Loader::Register(new FileSystem());
|
||||
Loader::Register(new ModelSurfs());
|
||||
@ -91,7 +88,6 @@ namespace Components
|
||||
Loader::Register(new ZoneBuilder());
|
||||
Loader::Register(new AssetHandler());
|
||||
Loader::Register(new Localization());
|
||||
//Loader::Register(new MusicalTalent());
|
||||
Loader::Register(new ServerCommands());
|
||||
Loader::Register(new StructuredData());
|
||||
Loader::Register(new ConnectProtocol());
|
||||
|
@ -80,7 +80,6 @@ namespace Components
|
||||
#include "Modules/Console.hpp"
|
||||
#include "Modules/UIScript.hpp"
|
||||
#include "Modules/ModList.hpp"
|
||||
#include "Modules/Monitor.hpp"
|
||||
#include "Modules/Network.hpp"
|
||||
#include "Modules/Theatre.hpp"
|
||||
#include "Modules/QuickPatch.hpp"
|
||||
@ -104,11 +103,9 @@ namespace Components
|
||||
#include "Modules/Discovery.hpp"
|
||||
#include "Modules/Exception.hpp"
|
||||
#include "Modules/FastFiles.hpp"
|
||||
#include "Modules/FrameTime.hpp"
|
||||
#include "Modules/Gametypes.hpp"
|
||||
#include "Modules/Materials.hpp"
|
||||
#include "Modules/Singleton.hpp"
|
||||
#include "Modules/Threading.hpp"
|
||||
#include "Modules/CardTitles.hpp"
|
||||
#include "Modules/FileSystem.hpp"
|
||||
#include "Modules/ModelSurfs.hpp"
|
||||
@ -121,7 +118,6 @@ namespace Components
|
||||
#include "Modules/ZoneBuilder.hpp"
|
||||
#include "Modules/AssetHandler.hpp"
|
||||
#include "Modules/Localization.hpp"
|
||||
#include "Modules/MusicalTalent.hpp"
|
||||
#include "Modules/ServerCommands.hpp"
|
||||
#include "Modules/StructuredData.hpp"
|
||||
#include "Modules/ConnectProtocol.hpp"
|
||||
|
@ -8,12 +8,12 @@ namespace Components
|
||||
class IAsset
|
||||
{
|
||||
public:
|
||||
virtual ~IAsset() {};
|
||||
virtual Game::XAssetType getType() { return Game::XAssetType::ASSET_TYPE_INVALID; };
|
||||
virtual void mark(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) { /*ErrorTypeNotSupported(this);*/ };
|
||||
virtual void save(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) { /*ErrorTypeNotSupported(this);*/ };
|
||||
virtual void dump(Game::XAssetHeader /*header*/) { /*ErrorTypeNotSupported(this);*/ };
|
||||
virtual void load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, ZoneBuilder::Zone* /*builder*/) { /*ErrorTypeNotSupported(this);*/ };
|
||||
virtual ~IAsset() {}
|
||||
virtual Game::XAssetType getType() { return Game::XAssetType::ASSET_TYPE_INVALID; }
|
||||
virtual void mark(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) {}
|
||||
virtual void save(Game::XAssetHeader /*header*/, ZoneBuilder::Zone* /*builder*/) {}
|
||||
virtual void dump(Game::XAssetHeader /*header*/) {}
|
||||
virtual void load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, ZoneBuilder::Zone* /*builder*/) {}
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
Game::Font_s *asset = header.font;
|
||||
const auto* asset = header.font;
|
||||
|
||||
if (asset->material)
|
||||
{
|
||||
@ -98,154 +98,163 @@ namespace Assets
|
||||
Components::FileSystem::File fontDefFile(Utils::String::VA("%s.json", 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());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fontDef.is_object())
|
||||
nlohmann::json fontDef = nlohmann::json::parse(fontDefFile.getBuffer());
|
||||
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;
|
||||
}
|
||||
|
||||
auto w = fontDef["textureWidth"].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);
|
||||
|
||||
// Setup assets
|
||||
const auto* texName = builder->getAllocator()->duplicateString(Utils::String::VA("if_%s", name.data() + 6 /* skip "fonts/" */));
|
||||
const auto* fontName = builder->getAllocator()->duplicateString(name);
|
||||
const auto* glowMaterialName = builder->getAllocator()->duplicateString(Utils::String::VA("%s_glow", name.data()));
|
||||
|
||||
auto* image = builder->getAllocator()->allocate<Game::GfxImage>();
|
||||
std::memcpy(image, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_IMAGE, "gamefonts_pc").image, sizeof(Game::GfxImage));
|
||||
|
||||
image->name = texName;
|
||||
|
||||
auto* material = builder->getAllocator()->allocate<Game::Material>();
|
||||
std::memcpy(material, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc").material, sizeof(Game::Material));
|
||||
|
||||
auto textureTable = builder->getAllocator()->allocate<Game::MaterialTextureDef>();
|
||||
std::memcpy(textureTable, material->textureTable, sizeof(Game::MaterialTextureDef));
|
||||
|
||||
material->textureTable = textureTable;
|
||||
material->textureTable->u.image = image;
|
||||
material->info.name = fontName;
|
||||
|
||||
auto* glowMaterial = builder->getAllocator()->allocate<Game::Material>();
|
||||
std::memcpy(glowMaterial, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc_glow").material, sizeof(Game::Material));
|
||||
|
||||
glowMaterial->textureTable = material->textureTable;
|
||||
glowMaterial->info.name = glowMaterialName;
|
||||
|
||||
std::vector<std::uint16_t> charset;
|
||||
|
||||
if (fontDef["charset"].is_array())
|
||||
{
|
||||
nlohmann::json::array_t charsetArray = fontDef["charset"];
|
||||
for (auto& ch : charsetArray)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Font define {} is invalid", name);
|
||||
return;
|
||||
charset.push_back(static_cast<std::uint16_t>(ch.get<int>()));
|
||||
}
|
||||
|
||||
int w = fontDef["textureWidth"].get<int>();
|
||||
int h = fontDef["textureHeight"].get<int>();
|
||||
|
||||
int size = fontDef["size"].get<int>();
|
||||
int yOffset = fontDef["yOffset"].get<int>();
|
||||
// order matters
|
||||
std::ranges::sort(charset);
|
||||
|
||||
auto* pixels = builder->getAllocator()->allocateArray<uint8_t>(w * h);
|
||||
|
||||
// Setup assets
|
||||
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* glowMaterialName = builder->getAllocator()->duplicateString(Utils::String::VA("%s_glow", name.data()));
|
||||
|
||||
auto* image = builder->getAllocator()->allocate<Game::GfxImage>();
|
||||
std::memcpy(image, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_IMAGE, "gamefonts_pc").image, sizeof(Game::GfxImage));
|
||||
|
||||
image->name = texName;
|
||||
|
||||
auto* material = builder->getAllocator()->allocate<Game::Material>();
|
||||
std::memcpy(material, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc").material, sizeof(Game::Material));
|
||||
|
||||
auto textureTable = builder->getAllocator()->allocate<Game::MaterialTextureDef>();
|
||||
std::memcpy(textureTable, material->textureTable, sizeof(Game::MaterialTextureDef));
|
||||
|
||||
material->textureTable = textureTable;
|
||||
material->textureTable->u.image = image;
|
||||
material->info.name = fontName;
|
||||
|
||||
auto* glowMaterial = builder->getAllocator()->allocate<Game::Material>();
|
||||
std::memcpy(glowMaterial, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc_glow").material, sizeof(Game::Material));
|
||||
|
||||
glowMaterial->textureTable = material->textureTable;
|
||||
glowMaterial->info.name = glowMaterialName;
|
||||
|
||||
std::vector<uint16_t> charset;
|
||||
|
||||
if (fontDef["charset"].is_array())
|
||||
for (std::uint16_t i = 32; i < 128; i++)
|
||||
{
|
||||
nlohmann::json::array_t charsetArray = fontDef["charset"];
|
||||
for (auto& ch : charsetArray)
|
||||
charset.push_back(static_cast<uint16_t>(ch.get<int>()));
|
||||
|
||||
// order matters
|
||||
std::sort(charset.begin(), charset.end());
|
||||
|
||||
for (uint16_t i = 32; i < 128; i++)
|
||||
if (std::ranges::find(charset, i) == charset.end())
|
||||
{
|
||||
if (std::find(charset.begin(), charset.end(), 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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint16_t i = 32; i < 128; i++)
|
||||
charset.push_back(i);
|
||||
}
|
||||
|
||||
auto* font = builder->getAllocator()->allocate<Game::Font_s>();
|
||||
|
||||
font->fontName = fontName;
|
||||
font->pixelHeight = size;
|
||||
font->material = material;
|
||||
font->glowMaterial = glowMaterial;
|
||||
font->glyphCount = charset.size();
|
||||
font->glyphs = builder->getAllocator()->allocateArray<Game::Glyph>(charset.size());
|
||||
|
||||
// Generate glyph data
|
||||
int result = PackFonts(reinterpret_cast<const uint8_t*>(fontFile.getBuffer().data()), charset, font->glyphs, static_cast<float>(size), pixels, w, h, yOffset);
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Truetype font {} is broken", name);
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Texture size of font {} is not enough", name);
|
||||
}
|
||||
else if(h - result > size)
|
||||
{
|
||||
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Texture of font {} have too much left over space: {}\n", name, h - result);
|
||||
}
|
||||
|
||||
header->font = font;
|
||||
|
||||
// Save generated materials
|
||||
Game::XAssetHeader tmpHeader;
|
||||
|
||||
tmpHeader.image = image;
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_IMAGE, tmpHeader);
|
||||
|
||||
tmpHeader.material = material;
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader);
|
||||
|
||||
tmpHeader.material = glowMaterial;
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader);
|
||||
|
||||
// Save generated image
|
||||
Utils::IO::CreateDir("userraw\\images");
|
||||
|
||||
int fileSize = w * h * 4;
|
||||
int iwiHeaderSize = static_cast<int>(sizeof(Game::GfxImageFileHeader));
|
||||
|
||||
Game::GfxImageFileHeader iwiHeader =
|
||||
{
|
||||
{ 'I', 'W', 'i' },
|
||||
/* version */
|
||||
8,
|
||||
/* flags */
|
||||
2,
|
||||
/* format */
|
||||
Game::IMG_FORMAT_BITMAP_RGBA,
|
||||
0,
|
||||
/* dimensions(x, y, z) */
|
||||
{ static_cast<short>(w), static_cast<short>(h), 1 },
|
||||
/* fileSizeForPicmip (mipSize in bytes + sizeof(GfxImageFileHeader)) */
|
||||
{ fileSize + iwiHeaderSize, fileSize, fileSize, fileSize }
|
||||
};
|
||||
|
||||
std::string outIwi;
|
||||
outIwi.resize(fileSize + sizeof(Game::GfxImageFileHeader));
|
||||
|
||||
std::memcpy(outIwi.data(), &iwiHeader, sizeof(Game::GfxImageFileHeader));
|
||||
|
||||
// Generate RGBA data
|
||||
auto* rgbaPixels = outIwi.data() + sizeof(Game::GfxImageFileHeader);
|
||||
|
||||
for (int i = 0; i < w * h * 4; i += 4)
|
||||
{
|
||||
rgbaPixels[i + 0] = static_cast<char>(255);
|
||||
rgbaPixels[i + 1] = static_cast<char>(255);
|
||||
rgbaPixels[i + 2] = static_cast<char>(255);
|
||||
rgbaPixels[i + 3] = static_cast<char>(pixels[i / 4]);
|
||||
}
|
||||
|
||||
Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::uint16_t i = 32; i < 128; i++)
|
||||
{
|
||||
charset.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
auto* font = builder->getAllocator()->allocate<Game::Font_s>();
|
||||
|
||||
font->fontName = fontName;
|
||||
font->pixelHeight = size;
|
||||
font->material = material;
|
||||
font->glowMaterial = glowMaterial;
|
||||
font->glyphCount = static_cast<int>(charset.size());
|
||||
font->glyphs = builder->getAllocator()->allocateArray<Game::Glyph>(charset.size());
|
||||
|
||||
// Generate glyph data
|
||||
int result = PackFonts(reinterpret_cast<const uint8_t*>(fontFile.getBuffer().data()), charset, font->glyphs, static_cast<float>(size), pixels, w, h, yOffset);
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Truetype font {} is broken", name);
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
Components::Logger::Error(Game::ERR_FATAL, "Texture size of font {} is not enough", name);
|
||||
}
|
||||
else if(h - result > size)
|
||||
{
|
||||
Components::Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Texture of font {} have too much left over space: {}\n", name, h - result);
|
||||
}
|
||||
|
||||
header->font = font;
|
||||
|
||||
// Save generated materials
|
||||
Game::XAssetHeader tmpHeader;
|
||||
|
||||
tmpHeader.image = image;
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_IMAGE, tmpHeader);
|
||||
|
||||
tmpHeader.material = material;
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader);
|
||||
|
||||
tmpHeader.material = glowMaterial;
|
||||
Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader);
|
||||
|
||||
// Save generated image
|
||||
Utils::IO::CreateDir("userraw\\images");
|
||||
|
||||
int fileSize = w * h * 4;
|
||||
int iwiHeaderSize = static_cast<int>(sizeof(Game::GfxImageFileHeader));
|
||||
|
||||
Game::GfxImageFileHeader iwiHeader =
|
||||
{
|
||||
{ 'I', 'W', 'i' },
|
||||
/* version */
|
||||
8,
|
||||
/* flags */
|
||||
2,
|
||||
/* format */
|
||||
Game::IMG_FORMAT_BITMAP_RGBA,
|
||||
0,
|
||||
/* dimensions(x, y, z) */
|
||||
{ static_cast<short>(w), static_cast<short>(h), 1 },
|
||||
/* fileSizeForPicmip (mipSize in bytes + sizeof(GfxImageFileHeader)) */
|
||||
{ fileSize + iwiHeaderSize, fileSize, fileSize, fileSize }
|
||||
};
|
||||
|
||||
std::string outIwi;
|
||||
outIwi.resize(fileSize + sizeof(Game::GfxImageFileHeader));
|
||||
|
||||
std::memcpy(outIwi.data(), &iwiHeader, sizeof(Game::GfxImageFileHeader));
|
||||
|
||||
// Generate RGBA data
|
||||
auto* rgbaPixels = outIwi.data() + sizeof(Game::GfxImageFileHeader);
|
||||
|
||||
for (auto i = 0; i < w * h * 4; i += 4)
|
||||
{
|
||||
rgbaPixels[i + 0] = static_cast<char>(255);
|
||||
rgbaPixels[i + 1] = static_cast<char>(255);
|
||||
rgbaPixels[i + 2] = static_cast<char>(255);
|
||||
rgbaPixels[i + 3] = static_cast<char>(pixels[i / 4]);
|
||||
}
|
||||
|
||||
Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi);
|
||||
}
|
||||
|
||||
void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
|
@ -3,13 +3,37 @@
|
||||
|
||||
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)
|
||||
{
|
||||
AssertSize(Game::LocalizeEntry, 8);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::LocalizeEntry* asset = header.localize;
|
||||
Game::LocalizeEntry* dest = buffer->dest<Game::LocalizeEntry>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.localize;
|
||||
auto* dest = buffer->dest<Game::LocalizeEntry>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
@ -28,4 +52,33 @@ namespace Assets
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
|
||||
static void ParseLocalizedStringsFile(Components::ZoneBuilder::Zone* builder, const std::string& name, const std::string& filename);
|
||||
};
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ namespace Assets
|
||||
"_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_nofog", "effect_blend_nofog"},
|
||||
@ -50,12 +51,6 @@ namespace Assets
|
||||
{"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"},
|
||||
{"mc_unlit", "mc_unlit_replace_lin_nocast"},
|
||||
{"mc_unlit_alphatest", "mc_unlit_blend_lin"}
|
||||
/*,
|
||||
{"", ""},
|
||||
{"", ""},
|
||||
{"", ""},
|
||||
{"", ""},
|
||||
{"", ""},*/
|
||||
};
|
||||
|
||||
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());
|
||||
|
||||
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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Game::Material* asset = reader.readObject<Game::Material>();
|
||||
auto* asset = reader.readObject<Game::Material>();
|
||||
|
||||
if (asset->info.name)
|
||||
{
|
||||
@ -260,7 +255,7 @@ namespace Assets
|
||||
Game::XAssetHeader header = entry->asset.header;
|
||||
|
||||
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",
|
||||
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);
|
||||
builder->storePointer(asset->textureTable);
|
||||
|
||||
Game::MaterialTextureDef* destTextureTable = buffer->dest<Game::MaterialTextureDef>();
|
||||
auto* destTextureTable = buffer->dest<Game::MaterialTextureDef>();
|
||||
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];
|
||||
Game::MaterialTextureDef* textureDef = &asset->textureTable[i];
|
||||
auto* destTextureDef = &destTextureTable[i];
|
||||
auto* textureDef = &asset->textureTable[i];
|
||||
|
||||
if (textureDef->semantic == SEMANTIC_WATER_MAP)
|
||||
{
|
||||
|
@ -7,31 +7,44 @@ namespace Assets
|
||||
{
|
||||
Components::FileSystem::File rawFile(name);
|
||||
|
||||
if (rawFile.exists())
|
||||
if (!rawFile.exists())
|
||||
{
|
||||
Game::RawFile* asset = builder->getAllocator()->allocate<Game::RawFile>();
|
||||
|
||||
if (asset)
|
||||
{
|
||||
//std::string data = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
|
||||
|
||||
asset->name = builder->getAllocator()->duplicateString(name);
|
||||
asset->buffer = builder->getAllocator()->duplicateString(rawFile.getBuffer());
|
||||
asset->compressedLen = 0;//data.size();
|
||||
asset->len = rawFile.getBuffer().size();
|
||||
|
||||
header->rawfile = asset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto* asset = builder->getAllocator()->allocate<Game::RawFile>();
|
||||
if (!asset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto data = Utils::Compression::ZLib::Compress(rawFile.getBuffer());
|
||||
|
||||
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->compressedLen = 0;
|
||||
}
|
||||
|
||||
asset->len = static_cast<int>(rawFile.getBuffer().size());
|
||||
|
||||
header->rawfile = asset;
|
||||
}
|
||||
|
||||
void IRawFile::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
AssertSize(Game::RawFile, 16);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::RawFile* asset = header.rawfile;
|
||||
Game::RawFile* dest = buffer->dest<Game::RawFile>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.rawfile;
|
||||
auto* dest = buffer->dest<Game::RawFile>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
|
@ -10,6 +10,6 @@ namespace Assets
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
|
||||
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 saveStructuredDataEnumArray(Game::StructuredDataEnum* enums, int numEnums, Components::ZoneBuilder::Zone* builder);
|
||||
void saveStructuredDataStructArray(Game::StructuredDataStruct* structs, int numStructs, Components::ZoneBuilder::Zone* builder);
|
||||
static void saveStructuredDataEnumArray(Game::StructuredDataEnum* enums, int numEnums, Components::ZoneBuilder::Zone* builder);
|
||||
static void saveStructuredDataStructArray(Game::StructuredDataStruct* structs, int numStructs, Components::ZoneBuilder::Zone* builder);
|
||||
};
|
||||
}
|
||||
|
@ -12,16 +12,26 @@ namespace Assets
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
nlohmann::json infoData = nlohmann::json::parse(aliasFile.getBuffer());
|
||||
nlohmann::json aliasesContainer = infoData["head"];
|
||||
nlohmann::json infoData;
|
||||
try
|
||||
{
|
||||
infoData = nlohmann::json::parse(aliasFile.getBuffer());
|
||||
}
|
||||
catch (const nlohmann::json::parse_error& ex)
|
||||
{
|
||||
Components::Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
nlohmann::json aliasesContainer = infoData["head"];
|
||||
nlohmann::json::array_t aliases = aliasesContainer;
|
||||
|
||||
aliasList->count = aliases.size();
|
||||
@ -34,7 +44,7 @@ namespace Assets
|
||||
return;
|
||||
}
|
||||
|
||||
aliasList->aliasName = builder->getAllocator()->duplicateString(name.c_str());
|
||||
aliasList->aliasName = builder->getAllocator()->duplicateString(name);
|
||||
|
||||
for (size_t i = 0; i < aliasList->count; i++)
|
||||
{
|
||||
@ -288,19 +298,14 @@ namespace Assets
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
auto curve = Components::AssetHandler::FindAssetForZone(
|
||||
Game::XAssetType::ASSET_TYPE_SOUND_CURVE,
|
||||
fallOffCurve.c_str(),
|
||||
builder
|
||||
).sndCurve;
|
||||
|
||||
auto curve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, fallOffCurve, builder).sndCurve;
|
||||
alias->volumeFalloffCurve = curve;
|
||||
}
|
||||
|
||||
@ -376,9 +381,9 @@ namespace Assets
|
||||
{
|
||||
AssertSize(Game::snd_alias_list_t, 12);
|
||||
|
||||
Utils::Stream* buffer = builder->getBuffer();
|
||||
Game::snd_alias_list_t* asset = header.sound;
|
||||
Game::snd_alias_list_t* dest = buffer->dest<Game::snd_alias_list_t>();
|
||||
auto* buffer = builder->getBuffer();
|
||||
auto* asset = header.sound;
|
||||
auto* dest = buffer->dest<Game::snd_alias_list_t>();
|
||||
buffer->save(asset);
|
||||
|
||||
buffer->pushBlock(Game::XFILE_BLOCK_VIRTUAL);
|
||||
@ -402,7 +407,7 @@ namespace Assets
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
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);
|
||||
|
||||
for (unsigned int i = 0; i < asset->count; ++i)
|
||||
@ -453,7 +458,7 @@ namespace Assets
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
builder->storePointer(alias->soundFile);
|
||||
|
||||
Game::SoundFile* destSoundFile = buffer->dest<Game::SoundFile>();
|
||||
auto* destSoundFile = buffer->dest<Game::SoundFile>();
|
||||
buffer->save(alias->soundFile);
|
||||
|
||||
// Save_SoundFileRef
|
||||
@ -503,7 +508,7 @@ namespace Assets
|
||||
buffer->align(Utils::Stream::ALIGN_4);
|
||||
builder->storePointer(alias->speakerMap);
|
||||
|
||||
Game::SpeakerMap* destSoundFile = buffer->dest<Game::SpeakerMap>();
|
||||
auto* destSoundFile = buffer->dest<Game::SpeakerMap>();
|
||||
buffer->save(alias->speakerMap);
|
||||
|
||||
if (alias->speakerMap->name)
|
||||
|
@ -277,7 +277,7 @@ namespace Components
|
||||
|
||||
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;
|
||||
cert.set_token(Auth::GuidToken.toString());
|
||||
|
@ -130,12 +130,14 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
std::string error;
|
||||
const auto banData = nlohmann::json::parse(bans.getBuffer());
|
||||
|
||||
if (!banData.is_object())
|
||||
nlohmann::json banData;
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -144,7 +146,7 @@ namespace Components
|
||||
|
||||
if (idList.is_array())
|
||||
{
|
||||
nlohmann::json::array_t arr = idList;
|
||||
const nlohmann::json::array_t arr = idList;
|
||||
|
||||
for (auto &idEntry : arr)
|
||||
{
|
||||
@ -161,7 +163,7 @@ namespace Components
|
||||
|
||||
if (ipList.is_array())
|
||||
{
|
||||
nlohmann::json::array_t arr = ipList;
|
||||
const nlohmann::json::array_t arr = ipList;
|
||||
|
||||
for (auto &ipEntry : arr)
|
||||
{
|
||||
@ -225,7 +227,7 @@ namespace Components
|
||||
{
|
||||
Command::Add("banClient", [](Command::Params* params)
|
||||
{
|
||||
if (!(*Game::com_sv_running)->current.enabled)
|
||||
if (!Dedicated::IsRunning())
|
||||
{
|
||||
Logger::Print("Server is not running.\n");
|
||||
return;
|
||||
@ -269,7 +271,7 @@ namespace Components
|
||||
|
||||
Command::Add("unbanClient", [](Command::Params* params)
|
||||
{
|
||||
if (!(*Game::com_sv_running)->current.enabled)
|
||||
if (!Dedicated::IsRunning())
|
||||
{
|
||||
Logger::Print("Server is not running.\n");
|
||||
return;
|
||||
|
@ -132,7 +132,7 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
g_botai[entref.entnum] = {0};
|
||||
ZeroMemory(&g_botai[entref.entnum], sizeof(BotMovementInfo));
|
||||
g_botai[entref.entnum].weapon = 1;
|
||||
g_botai[entref.entnum].active = true;
|
||||
});
|
||||
@ -317,7 +317,7 @@ namespace Components
|
||||
// Zero the bot command array
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ namespace Components
|
||||
}
|
||||
|
||||
// Game's code
|
||||
if (surfaceType != Game::materialSurfType_t::SURF_TYPE_DEFAULT)
|
||||
if (surfaceType != Game::SURF_TYPE_DEFAULT)
|
||||
{
|
||||
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()
|
||||
{
|
||||
BGSurfacePenetration = Dvar::Register<float>("bg_surfacePenetration", 0.0f,
|
||||
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO,
|
||||
"Set to a value greater than 0 to override the surface penetration depth");
|
||||
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO, "Set to a value greater than 0 to override the surface penetration depth");
|
||||
BGBulletRange = Game::Dvar_RegisterFloat("bg_bulletRange", 8192.0f,
|
||||
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO,
|
||||
"Max range used when calculating the bullet end position");
|
||||
0.0f, std::numeric_limits<float>::max(), Game::DVAR_CODINFO, "Max range used when calculating the bullet end position");
|
||||
|
||||
Utils::Hook(0x4F6980, BG_GetSurfacePenetrationDepthStub, 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 void Bullet_FireStub();
|
||||
|
||||
static void BG_srand_Hk(unsigned int* pHoldrand);
|
||||
};
|
||||
}
|
||||
|
@ -10,22 +10,22 @@ namespace Components
|
||||
signature.add({
|
||||
"\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();
|
||||
|
||||
// Some more generic obfuscation (mov al, 1; retn)
|
||||
Utils::Hook::Set<DWORD>(0x471B20, 0xC301B0);
|
||||
Utils::Hook::Set<DWORD>(0x43A070, 0xC301B0);
|
||||
Utils::Hook::Set<DWORD>(0x4C8B30, 0xC301B0);
|
||||
Utils::Hook::Set<DWORD>(0x469340, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x471B20, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x43A070, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x4C8B30, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x469340, 0xC301B0);
|
||||
|
||||
// Other checks
|
||||
Utils::Hook::Set<DWORD>(0x401000, 0xC301B0);
|
||||
Utils::Hook::Set<DWORD>(0x45F8B0, 0xC301B0);
|
||||
Utils::Hook::Set<DWORD>(0x46FAE0, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x401000, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x45F8B0, 0xC301B0);
|
||||
Utils::Hook::Set<std::uint32_t>(0x46FAE0, 0xC301B0);
|
||||
|
||||
// Removed in 159 SP binaries
|
||||
Utils::Hook::Nop(0x46B173, 9);
|
||||
@ -35,19 +35,21 @@ namespace Components
|
||||
// Disable some checks on certain game events
|
||||
Utils::Hook::Nop(0x43EC96, 9);
|
||||
Utils::Hook::Nop(0x4675C6, 9);
|
||||
Utils::Hook::Nop(0x405A36, 9);
|
||||
|
||||
// Random checks scattered throughout the binary
|
||||
Utils::Hook::Set<BYTE>(0x499F90, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x4FC700, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x4C4170, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x49E8C0, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x42DB00, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x4F4CF0, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x432180, 0xC3);
|
||||
Utils::Hook::Set<BYTE>(0x461930, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x499F90, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x4FC700, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x4C4170, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x49E8C0, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x42DB00, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x4F4CF0, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x432180, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x461930, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x430410, 0xC3);
|
||||
|
||||
// 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
|
||||
Utils::Hook::Nop(0x4826F8, 5);
|
||||
|
@ -29,23 +29,35 @@ namespace Components
|
||||
// Prevent callbacks from adding a new callback (would make the vector iterator invalid)
|
||||
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;
|
||||
text[1] = text[0];
|
||||
|
||||
if (msgIndex == 1)
|
||||
{
|
||||
// Overwrite / with \x15
|
||||
text[msgIndex] = text[msgIndex - 1];
|
||||
}
|
||||
// Skip over the first character
|
||||
++text;
|
||||
}
|
||||
|
||||
if (IsMuted(player))
|
||||
{
|
||||
SendChat = false;
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE,
|
||||
Utils::String::VA("%c \"You are muted\"", 0x65));
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65));
|
||||
}
|
||||
|
||||
for (const auto& callback : SayCallbacks)
|
||||
{
|
||||
if (!ChatCallback(player, callback.getPos(), (text + 1), mode))
|
||||
if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode))
|
||||
{
|
||||
SendChat = false;
|
||||
}
|
||||
@ -54,14 +66,13 @@ namespace Components
|
||||
if (sv_disableChat.get<bool>())
|
||||
{
|
||||
SendChat = false;
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE,
|
||||
Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, 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_AddString(text + 1);
|
||||
Game::Scr_AddString(text + msgIndex);
|
||||
Game::Scr_NotifyLevel(Game::SL_GetString("say", 0), 2);
|
||||
|
||||
return text;
|
||||
@ -285,9 +296,9 @@ namespace Components
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -308,9 +319,9 @@ namespace Components
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -345,6 +356,12 @@ namespace Components
|
||||
|
||||
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;
|
||||
|
||||
auto message = params->join(1);
|
||||
@ -364,6 +381,12 @@ namespace Components
|
||||
|
||||
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;
|
||||
|
||||
const auto client = std::atoi(params->get(1));
|
||||
@ -384,6 +407,12 @@ namespace Components
|
||||
|
||||
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;
|
||||
|
||||
auto message = params->join(1);
|
||||
@ -393,6 +422,12 @@ namespace Components
|
||||
|
||||
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;
|
||||
|
||||
const auto client = atoi(params->get(1));
|
||||
|
@ -272,18 +272,5 @@ namespace Components
|
||||
// clanName in CG_Obituary
|
||||
Utils::Hook(0x586DD6, 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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -157,9 +157,9 @@ namespace Components
|
||||
ClientParams params;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ namespace Components
|
||||
HKEY hKey = nullptr;
|
||||
std::string data;
|
||||
|
||||
char ownPth[MAX_PATH] = { 0 };
|
||||
char workdir[MAX_PATH] = { 0 };
|
||||
char ownPth[MAX_PATH] = {0};
|
||||
char workdir[MAX_PATH] = {0};
|
||||
|
||||
DWORD dwsize = MAX_PATH;
|
||||
HMODULE hModule = GetModuleHandle(nullptr);
|
||||
HMODULE hModule = GetModuleHandleA(nullptr);
|
||||
|
||||
if (hModule != nullptr)
|
||||
{
|
||||
@ -44,7 +44,7 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
char* endPtr = strstr(workdir, "iw4x.exe");
|
||||
auto* endPtr = std::strstr(workdir, "iw4x.exe");
|
||||
if (endPtr != nullptr)
|
||||
{
|
||||
*endPtr = 0;
|
||||
@ -183,7 +183,7 @@ namespace Components
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
cmdLine = cmdLine.substr(pos + 7);
|
||||
pos = cmdLine.find_first_of("/");
|
||||
pos = cmdLine.find_first_of('/');
|
||||
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
|
@ -1,10 +1,14 @@
|
||||
#include <STDInclude.hpp>
|
||||
#undef MOUSE_MOVED
|
||||
#include <curses.h>
|
||||
|
||||
#define REMOVE_HEADERBAR 1
|
||||
|
||||
namespace Components
|
||||
{
|
||||
WINDOW* Console::OutputWindow;
|
||||
WINDOW* Console::InputWindow;
|
||||
WINDOW* Console::InfoWindow;
|
||||
static WINDOW* OutputWindow;
|
||||
static WINDOW* InputWindow;
|
||||
static WINDOW* InfoWindow;
|
||||
|
||||
int Console::OutputTop = 0;
|
||||
int Console::OutBuffer = 0;
|
||||
@ -20,6 +24,24 @@ namespace Components
|
||||
bool Console::HasConsole = 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;
|
||||
|
||||
Game::SafeArea Console::OriginalSafeArea;
|
||||
@ -59,9 +81,9 @@ namespace Components
|
||||
clientCount = Game::PartyHost_CountMembers(reinterpret_cast<Game::PartyData*>(0x1081C00));
|
||||
}
|
||||
|
||||
wclear(Console::InfoWindow);
|
||||
wprintw(Console::InfoWindow, "%s : %d/%d players : map %s", hostname.data(), clientCount, maxclientCount, (!mapname.empty()) ? mapname.data() : "none");
|
||||
wnoutrefresh(Console::InfoWindow);
|
||||
wclear(InfoWindow);
|
||||
wprintw(InfoWindow, "%s : %d/%d players : map %s", hostname.data(), clientCount, maxclientCount, (!mapname.empty()) ? mapname.data() : "none");
|
||||
wnoutrefresh(InfoWindow);
|
||||
}
|
||||
else if (IsWindow(Console::GetWindow()) != FALSE)
|
||||
{
|
||||
@ -71,13 +93,13 @@ namespace Components
|
||||
|
||||
void Console::ShowPrompt()
|
||||
{
|
||||
wattron(Console::InputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||
wprintw(Console::InputWindow, "%s> ", VERSION);
|
||||
wattron(InputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||
wprintw(InputWindow, "%s> ", VERSION);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -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()
|
||||
{
|
||||
if (!Console::HasConsole)
|
||||
{
|
||||
Console::ShowPrompt();
|
||||
wrefresh(Console::InputWindow);
|
||||
wrefresh(InputWindow);
|
||||
Console::HasConsole = true;
|
||||
}
|
||||
|
||||
@ -126,7 +186,7 @@ namespace Components
|
||||
Console::LastRefresh = currentTime;
|
||||
}
|
||||
|
||||
int c = wgetch(Console::InputWindow);
|
||||
int c = wgetch(InputWindow);
|
||||
|
||||
if (c == ERR)
|
||||
{
|
||||
@ -138,21 +198,21 @@ namespace Components
|
||||
case '\r':
|
||||
case 459: // keypad enter
|
||||
{
|
||||
wattron(Console::OutputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||
wprintw(Console::OutputWindow, "%s", "]");
|
||||
wattron(OutputWindow, COLOR_PAIR(10) | A_BOLD);
|
||||
wprintw(OutputWindow, "%s", "]");
|
||||
|
||||
if (Console::LineBufferIndex)
|
||||
{
|
||||
wprintw(Console::OutputWindow, "%s", Console::LineBuffer);
|
||||
wprintw(OutputWindow, "%s", Console::LineBuffer);
|
||||
}
|
||||
|
||||
wprintw(Console::OutputWindow, "%s", "\n");
|
||||
wattroff(Console::OutputWindow, A_BOLD);
|
||||
wclear(Console::InputWindow);
|
||||
wprintw(OutputWindow, "%s", "\n");
|
||||
wattroff(OutputWindow, A_BOLD);
|
||||
wclear(InputWindow);
|
||||
|
||||
Console::ShowPrompt();
|
||||
|
||||
wrefresh(Console::InputWindow);
|
||||
wrefresh(InputWindow);
|
||||
|
||||
Console::ScrollOutput(1);
|
||||
Console::RefreshOutput();
|
||||
@ -173,11 +233,11 @@ namespace Components
|
||||
Console::LineBuffer[0] = '\0';
|
||||
Console::LineBufferIndex = 0;
|
||||
|
||||
wclear(Console::InputWindow);
|
||||
wclear(InputWindow);
|
||||
|
||||
Console::ShowPrompt();
|
||||
|
||||
wrefresh(Console::InputWindow);
|
||||
wrefresh(InputWindow);
|
||||
break;
|
||||
}
|
||||
case 8: // backspace
|
||||
@ -187,8 +247,8 @@ namespace Components
|
||||
Console::LineBufferIndex--;
|
||||
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
||||
|
||||
wprintw(Console::InputWindow, "%c %c", static_cast<char>(c), static_cast<char>(c));
|
||||
wrefresh(Console::InputWindow);
|
||||
wprintw(InputWindow, "%c %c", static_cast<char>(c), static_cast<char>(c));
|
||||
wrefresh(InputWindow);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -206,10 +266,10 @@ namespace Components
|
||||
}
|
||||
case KEY_UP:
|
||||
{
|
||||
wclear(Console::InputWindow);
|
||||
wclear(InputWindow);
|
||||
Console::ShowPrompt();
|
||||
wprintw(Console::InputWindow, "%s", Console::LineBuffer2);
|
||||
wrefresh(Console::InputWindow);
|
||||
wprintw(InputWindow, "%s", Console::LineBuffer2);
|
||||
wrefresh(InputWindow);
|
||||
|
||||
strcpy_s(Console::LineBuffer, Console::LineBuffer2);
|
||||
Console::LineBufferIndex = strlen(Console::LineBuffer);
|
||||
@ -223,8 +283,8 @@ namespace Components
|
||||
|
||||
Console::LineBuffer[Console::LineBufferIndex++] = static_cast<char>(c);
|
||||
Console::LineBuffer[Console::LineBufferIndex] = '\0';
|
||||
wprintw(Console::InputWindow, "%c", static_cast<char>(c));
|
||||
wrefresh(Console::InputWindow);
|
||||
wprintw(InputWindow, "%c", static_cast<char>(c));
|
||||
wrefresh(InputWindow);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -236,17 +296,17 @@ namespace Components
|
||||
{
|
||||
__try
|
||||
{
|
||||
delwin(Console::OutputWindow);
|
||||
delwin(Console::InputWindow);
|
||||
delwin(Console::InfoWindow);
|
||||
delwin(OutputWindow);
|
||||
delwin(InputWindow);
|
||||
delwin(InfoWindow);
|
||||
endwin();
|
||||
delscreen(SP);
|
||||
}
|
||||
__finally {}
|
||||
|
||||
Console::OutputWindow = nullptr;
|
||||
Console::InputWindow = nullptr;
|
||||
Console::InfoWindow = nullptr;
|
||||
OutputWindow = nullptr;
|
||||
InputWindow = nullptr;
|
||||
InfoWindow = nullptr;
|
||||
}
|
||||
|
||||
void Console::Create()
|
||||
@ -273,15 +333,15 @@ namespace Components
|
||||
raw();
|
||||
noecho();
|
||||
|
||||
Console::OutputWindow = newpad(Console::Height - 1, Console::Width);
|
||||
Console::InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
|
||||
Console::InfoWindow = newwin(1, Console::Width, 0, 0);
|
||||
OutputWindow = newpad(Console::Height - 1, Console::Width);
|
||||
InputWindow = newwin(1, Console::Width, Console::Height - 1, 0);
|
||||
InfoWindow = newwin(1, Console::Width, 0, 0);
|
||||
|
||||
scrollok(Console::OutputWindow, true);
|
||||
idlok(Console::OutputWindow, true);
|
||||
scrollok(Console::InputWindow, true);
|
||||
nodelay(Console::InputWindow, true);
|
||||
keypad(Console::InputWindow, true);
|
||||
scrollok(OutputWindow, true);
|
||||
idlok(OutputWindow, true);
|
||||
scrollok(InputWindow, true);
|
||||
nodelay(InputWindow, true);
|
||||
keypad(InputWindow, true);
|
||||
|
||||
if (has_colors())
|
||||
{
|
||||
@ -298,10 +358,10 @@ namespace Components
|
||||
init_pair(10, COLOR_WHITE, COLOR_BLACK);
|
||||
}
|
||||
|
||||
wbkgd(Console::InfoWindow, COLOR_PAIR(1));
|
||||
wbkgd(InfoWindow, COLOR_PAIR(1));
|
||||
|
||||
wrefresh(Console::InfoWindow);
|
||||
wrefresh(Console::InputWindow);
|
||||
wrefresh(InfoWindow);
|
||||
wrefresh(InputWindow);
|
||||
|
||||
Console::RefreshOutput();
|
||||
}
|
||||
@ -332,7 +392,7 @@ namespace Components
|
||||
|
||||
void Console::Print(const char* message)
|
||||
{
|
||||
if (!Console::OutputWindow) return;
|
||||
if (!OutputWindow) return;
|
||||
|
||||
const char* p = message;
|
||||
while (*p != '\0')
|
||||
@ -346,34 +406,277 @@ namespace Components
|
||||
|
||||
if (color < 9 && color > 0)
|
||||
{
|
||||
wattron(Console::OutputWindow, COLOR_PAIR(color + 2));
|
||||
wattron(OutputWindow, COLOR_PAIR(color + 2));
|
||||
++p;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
waddch(Console::OutputWindow, *p);
|
||||
waddch(OutputWindow, *p);
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
wattron(Console::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;
|
||||
// }
|
||||
wattron(OutputWindow, COLOR_PAIR(9));
|
||||
|
||||
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()
|
||||
{
|
||||
Console::SkipShutdown = false;
|
||||
@ -475,7 +778,7 @@ namespace Components
|
||||
|
||||
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();
|
||||
}
|
||||
@ -588,14 +891,7 @@ namespace Components
|
||||
Utils::Hook(0x482AC3, Console::RegisterConColor, HOOK_CALL).install()->quick();
|
||||
|
||||
// Modify console style
|
||||
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
|
||||
ApplyConsoleStyle();
|
||||
|
||||
// Don't resize the console
|
||||
Utils::Hook(0x64DC6B, 0x64DCC2, HOOK_JUMP).install()->quick();
|
||||
@ -613,15 +909,10 @@ namespace Components
|
||||
if (Loader::IsPerformingUnitTests()) return;
|
||||
|
||||
// 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(0x43D570, Console::StdOutError, HOOK_JUMP).install()->quick();
|
||||
}
|
||||
Utils::Hook(0x4B2080, Console::StdOutPrint, 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.
|
||||
{
|
||||
@ -630,7 +921,7 @@ namespace Components
|
||||
// Redirect input (]command)
|
||||
Utils::Hook(0x47025A, 0x4F5770, HOOK_CALL).install()->quick();
|
||||
|
||||
Utils::Hook(0x60BB68, []()
|
||||
Utils::Hook(0x60BB68, []
|
||||
{
|
||||
Console::ShowAsyncConsole();
|
||||
}, HOOK_CALL).install()->quick();
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Terminus_4.49.1.ttf.hpp"
|
||||
|
||||
#define OUTPUT_HEIGHT 250
|
||||
#define OUTPUT_MAX_TOP (OUTPUT_HEIGHT - (Console::Height - 2))
|
||||
|
||||
@ -20,10 +22,9 @@ namespace Components
|
||||
static void ShowAsyncConsole();
|
||||
|
||||
private:
|
||||
// Text-based console stuff
|
||||
static WINDOW* OutputWindow;
|
||||
static WINDOW* InputWindow;
|
||||
static WINDOW* InfoWindow;
|
||||
|
||||
static constexpr int OUTPUT_BOX = 0x64;
|
||||
static constexpr int INPUT_BOX = 0x65;
|
||||
|
||||
static int Width;
|
||||
static int Height;
|
||||
@ -32,6 +33,13 @@ namespace Components
|
||||
static int OutBuffer;
|
||||
static int LastRefresh;
|
||||
|
||||
static COLORREF TextColor;
|
||||
static COLORREF BackgroundColor;
|
||||
static HBRUSH ForegroundBrush;
|
||||
static HBRUSH BackgroundBrush;
|
||||
|
||||
static HANDLE CustomConsoleFont;
|
||||
|
||||
static char LineBuffer[1024];
|
||||
static char LineBuffer2[1024];
|
||||
static int LineBufferIndex;
|
||||
@ -69,5 +77,29 @@ namespace Components
|
||||
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 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 "Game/Engine/ScopedCriticalSection.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
@ -264,11 +265,6 @@ namespace Components
|
||||
assert(0 && "a");
|
||||
}
|
||||
|
||||
void Debug::Cbuf_AddServerText_f_Hk()
|
||||
{
|
||||
assert(0 && "Cbuf_AddServerText_f was called.");
|
||||
}
|
||||
|
||||
void Debug::Com_Bug_f(Command::Params* params)
|
||||
{
|
||||
char newFileName[0x105]{};
|
||||
@ -293,7 +289,7 @@ namespace Components
|
||||
|
||||
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)
|
||||
{
|
||||
@ -306,8 +302,6 @@ namespace Components
|
||||
const auto result = CopyFileA(from_ospath, to_ospath, 0);
|
||||
Game::Com_OpenLogFile();
|
||||
|
||||
Game::Sys_LeaveCriticalSection(Game::CRITSECT_CONSOLE);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
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::Set<void(*)()>(0x60BCEA, Com_Assert_f);
|
||||
Utils::Hook(Game::Cbuf_AddServerText_f, Cbuf_AddServerText_f_Hk, HOOK_JUMP).install()->quick();
|
||||
|
||||
#ifdef _DEBUG
|
||||
Command::Add("bug", Com_Bug_f);
|
||||
|
@ -42,7 +42,6 @@ namespace Components
|
||||
static void CG_DrawDebugOverlays_Hk(int localClientNum);
|
||||
|
||||
static void Com_Assert_f();
|
||||
static void Cbuf_AddServerText_f_Hk();
|
||||
static void Com_Bug_f(Command::Params* params);
|
||||
|
||||
static void CL_InitDebugDvars();
|
||||
|
@ -19,6 +19,12 @@ namespace Components
|
||||
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()
|
||||
{
|
||||
static const char* fastfiles[7] =
|
||||
@ -59,8 +65,7 @@ namespace Components
|
||||
Command::Execute("snaps 30");
|
||||
Command::Execute("com_maxfps 125");
|
||||
|
||||
// Process command line?
|
||||
Utils::Hook::Call<void()>(0x60C3D0)();
|
||||
Game::Com_AddStartupCommands();
|
||||
}
|
||||
|
||||
__declspec(naked) void Dedicated::PostInitializationStub()
|
||||
@ -262,7 +267,7 @@ namespace Components
|
||||
|
||||
Scheduler::Loop([]
|
||||
{
|
||||
if ((*Game::com_sv_running)->current.enabled)
|
||||
if (Dedicated::IsRunning())
|
||||
{
|
||||
Dedicated::TransmitGuids();
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ namespace Components
|
||||
static Dvar::Var COMLogFilter;
|
||||
|
||||
static bool IsEnabled();
|
||||
static bool IsRunning();
|
||||
|
||||
static void Heartbeat();
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "GSC/Script.hpp"
|
||||
|
||||
#include <mongoose.h>
|
||||
|
||||
namespace Components
|
||||
{
|
||||
mg_mgr Download::Mgr;
|
||||
static mg_mgr Mgr;
|
||||
|
||||
Download::ClientDownload Download::CLDownload;
|
||||
|
||||
std::thread Download::ServerThread;
|
||||
@ -32,15 +35,15 @@ namespace Components
|
||||
|
||||
if (needPassword)
|
||||
{
|
||||
std::string pass = Dvar::Var("password").get<std::string>();
|
||||
if (pass.empty())
|
||||
const auto password = Dvar::Var("password").get<std::string>();
|
||||
if (password.empty())
|
||||
{
|
||||
// shouldn't ever happen but this is safe
|
||||
Party::ConnectError("A password is required to connect to this server!");
|
||||
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;
|
||||
@ -61,78 +64,56 @@ namespace Components
|
||||
if (!download) return false;
|
||||
download->files.clear();
|
||||
|
||||
std::string error;
|
||||
nlohmann::json listData = nlohmann::json::parse(list);
|
||||
|
||||
if (!error.empty() || !listData.is_array())
|
||||
nlohmann::json listData;
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
download->totalBytes = 0;
|
||||
nlohmann::json::array_t listDataArray = listData;
|
||||
const nlohmann::json::array_t listDataArray = listData;
|
||||
|
||||
for (auto& file : listDataArray)
|
||||
{
|
||||
if (!file.is_object()) return false;
|
||||
|
||||
auto hash = file["hash"];
|
||||
auto name = file["name"];
|
||||
auto size = file["size"];
|
||||
|
||||
if (!hash.is_string() || !name.is_string() || !size.is_number()) return false;
|
||||
|
||||
Download::ClientDownload::File fileEntry;
|
||||
fileEntry.name = name.get<std::string>();
|
||||
fileEntry.hash = hash.get<std::string>();
|
||||
fileEntry.size = size.get<size_t>();
|
||||
|
||||
if (!fileEntry.name.empty())
|
||||
try
|
||||
{
|
||||
download->files.push_back(fileEntry);
|
||||
download->totalBytes += fileEntry.size;
|
||||
const auto hash = file.at("hash").get<std::string>();
|
||||
const auto name = file.at("name").get<std::string>();
|
||||
const auto size = file.at("size").get<std::size_t>();
|
||||
|
||||
Download::ClientDownload::File fileEntry;
|
||||
fileEntry.name = name;
|
||||
fileEntry.hash = hash;
|
||||
fileEntry.size = size;
|
||||
|
||||
if (!fileEntry.name.empty())
|
||||
{
|
||||
download->files.push_back(fileEntry);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!download || download->files.size() <= index) return false;
|
||||
@ -214,21 +195,7 @@ namespace Components
|
||||
|
||||
Utils::String::Replace(url, " ", "%20");
|
||||
|
||||
// Just a speedtest ;)
|
||||
//download->totalBytes = 1048576000;
|
||||
//url = "http://speed.hetzner.de/1GB.bin";
|
||||
|
||||
download->valid = true;
|
||||
/*ZeroMemory(&download->mgr, sizeof download->mgr);
|
||||
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;
|
||||
|
||||
@ -373,33 +340,8 @@ namespace Components
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Server
|
||||
|
||||
bool Download::IsClient(mg_connection *nc)
|
||||
{
|
||||
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)
|
||||
|
||||
void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes)
|
||||
{
|
||||
fDownload->receivedBytes += bytes;
|
||||
fDownload->download->downBytes += bytes;
|
||||
@ -415,10 +357,10 @@ namespace Components
|
||||
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;
|
||||
dlSize = fDownload->download->files.size();
|
||||
dlProgress = static_cast<unsigned int>(progress);
|
||||
dlProgress = static_cast<std::uint32_t>(progress);
|
||||
|
||||
framePushed = true;
|
||||
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>();
|
||||
if (g_password.empty()) return true;
|
||||
const auto status = ServerInfo::GetInfo();
|
||||
const auto host = ServerInfo::GetHostInfo();
|
||||
|
||||
// sha256 hashes are 64 chars long but we're gonna be safe here
|
||||
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;
|
||||
std::unordered_map<std::string, nlohmann::json> info;
|
||||
info["status"] = status.to_json();
|
||||
info["host"] = host.to_json();
|
||||
|
||||
std::vector<nlohmann::json> players;
|
||||
|
||||
// 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["ping"] = 0;
|
||||
playerInfo["name"] = "";
|
||||
|
||||
if ((*Game::com_sv_running)->current.enabled)
|
||||
if (Dedicated::IsRunning())
|
||||
{
|
||||
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
||||
|
||||
@ -760,133 +434,254 @@ namespace Components
|
||||
else
|
||||
{
|
||||
// Score and ping are irrelevant
|
||||
const char* namePtr = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
|
||||
if (!namePtr || !namePtr[0]) continue;
|
||||
const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
|
||||
if (!name || *name == '\0') continue;
|
||||
|
||||
playerInfo["name"] = namePtr;
|
||||
playerInfo["name"] = name;
|
||||
}
|
||||
|
||||
players.emplace_back(playerInfo);
|
||||
}
|
||||
|
||||
info["players"] = players;
|
||||
|
||||
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;
|
||||
return {nlohmann::json(info).dump()};
|
||||
}
|
||||
|
||||
void Download::EventHandler(mg_connection *nc, int ev, void *ev_data)
|
||||
static std::string ListHandler()
|
||||
{
|
||||
// Only handle http requests
|
||||
if (ev != MG_EV_HTTP_REQUEST) return;
|
||||
static nlohmann::json jsonList;
|
||||
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)
|
||||
// {
|
||||
// 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
|
||||
if (!fs_gameDirVar.empty() && !handled)
|
||||
{
|
||||
//std::string path = (Dvar::Var("fs_basepath").get<std::string>() + "\\" BASEGAME "\\html");
|
||||
//mg_serve_http_opts opts = { 0 };
|
||||
//opts.document_root = path.data();
|
||||
//mg_serve_http(nc, message, opts);
|
||||
handled = true;
|
||||
|
||||
FileSystem::File file;
|
||||
std::string url = "html" + std::string(message->uri.p, message->uri.len);
|
||||
std::vector<nlohmann::json> fileList;
|
||||
|
||||
if (Utils::String::EndsWith(url, "/"))
|
||||
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)
|
||||
{
|
||||
url.append("index.html");
|
||||
file = FileSystem::File(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = FileSystem::File(url);
|
||||
|
||||
if (!file.exists())
|
||||
std::string filename = path + "\\" + file;
|
||||
if (file.find("_svr_") == std::string::npos)
|
||||
{
|
||||
url.append("/index.html");
|
||||
file = FileSystem::File(url);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
std::string mimeType = Utils::GetMimeType(url);
|
||||
jsonList = fileList;
|
||||
}
|
||||
|
||||
if (file.exists())
|
||||
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)
|
||||
{
|
||||
std::string buffer = file.getBuffer();
|
||||
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;
|
||||
}
|
||||
|
||||
mg_printf(nc,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n", mimeType.data(), buffer.size());
|
||||
file["name"] = mapName + Maps::UserMapFiles[i];
|
||||
file["size"] = fileBuffer.size();
|
||||
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
||||
|
||||
mg_send(nc, buffer.data(), static_cast<int>(buffer.size()));
|
||||
fileList.emplace_back(file);
|
||||
}
|
||||
else
|
||||
|
||||
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)
|
||||
{
|
||||
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");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||
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;
|
||||
|
||||
if (url.ends_with("/"))
|
||||
{
|
||||
url.append("index.html");
|
||||
file = FileSystem::File(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = FileSystem::File(url);
|
||||
if (!file.exists())
|
||||
{
|
||||
url.append("/index.html");
|
||||
file = FileSystem::File(url);
|
||||
}
|
||||
}
|
||||
|
||||
const auto mimeType = Utils::GetMimeType(url);
|
||||
|
||||
if (file.exists())
|
||||
{
|
||||
mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n");
|
||||
mg_printf(c, "Content-Type: %s\r\n", mimeType.data());
|
||||
mg_printf(c, "Content-Length: %d\r\n", static_cast<int>(file.getBuffer().size()));
|
||||
mg_printf(c, "%s", "Connection: close\r\n");
|
||||
mg_printf(c, "%s", "\r\n");
|
||||
mg_send(c, file.getBuffer().data(), file.getBuffer().size());
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found");
|
||||
}
|
||||
}
|
||||
|
||||
static void EventHandler(mg_connection* c, int ev, void* ev_data, [[maybe_unused]] void* fn_data)
|
||||
{
|
||||
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
|
||||
|
||||
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(&Download::Mgr, nullptr);
|
||||
mg_mgr_init(&Mgr);
|
||||
|
||||
Network::OnStart([]()
|
||||
Network::OnStart([]
|
||||
{
|
||||
mg_connection* nc = mg_bind(&Download::Mgr, Utils::String::VA("%hu", Network::GetPort()), Download::EventHandler);
|
||||
|
||||
if (nc)
|
||||
const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr);
|
||||
if (!nc)
|
||||
{
|
||||
// Handle special requests
|
||||
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");
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n");
|
||||
}
|
||||
});
|
||||
|
||||
@ -896,7 +691,7 @@ namespace Components
|
||||
{
|
||||
while (!Download::Terminate)
|
||||
{
|
||||
mg_mgr_poll(&Download::Mgr, 100);
|
||||
mg_mgr_poll(&Mgr, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -917,13 +712,8 @@ namespace Components
|
||||
|
||||
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<const char*>("sv_wwwBaseUrl", "", Game::DVAR_ARCHIVE, "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).");
|
||||
Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server.");
|
||||
Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
|
||||
Script::AddFunction("HttpGet", Script::ShowDeprecationWarning);
|
||||
@ -934,7 +724,7 @@ namespace Components
|
||||
{
|
||||
if (Download::ServerRunning)
|
||||
{
|
||||
mg_mgr_free(&Download::Mgr);
|
||||
mg_mgr_free(&Mgr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include <mongoose.h>
|
||||
|
||||
namespace Components
|
||||
{
|
||||
@ -26,24 +25,23 @@ namespace Components
|
||||
bool terminateThread;
|
||||
bool isMap;
|
||||
bool isPrivate;
|
||||
//mg_mgr mgr;
|
||||
Network::Address target;
|
||||
std::string hashedPassword;
|
||||
std::string mod;
|
||||
std::thread thread;
|
||||
|
||||
size_t totalBytes;
|
||||
size_t downBytes;
|
||||
std::size_t totalBytes;
|
||||
std::size_t downBytes;
|
||||
|
||||
int lastTimeStamp;
|
||||
size_t timeStampBytes;
|
||||
std::size_t timeStampBytes;
|
||||
|
||||
class File
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
std::string hash;
|
||||
size_t size;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
std::vector<File> files;
|
||||
@ -64,7 +62,6 @@ namespace Components
|
||||
if (this->valid)
|
||||
{
|
||||
this->valid = false;
|
||||
//mg_mgr_free(&(this->mgr));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -79,30 +76,15 @@ namespace Components
|
||||
bool downloading;
|
||||
unsigned int index;
|
||||
std::string buffer;
|
||||
size_t receivedBytes;
|
||||
std::size_t receivedBytes;
|
||||
};
|
||||
|
||||
static mg_mgr Mgr;
|
||||
static ClientDownload CLDownload;
|
||||
static std::thread ServerThread;
|
||||
static bool Terminate;
|
||||
static bool ServerRunning;
|
||||
|
||||
static void DownloadProgress(FileDownload* fDownload, size_t bytes);
|
||||
|
||||
static bool VerifyPassword(mg_connection *nc, http_message* message);
|
||||
|
||||
static void EventHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
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 DownloadProgress(FileDownload* fDownload, std::size_t bytes);
|
||||
|
||||
static void ModDownloader(ClientDownload* download);
|
||||
static bool ParseModList(ClientDownload* download, const std::string& list);
|
||||
|
@ -4,6 +4,8 @@ namespace Components
|
||||
{
|
||||
const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg";
|
||||
|
||||
Dvar::Var Dvar::Name;
|
||||
|
||||
Dvar::Var::Var(const std::string& dvarName)
|
||||
{
|
||||
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 (this->dvar_ == nullptr)
|
||||
{
|
||||
this->dvar_ = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "",
|
||||
Game::DvarSetSource::DVAR_SOURCE_INTERNAL));
|
||||
this->dvar_ = const_cast<Game::dvar_t*>(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", Game::DVAR_SOURCE_INTERNAL));
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,8 +27,7 @@ namespace Components
|
||||
if (this->dvar_ == nullptr)
|
||||
return "";
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_STRING
|
||||
|| this->dvar_->type == Game::DVAR_TYPE_ENUM)
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_STRING || this->dvar_->type == Game::DVAR_TYPE_ENUM)
|
||||
{
|
||||
if (this->dvar_->current.string != nullptr)
|
||||
return this->dvar_->current.string;
|
||||
@ -82,8 +82,8 @@ namespace Components
|
||||
if (this->dvar_ == nullptr)
|
||||
return vector;
|
||||
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3
|
||||
|| this->dvar_->type == Game::DVAR_TYPE_FLOAT_4)
|
||||
if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3 ||
|
||||
this->dvar_->type == Game::DVAR_TYPE_FLOAT_4)
|
||||
{
|
||||
return this->dvar_->current.vector;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Dvar::ResetDvarsValue()
|
||||
{
|
||||
if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath))
|
||||
if (!Utils::IO::FileExists(ArchiveDvarPath))
|
||||
return;
|
||||
|
||||
Command::Execute("exec archivedvars.cfg", true);
|
||||
// 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
|
||||
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
||||
@ -224,7 +224,7 @@ namespace Components
|
||||
Scheduler::Loop([]
|
||||
{
|
||||
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
|
||||
if (name == lastValidName) return;
|
||||
@ -232,8 +232,8 @@ namespace Components
|
||||
std::string saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(Utils::String::Trim(name)));
|
||||
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);
|
||||
Dvar::Var("name").set(lastValidName);
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name);
|
||||
Name.set(lastValidName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -249,18 +249,19 @@ namespace Components
|
||||
{
|
||||
const char* steamName = Steam::Proxy::SteamFriends->GetPersonaName();
|
||||
|
||||
if (steamName && !std::string(steamName).empty())
|
||||
if (steamName && *steamName != '\0')
|
||||
{
|
||||
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)
|
||||
{
|
||||
static const char* exceptions[] =
|
||||
static std::array<const char*, 8> exceptions =
|
||||
{
|
||||
"ui_showEndOfGame",
|
||||
"systemlink",
|
||||
@ -272,21 +273,21 @@ namespace Components
|
||||
"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;
|
||||
}
|
||||
}
|
||||
|
||||
Dvar::SetFromStringByNameExternal(dvarName, string);
|
||||
SetFromStringByNameExternal(dvarName, 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()
|
||||
@ -303,23 +304,21 @@ namespace Components
|
||||
|
||||
void Dvar::SaveArchiveDvar(const Game::dvar_t* var)
|
||||
{
|
||||
if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath))
|
||||
if (!Utils::IO::FileExists(ArchiveDvarPath))
|
||||
{
|
||||
Utils::IO::WriteFile(Dvar::ArchiveDvarPath,
|
||||
"// generated by IW4x, do not modify\n");
|
||||
Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n");
|
||||
}
|
||||
|
||||
Utils::IO::WriteFile(Dvar::ArchiveDvarPath,
|
||||
Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true);
|
||||
Utils::IO::WriteFile(ArchiveDvarPath, 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
|
||||
const auto* dvar = Game::Dvar_FindVar(dvarName);
|
||||
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);
|
||||
return;
|
||||
@ -328,7 +327,7 @@ namespace Components
|
||||
#ifdef DEBUG_DVARS
|
||||
Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Server is overriding saved dvar '{}'\n", dvarName);
|
||||
#endif
|
||||
Dvar::SaveArchiveDvar(dvar);
|
||||
SaveArchiveDvar(dvar);
|
||||
}
|
||||
|
||||
Utils::Hook::Call<void(const char*, const char*)>(0x4F52E0)(dvarName, value);
|
||||
@ -348,7 +347,7 @@ namespace Components
|
||||
pushad
|
||||
|
||||
push eax
|
||||
call Dvar::OnRegisterVariant
|
||||
call OnRegisterVariant
|
||||
add esp, 0x4
|
||||
|
||||
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()
|
||||
{
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
Utils::Hook::Xor<BYTE>(0x4F9992, Game::DVAR_ARCHIVE);
|
||||
Utils::Hook::Xor<std::uint8_t>(0x4F9992, Game::DVAR_ARCHIVE);
|
||||
|
||||
// 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
|
||||
// because that's the max on SP
|
||||
@ -395,57 +411,57 @@ namespace Components
|
||||
Utils::Hook::Set<float*>(0x408078, &volume);
|
||||
|
||||
// Uncheat ui_showList
|
||||
Utils::Hook::Xor<BYTE>(0x6310DC, Game::DVAR_CHEAT);
|
||||
Utils::Hook::Xor<std::uint8_t>(0x6310DC, Game::DVAR_CHEAT);
|
||||
|
||||
// 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
|
||||
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
|
||||
Utils::Hook::Xor<INT>(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<BYTE>(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::uint32_t>(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal
|
||||
Utils::Hook::Xor<std::uint32_t>(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical
|
||||
Utils::Hook::Xor<std::uint8_t>(0x42E398, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_horizontal
|
||||
Utils::Hook::Xor<std::uint8_t>(0x42E3C4, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_vertical
|
||||
|
||||
// Don't allow setting cheat protected dvars via menus
|
||||
Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x63CA96, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x63CDB5, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x635E47, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x63C897, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x63CA96, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x63CDB5, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x635E47, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
|
||||
// Script_SetDvar
|
||||
Utils::Hook(0x63444C, Dvar::SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x63444C, SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick();
|
||||
|
||||
// Slider
|
||||
Utils::Hook(0x636159, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636189, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x6364EA, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636159, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636189, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x6364EA, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
|
||||
Utils::Hook(0x636207, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636608, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636695, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
|
||||
// Entirely block setting cheat dvars internally without sv_cheats
|
||||
//Utils::Hook(0x4F52EC, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636207, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636608, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x636695, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
|
||||
|
||||
// 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
|
||||
Scheduler::Once(Dvar::ResetDvarsValue, Scheduler::Pipeline::MAIN);
|
||||
Scheduler::Once(ResetDvarsValue, Scheduler::Pipeline::MAIN);
|
||||
|
||||
// Reset archive dvars when client leaves a server
|
||||
Events::OnSteamDisconnect(Dvar::ResetDvarsValue);
|
||||
Events::OnSteamDisconnect(ResetDvarsValue);
|
||||
|
||||
// For debugging
|
||||
Utils::Hook(0x6483FA, Dvar::Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x648438, Dvar::Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x6483FA, 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()
|
||||
{
|
||||
Utils::IO::RemoveFile(Dvar::ArchiveDvarPath);
|
||||
Utils::IO::RemoveFile(ArchiveDvarPath);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace Components
|
||||
{
|
||||
public:
|
||||
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;
|
||||
};
|
||||
@ -51,17 +51,20 @@ namespace Components
|
||||
|
||||
private:
|
||||
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 SetFromStringByNameSafeExternal(const char* dvar, const char* value);
|
||||
static void SetFromStringByNameExternal(const char* dvarName, const char* string);
|
||||
static void SetFromStringByNameSafeExternal(const char* dvarName, const char* string);
|
||||
|
||||
static bool AreArchiveDvarsProtected();
|
||||
static void SaveArchiveDvar(const Game::dvar_t* var);
|
||||
static 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 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;
|
||||
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled())
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
return;
|
||||
|
||||
Friends::UIStreamFriendly = Dvar::Register<bool>("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI");
|
||||
@ -720,7 +720,7 @@ namespace Components
|
||||
|
||||
Friends::~Friends()
|
||||
{
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) return;
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
|
||||
|
||||
Friends::StoreFriendsList();
|
||||
|
||||
|
@ -106,6 +106,31 @@ namespace Components
|
||||
|
||||
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()
|
||||
|
@ -72,15 +72,15 @@ namespace Components
|
||||
const auto* op = Game::Scr_GetString(1);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,14 @@ namespace Components
|
||||
std::vector<int> Script::ScriptMainHandles;
|
||||
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()
|
||||
{
|
||||
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName);
|
||||
@ -297,13 +305,11 @@ namespace Components
|
||||
{
|
||||
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 (got != Script::CustomScrFunctions.end())
|
||||
if (const auto itr = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrFunctions.end())
|
||||
{
|
||||
*type = got->second.type;
|
||||
return got->second.actionFunc;
|
||||
*type = itr->second.type;
|
||||
return itr->second.actionFunc;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -321,13 +327,11 @@ namespace Components
|
||||
{
|
||||
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 (got != Script::CustomScrMethods.end())
|
||||
if (const auto itr = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrMethods.end())
|
||||
{
|
||||
*type = got->second.type;
|
||||
return got->second.actionFunc;
|
||||
*type = itr->second.type;
|
||||
return itr->second.actionFunc;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -437,7 +441,7 @@ namespace Components
|
||||
|
||||
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");
|
||||
return "";
|
||||
@ -534,14 +538,6 @@ namespace Components
|
||||
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()
|
||||
{
|
||||
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
|
||||
|
@ -51,11 +51,9 @@ namespace Components
|
||||
if (classnum == Game::ClassNum::CLASS_NUM_ENTITY)
|
||||
{
|
||||
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
||||
|
||||
const auto got =CustomEntityFields.find(entity_offset);
|
||||
if (got != CustomEntityFields.end())
|
||||
if (const auto itr = CustomEntityFields.find(entity_offset); itr != CustomEntityFields.end())
|
||||
{
|
||||
got->second.setter(&Game::g_entities[entnum], offset);
|
||||
itr->second.setter(&Game::g_entities[entnum], offset);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -68,11 +66,9 @@ namespace Components
|
||||
void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset)
|
||||
{
|
||||
const auto client_offset = static_cast<std::uint16_t>(offset);
|
||||
|
||||
const auto got = CustomClientFields.find(client_offset);
|
||||
if (got != CustomClientFields.end())
|
||||
if (const auto itr = CustomClientFields.find(client_offset); itr != CustomClientFields.end())
|
||||
{
|
||||
got->second.setter(client, &got->second);
|
||||
itr->second.setter(client, &itr->second);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -87,13 +83,11 @@ namespace Components
|
||||
// If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client
|
||||
if (Game::g_entities[entnum].client != nullptr)
|
||||
{
|
||||
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
|
||||
|
||||
const auto got =CustomClientFields.find(client_offset);
|
||||
if (got != CustomClientFields.end())
|
||||
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
|
||||
if (const auto itr = CustomClientFields.find(client_offset); itr != CustomClientFields.end())
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@ -102,10 +96,9 @@ namespace Components
|
||||
// Regular entity offsets can be searched directly in our custom handler
|
||||
const auto entity_offset = static_cast<std::uint16_t>(offset);
|
||||
|
||||
const auto got = CustomEntityFields.find(entity_offset);
|
||||
if (got != CustomEntityFields.end())
|
||||
if (const auto itr = CustomEntityFields.find(entity_offset); itr != CustomEntityFields.end())
|
||||
{
|
||||
got->second.getter(&Game::g_entities[entnum], offset);
|
||||
itr->second.getter(&Game::g_entities[entnum], offset);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -246,7 +239,7 @@ namespace Components
|
||||
void ScriptExtension::AddMethods()
|
||||
{
|
||||
// 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* client = Script::GetClient(ent);
|
||||
@ -259,7 +252,7 @@ namespace Components
|
||||
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* client = Script::GetClient(ent);
|
||||
@ -267,7 +260,7 @@ namespace Components
|
||||
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);
|
||||
|
||||
@ -306,7 +299,7 @@ namespace Components
|
||||
|
||||
void ScriptExtension::AddEntityFields()
|
||||
{
|
||||
AddEntityField("entityflags", Game::fieldtype_t::F_INT,
|
||||
AddEntityField("entityflags", Game::F_INT,
|
||||
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
|
||||
{
|
||||
ent->flags = Game::Scr_GetInt(0);
|
||||
@ -319,7 +312,7 @@ namespace Components
|
||||
|
||||
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)
|
||||
{
|
||||
pSelf->flags = Game::Scr_GetInt(0);
|
||||
|
@ -54,7 +54,6 @@ namespace Components
|
||||
if (!Data.contains(key))
|
||||
{
|
||||
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& data = Data.at(key);
|
||||
@ -91,7 +90,6 @@ namespace Components
|
||||
{
|
||||
Data.clear();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ScriptStorage::ScriptStorage()
|
||||
|
@ -204,7 +204,7 @@ namespace Components
|
||||
|
||||
IPCPipe::IPCPipe()
|
||||
{
|
||||
if (Dedicated::IsEnabled() || Monitor::IsEnabled() || Loader::IsPerformingUnitTests() || ZoneBuilder::IsEnabled()) return;
|
||||
if (Dedicated::IsEnabled() || Loader::IsPerformingUnitTests() || ZoneBuilder::IsEnabled()) return;
|
||||
|
||||
// Server pipe
|
||||
IPCPipe::ServerPipe.onConnect(IPCPipe::ConnectClient);
|
||||
|
@ -5,25 +5,37 @@ namespace Components
|
||||
std::recursive_mutex Localization::LocalizeMutex;
|
||||
Dvar::Var Localization::UseLocalization;
|
||||
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();
|
||||
|
||||
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 (entry->value) allocator->free(entry->value);
|
||||
entry->value = newStaticValue;
|
||||
|
||||
SaveParseOutput(entry);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Game::LocalizeEntry* entry = allocator->allocate<Game::LocalizeEntry>();
|
||||
auto* entry = allocator->allocate<Game::LocalizeEntry>();
|
||||
if (!entry) return;
|
||||
|
||||
entry->name = allocator->duplicateString(key);
|
||||
@ -33,7 +45,7 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
entry->value = allocator->duplicateString(value);
|
||||
entry->value = allocator->duplicateString(psNewString);
|
||||
if (!entry->value)
|
||||
{
|
||||
allocator->free(entry->name);
|
||||
@ -41,24 +53,23 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
Localization::LocalizeMap[key] = entry;
|
||||
SaveParseOutput(entry);
|
||||
|
||||
LocalizeMap[key] = entry;
|
||||
}
|
||||
|
||||
const char* Localization::Get(const char* key)
|
||||
{
|
||||
if (!Localization::UseLocalization.get<bool>()) return key;
|
||||
if (!UseLocalization.get<bool>()) return key;
|
||||
|
||||
Game::LocalizeEntry* entry = nullptr;
|
||||
{
|
||||
std::lock_guard _(Localization::LocalizeMutex);
|
||||
|
||||
if (Localization::TempLocalizeMap.contains(key))
|
||||
{
|
||||
std::lock_guard _(LocalizeMutex);
|
||||
|
||||
if (LocalizeMap.contains(key))
|
||||
{
|
||||
entry = Localization::TempLocalizeMap[key];
|
||||
}
|
||||
else if (Localization::LocalizeMap.contains(key))
|
||||
{
|
||||
entry = Localization::LocalizeMap[key];
|
||||
entry = LocalizeMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,89 +86,21 @@ namespace Components
|
||||
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);
|
||||
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);
|
||||
if (!entry->value)
|
||||
{
|
||||
allocator->free(entry->name);
|
||||
allocator->free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
Localization::TempLocalizeMap[key] = entry;
|
||||
}
|
||||
Set(psLocalReference, psNewString);
|
||||
}
|
||||
|
||||
void Localization::ClearTemp()
|
||||
void Localization::ParseOutput(const std::function<void(Game::LocalizeEntry*)>& callback)
|
||||
{
|
||||
std::lock_guard _(Localization::LocalizeMutex);
|
||||
Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator();
|
||||
|
||||
for (auto i = Localization::TempLocalizeMap.begin(); i != Localization::TempLocalizeMap.end(); ++i)
|
||||
{
|
||||
if (i->second)
|
||||
{
|
||||
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();
|
||||
ParseCallback = callback;
|
||||
}
|
||||
|
||||
void __stdcall Localization::SetStringStub(const char* key, const char* value, bool /*isEnglish*/)
|
||||
void Localization::SaveParseOutput(Game::LocalizeEntry* asset)
|
||||
{
|
||||
Localization::Set(key, value);
|
||||
}
|
||||
|
||||
void Localization::LoadLanguageStrings()
|
||||
{
|
||||
//if (ZoneBuilder::IsEnabled())
|
||||
if (ParseCallback)
|
||||
{
|
||||
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
|
||||
ParseCallback(asset);
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +189,7 @@ namespace Components
|
||||
// I have no idea why, but the last 2 lines are invisible!
|
||||
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)
|
||||
@ -389,38 +332,31 @@ namespace Components
|
||||
|
||||
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 };
|
||||
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];
|
||||
}
|
||||
else if (Localization::LocalizeMap.contains(filename))
|
||||
{
|
||||
header.localize = Localization::LocalizeMap[filename];
|
||||
header.localize = itr->second;
|
||||
}
|
||||
|
||||
return header;
|
||||
});
|
||||
|
||||
// Resolving hook
|
||||
Utils::Hook(0x629B90, Localization::Get, HOOK_JUMP).install()->quick();
|
||||
|
||||
// Set loading entry point
|
||||
Utils::Hook(0x41D859, Localization::SELoadLanguageStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x629B90, Get, HOOK_JUMP).install()->quick();
|
||||
|
||||
// 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);
|
||||
|
||||
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
|
||||
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 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::ClearTemp();
|
||||
Localization::LocalizeMap.clear();
|
||||
LocalizeMap.clear();
|
||||
}
|
||||
}
|
||||
|
@ -8,21 +8,23 @@ namespace Components
|
||||
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 void SetTemp(const std::string& key, const std::string& value);
|
||||
static void ClearTemp();
|
||||
static std::optional<std::string> PrefixOverride;
|
||||
static void ParseOutput(const std::function<void(Game::LocalizeEntry*)>& callback);
|
||||
|
||||
private:
|
||||
static std::recursive_mutex LocalizeMutex;
|
||||
static std::unordered_map<std::string, Game::LocalizeEntry*> LocalizeMap;
|
||||
static std::unordered_map<std::string, Game::LocalizeEntry*> TempLocalizeMap;
|
||||
static Dvar::Var UseLocalization;
|
||||
|
||||
static void __stdcall SetStringStub(const char* key, const char* value, bool isEnglish);
|
||||
static void LoadLanguageStrings();
|
||||
static void SELoadLanguageStub();
|
||||
static std::function<void(Game::LocalizeEntry*)> ParseCallback;
|
||||
|
||||
static void __stdcall SetStringStub(const char* psLocalReference, const char* psNewString, int bSentenceIsEnglish);
|
||||
|
||||
static void SaveParseOutput(Game::LocalizeEntry* asset);
|
||||
|
||||
static void SetCredits();
|
||||
|
||||
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::vector<std::string> Logger::MessageQueue;
|
||||
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()
|
||||
{
|
||||
@ -106,21 +107,6 @@ namespace Components
|
||||
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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -177,9 +163,9 @@ namespace Components
|
||||
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);
|
||||
|
||||
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
|
||||
|
@ -12,9 +12,7 @@ namespace Components
|
||||
|
||||
static void Print_Stub(int channel, const char* message, ...);
|
||||
|
||||
static void PipeOutput(void(*callback)(const std::string&));
|
||||
|
||||
static void Flush();
|
||||
static void PipeOutput(const std::function<void(const std::string&)>& callback);
|
||||
|
||||
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);
|
||||
@ -96,10 +94,13 @@ namespace Components
|
||||
static std::mutex MessageMutex;
|
||||
static std::vector<std::string> MessageQueue;
|
||||
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 Frame();
|
||||
|
||||
static void G_LogPrintf_Hk(const char* fmt, ...);
|
||||
static void PrintMessage_Stub();
|
||||
static void PrintMessagePipe(const char* data);
|
||||
|
@ -408,7 +408,7 @@ namespace Components
|
||||
{
|
||||
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]);
|
||||
if (Utils::IO::FileExists(filePath))
|
||||
|
@ -859,11 +859,6 @@ namespace Components
|
||||
Menus::Add("ui_mp/resetclass.menu");
|
||||
Menus::Add("ui_mp/popup_customtitle.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()
|
||||
|
@ -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;
|
||||
// Packet interception
|
||||
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)
|
||||
{
|
||||
@ -17,7 +16,7 @@ namespace Components
|
||||
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);
|
||||
}
|
||||
@ -151,13 +150,11 @@ namespace Components
|
||||
|
||||
void Network::Send(Game::netsrc_t type, Address target, const std::string& data)
|
||||
{
|
||||
// NET_OutOfBandPrint only supports non-binary data!
|
||||
//Game::NET_OutOfBandPrint(type, *target.Get(), data.data());
|
||||
// Do not use NET_OutOfBandPrint. It only supports non-binary data!
|
||||
|
||||
std::string rawData;
|
||||
rawData.append("\xFF\xFF\xFF\xFF", 4);
|
||||
rawData.append(data);
|
||||
//rawData.append("\0", 1);
|
||||
|
||||
SendRaw(type, target, rawData);
|
||||
}
|
||||
@ -171,8 +168,7 @@ namespace Components
|
||||
{
|
||||
if (!target.isValid()) return;
|
||||
|
||||
// NET_OutOfBandData doesn't seem to work properly
|
||||
//Game::NET_OutOfBandData(type, *target.Get(), data.data(), data.size());
|
||||
// NET_OutOfBandData doesn't seem to work properly. Do not use it
|
||||
Game::Sys_SendPacket(type, data.size(), data.data(), *target.get());
|
||||
}
|
||||
|
||||
@ -282,11 +278,6 @@ namespace Components
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
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;
|
||||
auto address_ = Address(address);
|
||||
handler->second(address_, data);
|
||||
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()
|
||||
{
|
||||
AssertSize(Game::netadr_t, 20);
|
||||
@ -420,7 +362,6 @@ namespace Components
|
||||
|
||||
// Handle client packets
|
||||
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
|
||||
Utils::Hook::Set<BYTE>(0x5AA5B6, 0xEB); // CL_SteamServerAuth
|
||||
|
@ -8,7 +8,7 @@ namespace Components
|
||||
class Address
|
||||
{
|
||||
public:
|
||||
Address() { setType(Game::netadrtype_t::NA_BAD); }
|
||||
Address() { setType(Game::NA_BAD); }
|
||||
Address(const std::string& addrString);
|
||||
Address(sockaddr* addr);
|
||||
Address(sockaddr addr) : Address(&addr) {}
|
||||
@ -73,12 +73,10 @@ namespace Components
|
||||
static void BroadcastAll(const std::string& data);
|
||||
|
||||
static void OnClientPacket(const std::string& command, const NetworkCallback& callback);
|
||||
static void OnServerPacket(const std::string& command, const NetworkCallback& callback);
|
||||
|
||||
private:
|
||||
static Utils::Signal<CallbackRaw> StartupSignal;
|
||||
static std::unordered_map<std::string, NetworkCallback> CL_Callbacks;
|
||||
static std::unordered_map<std::string, NetworkCallback> SV_Callbacks;
|
||||
|
||||
static void NetworkStart();
|
||||
static void NetworkStartStub();
|
||||
@ -88,10 +86,8 @@ namespace Components
|
||||
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 SV_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
|
||||
|
||||
static void CL_HandleCommandStub();
|
||||
static void SV_HandleCommandStub();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,11 +50,6 @@ namespace Components
|
||||
this->lastRequest.reset();
|
||||
}
|
||||
|
||||
nlohmann::json Node::Entry::to_json() const
|
||||
{
|
||||
return this->address.getString();
|
||||
}
|
||||
|
||||
void Node::LoadNodeRemotePreset()
|
||||
{
|
||||
std::string nodes = Utils::Cache::GetFile("/iw4/nodes.txt");
|
||||
@ -73,18 +68,10 @@ namespace Components
|
||||
{
|
||||
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");
|
||||
if (!defaultNodes.exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.getBuffer()))) return;
|
||||
}
|
||||
FileSystem::File defaultNodes("nodes_default.dat");
|
||||
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);
|
||||
|
||||
@ -171,7 +158,7 @@ namespace Components
|
||||
|
||||
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)
|
||||
{
|
||||
@ -251,7 +238,7 @@ namespace Components
|
||||
|
||||
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());
|
||||
ServerList::InsertRequest(address);
|
||||
@ -361,8 +348,7 @@ namespace Components
|
||||
Node::LoadNodes();
|
||||
};
|
||||
|
||||
if (Monitor::IsEnabled()) Network::OnStart(loadNodes);
|
||||
else Scheduler::OnGameInitialized(loadNodes, Scheduler::Pipeline::MAIN);
|
||||
Scheduler::OnGameInitialized(loadNodes, Scheduler::Pipeline::MAIN);
|
||||
|
||||
Network::OnStart([]
|
||||
{
|
||||
|
@ -31,7 +31,6 @@ namespace Components
|
||||
void sendRequest();
|
||||
|
||||
void reset();
|
||||
nlohmann::json to_json() const;
|
||||
};
|
||||
|
||||
Node();
|
||||
|
@ -65,7 +65,6 @@ namespace Components
|
||||
|
||||
void Party::ConnectError(const std::string& message)
|
||||
{
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Dvar::Var("partyend_reason").set(message);
|
||||
Command::Execute("openmenu menu_xboxlive_partyended");
|
||||
@ -139,7 +138,7 @@ namespace Components
|
||||
|
||||
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()
|
||||
@ -312,7 +311,7 @@ namespace Components
|
||||
}
|
||||
|
||||
// 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 clientCount = 0;
|
||||
@ -352,7 +351,7 @@ namespace Components
|
||||
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("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("voiceChat", (Voice::SV_VoiceEnabled() ? "1" : "0"));
|
||||
|
||||
|
@ -68,7 +68,7 @@ namespace Components
|
||||
{
|
||||
// check g_antilag dvar value
|
||||
mov eax, g_antilag;
|
||||
cmp byte ptr[eax + 16], 1;
|
||||
cmp byte ptr [eax + 16], 1;
|
||||
|
||||
// do antilag if 1
|
||||
je fireWeapon
|
||||
@ -96,7 +96,7 @@ namespace Components
|
||||
{
|
||||
// check g_antilag dvar value
|
||||
mov eax, g_antilag;
|
||||
cmp byte ptr[eax + 16], 1;
|
||||
cmp byte ptr [eax + 16], 1;
|
||||
|
||||
// do antilag if 1
|
||||
je fireWeaponMelee
|
||||
@ -332,6 +332,9 @@ namespace Components
|
||||
// Fix crash as nullptr goes unchecked
|
||||
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)
|
||||
Utils::Hook::Set<int>(0x4FB501, PROTOCOL);
|
||||
|
||||
@ -355,7 +358,7 @@ namespace Components
|
||||
Utils::Hook::Set<BYTE>(0x49C220, 0xC3); // We wanted to send a logging packet, but we haven't connected to LSP!
|
||||
Utils::Hook::Set<BYTE>(0x4BD900, 0xC3); // main LSP response func
|
||||
Utils::Hook::Set<BYTE>(0x682170, 0xC3); // Telling LSP that we're playing a private match
|
||||
Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket
|
||||
Utils::Hook::Nop(0x4FD448, 5); // Don't create lsp_socket
|
||||
|
||||
// Don't delete config files if corrupted
|
||||
Utils::Hook::Set<BYTE>(0x47DCB3, 0xEB);
|
||||
@ -446,11 +449,6 @@ namespace Components
|
||||
// default sv_pure to 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)
|
||||
Utils::Hook::Set<BYTE>(0x5A1D43, 0xEB);
|
||||
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
RCon::Container RCon::BackdoorContainer;
|
||||
Utils::Cryptography::ECC::Key RCon::BackdoorKey;
|
||||
RCon::Container RCon::RconContainer;
|
||||
Utils::Cryptography::ECC::Key RCon::RconKey;
|
||||
|
||||
std::string RCon::Password;
|
||||
|
||||
Dvar::Var RCon::RconPassword;
|
||||
Dvar::Var RCon::RconLogRequests;
|
||||
|
||||
RCon::RCon()
|
||||
void RCon::AddCommands()
|
||||
{
|
||||
Command::Add("rcon", [](Command::Params* params)
|
||||
{
|
||||
@ -20,43 +20,85 @@ namespace Components
|
||||
if (std::strcmp(operation, "login") == 0)
|
||||
{
|
||||
if (params->size() < 3) return;
|
||||
RCon::Password = params->get(2);
|
||||
Password = params->get(2);
|
||||
return;
|
||||
}
|
||||
else if (std::strcmp(operation, "logout") == 0)
|
||||
{
|
||||
RCon::Password.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
|
||||
if (!RCon::Password.empty())
|
||||
{
|
||||
Network::Address target(addr);
|
||||
if (!target.isValid() || target.getIP().full == 0)
|
||||
{
|
||||
target = Party::Target();
|
||||
}
|
||||
|
||||
if (target.isValid())
|
||||
{
|
||||
Network::SendCommand(target, "rcon", RCon::Password + " " + params->join(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
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 (std::strcmp(operation, "logout") == 0)
|
||||
{
|
||||
Password.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
|
||||
if (Password.empty())
|
||||
{
|
||||
Logger::Print("You need to be logged in and connected to a server!\n");
|
||||
}
|
||||
|
||||
Network::Address target(addr);
|
||||
if (!target.isValid() || target.getIP().full == 0)
|
||||
{
|
||||
target = Party::Target();
|
||||
}
|
||||
|
||||
if (target.isValid())
|
||||
{
|
||||
Network::SendCommand(target, "rcon", Password + " " + params->join(1));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::Print("You are connected to an invalid 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
|
||||
static uint8_t publicKey[] =
|
||||
static std::uint8_t publicKey[] =
|
||||
{
|
||||
0x04, 0x01, 0x9D, 0x18, 0x7F, 0x57, 0xD8, 0x95, 0x4C, 0xEE, 0xD0, 0x21,
|
||||
0xB5, 0x00, 0x53, 0xEC, 0xEB, 0x54, 0x7C, 0x4C, 0x37, 0x18, 0x53, 0x89,
|
||||
@ -72,17 +114,17 @@ namespace Components
|
||||
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([]
|
||||
{
|
||||
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");
|
||||
RconPassword = Dvar::Register<const char*>("rcon_password", "", Game::DVAR_NONE, "The password for rcon");
|
||||
RconLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in the output log");
|
||||
}, 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;
|
||||
|
||||
@ -104,7 +146,7 @@ namespace Components
|
||||
password.erase(password.begin());
|
||||
}
|
||||
|
||||
const auto svPassword = RCon::RconPassword.get<std::string>();
|
||||
const auto svPassword = RconPassword.get<std::string>();
|
||||
|
||||
if (svPassword.empty())
|
||||
{
|
||||
@ -112,69 +154,123 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (svPassword == password)
|
||||
{
|
||||
static std::string outputBuffer;
|
||||
outputBuffer.clear();
|
||||
|
||||
#ifndef DEBUG
|
||||
if (RCon::RconLogRequests.get<bool>())
|
||||
#endif
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
||||
}
|
||||
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
outputBuffer.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(command, true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", outputBuffer);
|
||||
outputBuffer.clear();
|
||||
}
|
||||
else
|
||||
if (svPassword != password)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
Network::OnServerPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
{
|
||||
RCon::BackdoorContainer.address = address;
|
||||
RCon::BackdoorContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
|
||||
RCon::BackdoorContainer.timestamp = Game::Sys_Milliseconds();
|
||||
static std::string outputBuffer;
|
||||
outputBuffer.clear();
|
||||
|
||||
Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge);
|
||||
});
|
||||
|
||||
Network::OnServerPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
{
|
||||
if (address != RCon::BackdoorContainer.address) return; // Invalid IP
|
||||
if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout
|
||||
RCon::BackdoorContainer.timestamp = 0;
|
||||
|
||||
Proto::RCon::Command command;
|
||||
command.ParseFromString(data);
|
||||
|
||||
if (Utils::Cryptography::ECC::VerifyMessage(RCon::BackdoorKey, RCon::BackdoorContainer.challenge, command.signature()))
|
||||
#ifndef DEBUG
|
||||
if (RconLogRequests.get<bool>())
|
||||
#endif
|
||||
{
|
||||
RCon::BackdoorContainer.output.clear();
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
RCon::BackdoorContainer.output.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(command.commands(), true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", RCon::BackdoorContainer.output);
|
||||
RCon::BackdoorContainer.output.clear();
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
||||
}
|
||||
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
outputBuffer.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(command, true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", outputBuffer);
|
||||
outputBuffer.clear();
|
||||
});
|
||||
|
||||
Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
{
|
||||
RconContainer.address = address;
|
||||
RconContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
|
||||
RconContainer.timestamp = Game::Sys_Milliseconds();
|
||||
|
||||
Network::SendCommand(address, "rconAuthorization", RconContainer.challenge);
|
||||
});
|
||||
|
||||
Network::OnClientPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
{
|
||||
if (address != RconContainer.address) return; // Invalid IP
|
||||
if (!RconContainer.timestamp || (Game::Sys_Milliseconds() - RconContainer.timestamp) > (1000 * 10)) return; // Timeout
|
||||
|
||||
RconContainer.timestamp = 0;
|
||||
|
||||
Proto::RCon::Command rconExec;
|
||||
rconExec.ParseFromString(data);
|
||||
|
||||
if (!Utils::Cryptography::ECC::VerifyMessage(RconKey, RconContainer.challenge, rconExec.signature()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RconContainer.output.clear();
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
RconContainer.output.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(rconExec.command(), true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", RconContainer.output);
|
||||
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:
|
||||
int timestamp;
|
||||
std::string output;
|
||||
std::string command;
|
||||
std::string challenge;
|
||||
Network::Address address;
|
||||
};
|
||||
|
||||
// Hue hue backdoor
|
||||
static Container BackdoorContainer;
|
||||
static Utils::Cryptography::ECC::Key BackdoorKey;
|
||||
class CryptoKey
|
||||
{
|
||||
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 Dvar::Var RconPassword;
|
||||
static Dvar::Var RconLogRequests;
|
||||
|
||||
static void AddCommands();
|
||||
};
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace Components
|
||||
if ((fileSize + 1) <= size)
|
||||
{
|
||||
Game::FS_Read(buf, fileSize, fileHandle);
|
||||
buf[fileSize] = 0;
|
||||
buf[fileSize] = '\0';
|
||||
Game::FS_FCloseFile(fileHandle);
|
||||
return buf;
|
||||
}
|
||||
@ -62,6 +62,59 @@ namespace Components
|
||||
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()
|
||||
{
|
||||
// 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(0x631640, GetMenuBuffer, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x463500, Com_LoadInfoString_Hk, HOOK_JUMP).install()->quick();
|
||||
|
||||
Command::Add("dumpraw", [](Command::Params* params)
|
||||
{
|
||||
|
@ -11,5 +11,7 @@ namespace Components
|
||||
|
||||
private:
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
Command::ClientParams params;
|
||||
|
||||
for (const auto& [id, callback] : ServerCommands::Commands)
|
||||
for (const auto& [id, callback] : Commands)
|
||||
{
|
||||
if (params.size() >= 1)
|
||||
{
|
||||
@ -33,7 +33,10 @@ namespace Components
|
||||
{
|
||||
push eax
|
||||
pushad
|
||||
|
||||
// Missing localClientNum!
|
||||
call ServerCommands::OnServerCommand
|
||||
|
||||
mov [esp + 20h], eax
|
||||
popad
|
||||
pop eax
|
||||
@ -63,7 +66,7 @@ namespace Components
|
||||
ServerCommands::ServerCommands()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,11 @@ namespace Components
|
||||
public:
|
||||
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:
|
||||
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 void CG_DeployServerCommand_Stub();
|
||||
|
@ -118,7 +118,6 @@ namespace Components
|
||||
{
|
||||
Utils::InfoString info;
|
||||
|
||||
// TODO: Possibly add all Dvar starting with _
|
||||
info.set("admin", Dvar::Var("_Admin").get<const char*>());
|
||||
info.set("website", Dvar::Var("_Website").get<const char*>());
|
||||
info.set("email", Dvar::Var("_Email").get<const char*>());
|
||||
@ -141,6 +140,7 @@ namespace Components
|
||||
info.set("sv_maxclients", Utils::String::VA("%i", maxClientCount));
|
||||
info.set("protocol", Utils::String::VA("%i", PROTOCOL));
|
||||
info.set("shortversion", SHORTVERSION);
|
||||
info.set("version", (*Game::version)->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("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");
|
||||
}
|
||||
else if ((*Game::com_sv_running)->current.enabled) // Match hosting
|
||||
else if (Dedicated::IsRunning()) // Match hosting
|
||||
{
|
||||
info.set("matchtype", "2");
|
||||
}
|
||||
@ -193,24 +193,24 @@ namespace Components
|
||||
// Add uifeeder
|
||||
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;
|
||||
|
||||
Utils::InfoString info = ServerInfo::GetInfo();
|
||||
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 ping = 0;
|
||||
std::string name;
|
||||
|
||||
if ((*Game::com_sv_running)->current.enabled)
|
||||
if (Dedicated::IsRunning())
|
||||
{
|
||||
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;
|
||||
name = Game::svs_clients[i].name;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace Components
|
||||
Dvar::Var ServerList::NETServerQueryLimit;
|
||||
Dvar::Var ServerList::NETServerFrames;
|
||||
|
||||
bool ServerList::useMasterServer = true;
|
||||
bool ServerList::UseMasterServer = true;
|
||||
|
||||
std::vector<ServerList::ServerInfo>* ServerList::GetList()
|
||||
{
|
||||
@ -51,7 +51,7 @@ namespace Components
|
||||
|
||||
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()
|
||||
@ -299,13 +299,13 @@ namespace Components
|
||||
{
|
||||
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);
|
||||
useMasterServer = false;
|
||||
UseMasterServer = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000);
|
||||
|
||||
useMasterServer = true;
|
||||
UseMasterServer = true;
|
||||
|
||||
ServerList::RefreshContainer.awatingList = true;
|
||||
ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds();
|
||||
@ -327,7 +327,17 @@ namespace Components
|
||||
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
|
||||
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())
|
||||
{
|
||||
Logger::Print("Favourites storage file is invalid!\n");
|
||||
@ -363,7 +373,16 @@ namespace Components
|
||||
const auto parseData = Utils::IO::ReadFile(FavouriteFile);
|
||||
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())
|
||||
{
|
||||
@ -409,7 +428,17 @@ namespace Components
|
||||
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())
|
||||
{
|
||||
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);
|
||||
Toast::Show("cardicon_headshot", "^1Error", "Failed to reach master server, using node servers instead.", 5000);
|
||||
|
||||
useMasterServer = false;
|
||||
UseMasterServer = false;
|
||||
Node::Synchronize();
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ namespace Components
|
||||
static void UpdateVisibleInfo();
|
||||
|
||||
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
|
||||
static bool useMasterServer;
|
||||
static bool UseMasterServer;
|
||||
|
||||
private:
|
||||
enum Column
|
||||
|
@ -20,7 +20,7 @@ namespace Components
|
||||
|
||||
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);
|
||||
|
||||
|
@ -4,27 +4,29 @@ namespace Components
|
||||
{
|
||||
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
|
||||
{
|
||||
SlowMotion::Delay -= timePassed;
|
||||
Delay -= msec;
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void SlowMotion::ApplySlowMotionStub()
|
||||
__declspec(naked) void SlowMotion::Com_UpdateSlowMotion_Stub()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
|
||||
push [esp + 24h]
|
||||
call SlowMotion::ApplySlowMotion
|
||||
add esp, 4h
|
||||
push [esp + 0x20 + 0x4]
|
||||
call Com_UpdateSlowMotion
|
||||
add esp, 0x4
|
||||
|
||||
popad
|
||||
|
||||
@ -32,23 +34,23 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void SlowMotion::SetSlowMotion()
|
||||
void SlowMotion::ScrCmd_SetSlowMotion_Stub()
|
||||
{
|
||||
int duration = 1000;
|
||||
float start = Game::Scr_GetFloat(0);
|
||||
float end = 1.0f;
|
||||
auto duration = 1000;
|
||||
auto start = Game::Scr_GetFloat(0);
|
||||
auto end = 1.0f;
|
||||
|
||||
if (Game::Scr_GetNumParam() >= 2u)
|
||||
if (Game::Scr_GetNumParam() >= 2)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
int delay = 0;
|
||||
auto delay = 0;
|
||||
|
||||
if (start > end)
|
||||
{
|
||||
@ -65,30 +67,32 @@ namespace Components
|
||||
duration = duration - delay;
|
||||
|
||||
Game::Com_SetSlowMotion(start, end, duration);
|
||||
SlowMotion::Delay = delay;
|
||||
Delay = delay;
|
||||
|
||||
// set snapshot num to 1 behind (T6 does this, why shouldn't we?)
|
||||
for (int i = 0; i < *Game::svs_clientCount; ++i)
|
||||
// Set snapshot num to 1 behind (T6 does this, why shouldn't we?)
|
||||
for (auto i = 0; i < *Game::svs_clientCount; ++i)
|
||||
{
|
||||
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))
|
||||
// {
|
||||
// Utils::Hook::Call<void(int)>(0x454A70)(a1);
|
||||
// }
|
||||
if (cg_drawDisconnect->current.enabled)
|
||||
{
|
||||
Game::CG_DrawDisconnect(localClientNum);
|
||||
}
|
||||
}
|
||||
|
||||
SlowMotion::SlowMotion()
|
||||
{
|
||||
SlowMotion::Delay = 0;
|
||||
Utils::Hook(0x5F5FF2, SlowMotion::SetSlowMotion, HOOK_JUMP).install()->quick();
|
||||
Utils::Hook(0x60B38A, SlowMotion::ApplySlowMotionStub, HOOK_CALL).install()->quick();
|
||||
cg_drawDisconnect = Game::Dvar_RegisterBool("cg_drawDisconnect", false, Game::DVAR_NONE, "Draw connection interrupted");
|
||||
|
||||
Utils::Hook(0x4A54ED, SlowMotion::DrawConnectionInterruptedStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4A54FB, SlowMotion::DrawConnectionInterruptedStub, HOOK_CALL).install()->quick();
|
||||
Delay = 0;
|
||||
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:
|
||||
static int Delay;
|
||||
|
||||
static void SetSlowMotion();
|
||||
static void ApplySlowMotion(int timePassed);
|
||||
static void ApplySlowMotionStub();
|
||||
static const Game::dvar_t* cg_drawDisconnect;
|
||||
|
||||
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 "GSC/Script.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
int64_t* Stats::GetStatsID()
|
||||
std::int64_t* Stats::GetStatsID()
|
||||
{
|
||||
static int64_t id = 0x110000100001337;
|
||||
static std::int64_t id = 0x110000100001337;
|
||||
return &id;
|
||||
}
|
||||
|
||||
@ -58,7 +59,7 @@ namespace Components
|
||||
|
||||
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)
|
||||
@ -73,18 +74,64 @@ namespace Components
|
||||
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()
|
||||
{
|
||||
// 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
|
||||
// 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.
|
||||
UIScript::Add("UpdateClasses", Stats::UpdateClasses);
|
||||
UIScript::Add("UpdateClasses", UpdateClasses);
|
||||
|
||||
// Allow playerdata to be changed while connected to a server
|
||||
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
|
||||
Utils::Hook::SetString(0x71C048, "iw4x.stat");
|
||||
@ -92,8 +139,9 @@ namespace Components
|
||||
// Patch stats steamid
|
||||
Utils::Hook::Nop(0x682EBF, 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);
|
||||
|
||||
// Never use remote stat saving
|
||||
@ -103,6 +151,34 @@ namespace Components
|
||||
Utils::Hook::Nop(0x402CE6, 2);
|
||||
|
||||
// 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:
|
||||
static void UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
|
||||
|
||||
static void SendStats();
|
||||
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;
|
||||
|
||||
const char* StructuredData::EnumTranslation[ENUM_MAX] =
|
||||
const char* StructuredData::EnumTranslation[COUNT] =
|
||||
{
|
||||
"features",
|
||||
"weapons",
|
||||
@ -23,7 +23,7 @@ namespace Components
|
||||
|
||||
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];
|
||||
|
||||
@ -192,8 +192,8 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
std::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::vector<std::vector<std::string>>> patchDefinitions;
|
||||
std::unordered_map<int, std::unordered_map<std::string, std::string>> otherPatchDefinitions;
|
||||
|
||||
// First check if all versions are present
|
||||
for (int i = 156;; ++i)
|
||||
@ -205,12 +205,14 @@ namespace Components
|
||||
std::vector<std::vector<std::string>> enumContainer;
|
||||
std::unordered_map<std::string, std::string> otherPatches;
|
||||
|
||||
std::string errors;
|
||||
nlohmann::json defData = nlohmann::json::parse(definition.getBuffer());
|
||||
|
||||
if (!errors.empty())
|
||||
nlohmann::json defData;
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -220,7 +222,7 @@ namespace Components
|
||||
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]];
|
||||
|
||||
@ -228,7 +230,7 @@ namespace Components
|
||||
|
||||
if (enumData.is_array())
|
||||
{
|
||||
for (auto rawEntry : enumData)
|
||||
for (const auto& rawEntry : enumData)
|
||||
{
|
||||
if (rawEntry.is_string())
|
||||
{
|
||||
@ -261,7 +263,7 @@ namespace Components
|
||||
if (patchDefinitions.empty()) return;
|
||||
|
||||
// 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);
|
||||
|
||||
// Prepare the buffers
|
||||
@ -271,7 +273,7 @@ namespace Components
|
||||
newData[i].version = (patchDefinitions.size() - i) + 155;
|
||||
|
||||
// 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);
|
||||
newData[i].enums = newEnums;
|
||||
}
|
||||
@ -292,14 +294,14 @@ namespace Components
|
||||
auto otherData = otherPatchDefinitions[newData[i].version];
|
||||
|
||||
// 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);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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())
|
||||
{
|
||||
|
@ -7,20 +7,21 @@ namespace Components
|
||||
public:
|
||||
enum PlayerDataType
|
||||
{
|
||||
ENUM_FEATURES,
|
||||
ENUM_WEAPONS,
|
||||
ENUM_ATTACHEMENTS,
|
||||
ENUM_CHALLENGES,
|
||||
ENUM_CAMOS,
|
||||
ENUM_PERKS,
|
||||
ENUM_KILLSTREAKS,
|
||||
ENUM_ACCOLADES,
|
||||
ENUM_CARDICONS,
|
||||
ENUM_CARDTITLES,
|
||||
ENUM_CARDNAMEPLATES,
|
||||
ENUM_TEAMS,
|
||||
ENUM_GAMETYPES,
|
||||
ENUM_MAX
|
||||
FEATURES,
|
||||
WEAPONS,
|
||||
ATTACHEMENTS,
|
||||
CHALLENGES,
|
||||
CAMOS,
|
||||
PERKS,
|
||||
KILLSTREAKS,
|
||||
ACCOLADES,
|
||||
CARDICONS,
|
||||
CARDTITLES,
|
||||
CARDNAMEPLATES,
|
||||
TEAMS,
|
||||
GAMETYPES,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
StructuredData();
|
||||
@ -34,6 +35,6 @@ namespace Components
|
||||
static void PatchCustomClassLimit(Game::StructuredDataDef* data, int count);
|
||||
static Utils::Memory::Allocator MemAllocator;
|
||||
|
||||
static const char* EnumTranslation[ENUM_MAX];
|
||||
static const char* EnumTranslation[COUNT];
|
||||
};
|
||||
}
|
||||
|
@ -1260,7 +1260,7 @@ namespace Components
|
||||
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;
|
||||
|
||||
@ -1287,12 +1287,12 @@ namespace Components
|
||||
|
||||
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));
|
||||
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;
|
||||
|
||||
@ -1339,7 +1339,7 @@ namespace Components
|
||||
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;
|
||||
|
||||
|
@ -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 int R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font);
|
||||
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 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 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 bool IsFontIcon(const char*& text, FontIconInfo& fontIcon);
|
||||
|
@ -6,7 +6,7 @@ namespace Components
|
||||
unsigned int Theatre::CurrentSelection;
|
||||
std::vector<Theatre::DemoInfo> Theatre::Demos;
|
||||
|
||||
char Theatre::BaselineSnapshot[131072] = { 0 };
|
||||
char Theatre::BaselineSnapshot[131072] = {0};
|
||||
int Theatre::BaselineSnapshotMsgLen;
|
||||
int Theatre::BaselineSnapshotMsgOff;
|
||||
|
||||
@ -18,7 +18,7 @@ namespace Components
|
||||
|
||||
void Theatre::RecordGamestateStub()
|
||||
{
|
||||
int sequence = (*Game::serverMessageSequence - 1);
|
||||
const auto sequence = (*Game::serverMessageSequence - 1);
|
||||
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_WriteByte(&buf, 6);
|
||||
|
||||
int compressedSize = Game::MSG_WriteBitsCompress(false, reinterpret_cast<char*>(buf.data), cmpData, buf.cursize);
|
||||
int fileCompressedSize = compressedSize + 4;
|
||||
const auto compressedSize = Game::MSG_WriteBitsCompress(false, reinterpret_cast<char*>(buf.data), cmpData, buf.cursize);
|
||||
const auto fileCompressedSize = compressedSize + 4;
|
||||
|
||||
int byte8 = 8;
|
||||
char byte0 = 0;
|
||||
@ -67,9 +67,9 @@ namespace Components
|
||||
Game::FS_WriteToDemo(&fileCompressedSize, 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))
|
||||
{
|
||||
@ -186,7 +186,7 @@ namespace Components
|
||||
Theatre::CurrentSelection = 0;
|
||||
Theatre::Demos.clear();
|
||||
|
||||
auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
||||
const auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
||||
|
||||
for (auto demo : demos)
|
||||
{
|
||||
@ -194,27 +194,32 @@ namespace Components
|
||||
|
||||
if (meta.exists())
|
||||
{
|
||||
std::string error;
|
||||
nlohmann::json metaObject = nlohmann::json::parse(meta.getBuffer());
|
||||
|
||||
if (metaObject.is_object())
|
||||
nlohmann::json metaObject;
|
||||
try
|
||||
{
|
||||
Theatre::DemoInfo demoInfo;
|
||||
demoInfo.name = demo.substr(0, demo.find_last_of("."));
|
||||
demoInfo.author = metaObject["author"].get<std::string>();
|
||||
demoInfo.gametype = metaObject["gametype"].get<std::string>();
|
||||
demoInfo.mapname = metaObject["mapname"].get<std::string>();
|
||||
demoInfo.length = metaObject["length"].get<int>();
|
||||
auto timestamp = metaObject["timestamp"].get<std::string>();
|
||||
demoInfo.timeStamp = _atoi64(timestamp.data());
|
||||
|
||||
Theatre::Demos.push_back(demoInfo);
|
||||
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;
|
||||
demoInfo.name = demo.substr(0, demo.find_last_of("."));
|
||||
demoInfo.author = metaObject["author"].get<std::string>();
|
||||
demoInfo.gametype = metaObject["gametype"].get<std::string>();
|
||||
demoInfo.mapname = metaObject["mapname"].get<std::string>();
|
||||
demoInfo.length = metaObject["length"].get<int>();
|
||||
auto timestamp = metaObject["timestamp"].get<std::string>();
|
||||
demoInfo.timeStamp = _atoi64(timestamp.data());
|
||||
|
||||
Theatre::Demos.push_back(demoInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -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()
|
||||
{
|
||||
if (Dedicated::IsEnabled() || Monitor::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -61,10 +61,10 @@ namespace Components
|
||||
|
||||
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);
|
||||
got->second(UIScript::Token(args), info);
|
||||
itr->second(UIScript::Token(args), info);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -242,8 +242,8 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
const auto got = VoteCommands.find(params->get(1));
|
||||
if (got == VoteCommands.end())
|
||||
const auto itr = VoteCommands.find(params->get(1));
|
||||
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(CallVoteDesc, 0x65));
|
||||
@ -256,7 +256,7 @@ namespace Components
|
||||
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)
|
||||
{
|
||||
DisplayVote(ent);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "AssetInterfaces/ILocalizeEntry.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
@ -76,7 +77,6 @@ namespace Components
|
||||
Game::DB_LoadXAssets(&info, 1, true);
|
||||
|
||||
AssetHandler::ClearTemporaryAssets();
|
||||
Localization::ClearTemp();
|
||||
}
|
||||
|
||||
Utils::Stream* ZoneBuilder::Zone::getBuffer()
|
||||
@ -147,41 +147,44 @@ namespace Components
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (this->dataMap.getColumns(i) > 2)
|
||||
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())
|
||||
{
|
||||
if (this->dataMap.getElementAt(i, 0) == "localize")
|
||||
{
|
||||
std::string stringOverride = this->dataMap.getElementAt(i, 2);
|
||||
Utils::String::Replace(stringOverride, "\\n", "\n");
|
||||
|
||||
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)
|
||||
{
|
||||
this->renameAsset(type, oldName, newName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Error(Game::ERR_FATAL, "Unable to rename '{}' to '{}' as the asset type '{}' is invalid!",
|
||||
oldName, newName, typeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->loadAssetByName(this->dataMap.getElementAt(i, 0), this->dataMap.getElementAt(i, 1), false))
|
||||
{
|
||||
return false;
|
||||
Assets::ILocalizeEntry::ParseLocalizedStringsFile(this, filename, file.getName());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->dataMap.getColumns(i) > 2)
|
||||
{
|
||||
auto oldName = this->dataMap.getElementAt(i, 1);
|
||||
auto newName = this->dataMap.getElementAt(i, 2);
|
||||
auto typeName = this->dataMap.getElementAt(i, 0);
|
||||
auto type = Game::DB_GetXAssetNameType(typeName.data());
|
||||
|
||||
if (type < Game::XAssetType::ASSET_TYPE_COUNT && type >= 0)
|
||||
{
|
||||
this->renameAsset(type, oldName, newName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Error(Game::ERR_FATAL, "Unable to rename '{}' to '{}' as the asset type '{}' is invalid!", oldName, newName, typeName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->loadAssetByName(this->dataMap.getElementAt(i, 0), this->dataMap.getElementAt(i, 1), false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -206,10 +209,9 @@ namespace Components
|
||||
{
|
||||
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,
|
||||
"Asset with name '{}' contains spaces. Check your zone source file to ensure this is correct!\n", name);
|
||||
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Asset with name '{}' contains spaces. Check your zone source file to ensure this is correct!\n", name);
|
||||
}
|
||||
|
||||
// Sanitize name for empty assets
|
||||
@ -226,7 +228,7 @@ namespace Components
|
||||
Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this, isSubAsset);
|
||||
|
||||
if (!assetHeader.data)
|
||||
{
|
||||
{
|
||||
Logger::Error(Game::ERR_FATAL, "Missing asset '{}' of type '{}'\n", name, Game::DB_GetXAssetTypeName(type));
|
||||
return false;
|
||||
}
|
||||
@ -420,8 +422,7 @@ namespace Components
|
||||
Utils::IO::WriteFile(outFile, outBuffer);
|
||||
|
||||
Logger::Print("done.\n");
|
||||
Logger::Print("Zone '{}' written with {} assets and {} script strings\n",
|
||||
outFile, (this->aliasList.size() + this->loadedAssets.size()), this->scriptStrings.size());
|
||||
Logger::Print("Zone '{}' written with {} assets and {} script strings\n", outFile, (this->aliasList.size() + this->loadedAssets.size()), this->scriptStrings.size());
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// 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);
|
||||
}
|
||||
@ -618,6 +619,11 @@ namespace Components
|
||||
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.
|
||||
void ZoneBuilder::Zone::mapScriptString(unsigned short* gameIndex)
|
||||
{
|
||||
@ -914,17 +920,17 @@ namespace Components
|
||||
frames++;
|
||||
}
|
||||
|
||||
// ReSharper disable once CppUnreachableCode
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ZoneBuilder::HandleError(Game::errorParm_t code, const char* fmt, ...)
|
||||
{
|
||||
char buffer[4096] = {0};
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
_vsnprintf_s(buffer, _TRUNCATE, fmt, args);
|
||||
va_end(args);
|
||||
char buffer[0x1000]{};
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf_s(buffer, _TRUNCATE, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
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