Merge pull request #578 from XLabsProject/develop

Release v0.7.6
This commit is contained in:
Edo 2022-11-22 15:17:06 +00:00 committed by GitHub
commit 26069a993d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 41966 additions and 2781 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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

@ -1 +1 @@
Subproject commit 10df83d292bf5bbdc487e57dc8c2dc8c7a01f4d1
Subproject commit 517ed29228d18cf2c5004d10826090108e06f049

View 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

File diff suppressed because it is too large Load Diff

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit ddfe2e8aa7c4239463a8a1d26724aef123333549
Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 4b47368501321c795d5b54d87a5bab35a21a7940
Subproject commit 03de03dee753442d4b23166982514639c4ccbc39

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit cb602f178ccea7f0c790cf5510f7a29c017db954
Subproject commit db81c30d24df98031e41e33423a5eef0e89ba8c6

2
deps/nlohmannjson vendored

@ -1 +1 @@
Subproject commit 307c053b9b250abc6e6d478a4336fd98592ae173
Subproject commit a3e6e26dc83a726b292f5be0492fcc408663ce55

2
deps/pdcurses vendored

@ -1 +1 @@
Subproject commit 2fa0f10dd844da47ee83c05a40a1ec541ceb95e1
Subproject commit f2d31a2633eb042f7bf1f79cba81522915a04579

27
deps/premake/fonts.lua vendored Normal file
View 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)

View File

@ -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

@ -1 +1 @@
Subproject commit 50bdb17409d4133d51ab6cfa095700f4c816576e
Subproject commit 57786d126249b5ed4f42b579047941805e742949

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 5752b171fd4cc96b8d1f9526ec1940199c6e9740
Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc

View File

@ -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());

View File

@ -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"

View File

@ -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);

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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);
};
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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);
};
}

View File

@ -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);
};
}

View File

@ -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)

View File

@ -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());

View File

@ -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;

View File

@ -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
}

View File

@ -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();
}
}

View File

@ -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);
};
}

View File

@ -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);

View File

@ -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));

View File

@ -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);
});
}
}

View File

@ -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, &params);
itr->second(ent, &params);
return;
}

View File

@ -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(&params);
itr->second(&params);
}
}
@ -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(&params);
itr->second(&params);
}
}
}

View File

@ -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)
{

View File

@ -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();

View File

@ -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);
};
}

View File

@ -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);

View File

@ -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();

View File

@ -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();
}

View File

@ -12,6 +12,7 @@ namespace Components
static Dvar::Var COMLogFilter;
static bool IsEnabled();
static bool IsRunning();
static void Heartbeat();

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
};
}

View File

@ -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();
}
}
}

View File

@ -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();
};
}

View File

@ -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();

View File

@ -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()

View File

@ -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;
}

View File

@ -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>)

View File

@ -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);

View File

@ -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()

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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))

View File

@ -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()

View File

@ -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();
}
}

View File

@ -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);
};
}

View File

@ -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");
}
}

View File

@ -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);
};
}

View File

@ -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

View File

@ -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();
};
}

View File

@ -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([]
{

View File

@ -31,7 +31,6 @@ namespace Components
void sendRequest();
void reset();
nlohmann::json to_json() const;
};
Node();

View File

@ -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"));

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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();
};
}

View File

@ -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)
{

View File

@ -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);
};
}

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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);
};
}

View File

@ -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);
});
}
}

View File

@ -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();
};
}

View File

@ -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())
{

View File

@ -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];
};
}

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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();
}
}

View File

@ -1,14 +0,0 @@
#pragma once
namespace Components
{
class Threading : public Component
{
public:
Threading();
private:
static void FrameEpilogueStub();
static void PacketEventStub();
};
}

View File

@ -144,7 +144,7 @@ namespace Components
Toast::Toast()
{
if (Dedicated::IsEnabled() || Monitor::IsEnabled() || ZoneBuilder::IsEnabled())
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled())
{
return;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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