Merge branch 'diamante_develop' into develop

# Conflicts:
#	.gitmodules
This commit is contained in:
Louvenarde 2023-06-22 19:23:13 +02:00
commit 4b1aade899
33 changed files with 472 additions and 650 deletions

View File

@ -8,8 +8,7 @@ assignees: ''
--- ---
_Do not open an issue here if you need help with modding or have a problem getting the client to run. _Do not open an issue here if you need help with modding or have a problem getting the client to run.
It is very likely your problem will be resolved by reading the [FAQ](https://xlabs.dev/iw4x_faq) carefully. Ask in `iw4x-support` or `iw4x-modding` channels on the [Discord](https://discord.gg/tKTQJxcCkk) server if you still have problems.
Ask in `iw4x-support` or `iw4x-modding` channels on the [Discord](https://discord.gg/sKeVmR3) server if you still have problems.
If this does not apply, please continue by filling in the template below._ If this does not apply, please continue by filling in the template below._
**What are you trying to do?** **What are you trying to do?**

View File

@ -7,7 +7,7 @@ assignees: ''
--- ---
_Before opening a new feature request, please see [Issues](https://github.com/XLabsProject/iw4x-client/issues) and check that a similar issue does not already exist. _Before opening a new feature request, please see [Issues](https://github.com/iw4x/iw4x-client/issues) and check that a similar issue does not already exist.
If this a new request, help us help you by filling in the template below._ If this a new request, help us help you by filling in the template below._
**What problem will this solve?** **What problem will this solve?**

View File

@ -24,7 +24,7 @@ jobs:
- Release - Release
steps: steps:
- name: Check out files - name: Check out files
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.3
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
@ -55,15 +55,11 @@ jobs:
name: Deploy artifacts name: Deploy artifacts
needs: build needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop') if: github.event_name == 'push' && (github.ref == 'refs/heads/develop')
steps: steps:
- name: Setup main environment
if: github.ref == 'refs/heads/master'
run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH }}" >> $GITHUB_ENV
- name: Setup develop environment - name: Setup develop environment
if: github.ref == 'refs/heads/develop' if: github.ref == 'refs/heads/develop'
run: echo "XLABS_MASTER_PATH=${{ secrets.XLABS_MASTER_SSH_PATH_DEV }}" >> $GITHUB_ENV run: echo "DIAMANTE_MASTER_PATH=${{ secrets.DIAMANTE_MASTER_SSH_PATH }}" >> $GITHUB_ENV
- name: Download Release binaries - name: Download Release binaries
uses: actions/download-artifact@v3.0.2 uses: actions/download-artifact@v3.0.2
@ -74,20 +70,11 @@ jobs:
- name: Install SSH key - name: Install SSH key
uses: shimataro/ssh-key-action@v2.5.1 uses: shimataro/ssh-key-action@v2.5.1
with: with:
key: ${{ secrets.XLABS_MASTER_SSH_PRIVATE_KEY }} key: ${{ secrets.DIAMANTE_MASTER_SSH_PRIVATE_KEY }}
known_hosts: 'just-a-placeholder-so-we-dont-get-errors' known_hosts: 'just-a-placeholder-so-we-dont-get-errors'
- name: Add known hosts - name: Add known hosts
run: ssh-keyscan -H ${{ secrets.XLABS_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts run: ssh-keyscan -H ${{ secrets.DIAMANTE_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts
# - name: Remove old data files
# run: ssh ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }} rm -rf ${{ env.XLABS_MASTER_PATH }}/iw4x/data/*
- name: Upload iw4x binary - name: Upload iw4x-client binary
run: rsync -avz iw4x.dll ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }}:${{ env.XLABS_MASTER_PATH }}/iw4x/ run: rsync -avz iw4x.dll ${{ secrets.DIAMANTE_MASTER_SSH_USER }}@${{ secrets.DIAMANTE_MASTER_SSH_ADDRESS }}:${{ env.DIAMANTE_MASTER_PATH }}/legacy/
# - name: Upload data files
# run: rsync -avz ./data/ ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }}:${{ env.XLABS_MASTER_PATH }}/iw4x/data/
- name: Publish changes
run: ssh ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }} ${{ secrets.XLABS_MASTER_SSH_CHANGE_PUBLISH_COMMAND }}

View File

@ -1,17 +0,0 @@
name: Notify Discord
on:
push:
branches:
- "*"
issues:
jobs:
notify:
runs-on: ubuntu-latest
if: github.repository_owner == 'XLabsProject'
steps:
- name: Send notification to Discord
uses: Ilshidur/action-discord@master
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_CI_BOT_WEBHOOK }}

View File

@ -1,48 +0,0 @@
name: "Draft new release"
on:
workflow_dispatch:
inputs:
version:
description: "The version you want to release."
required: true
jobs:
draft-new-release:
name: "Draft a new release"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.2
- name: Normalize version
id: normalize_version
run: |
version="${{ github.event.inputs.version }}"
version="r${version#v}"
echo "::set-output name=version::$version"
# Set up committer info and GPG key
- name: Import GPG key
id: import_gpg
uses: XLabsProject/ghaction-import-gpg@25d9d6ab99eb355c169c33c2306a72df85d9f516
with:
git-commit-gpgsign: true
git-committer-email: "${{ secrets.XLABS_CI_EMAIL }}"
git-committer-name: "${{ secrets.XLABS_CI_NAME }}"
# git-push-gpgsign: true
git-tag-gpgsign: true
git-user-signingkey: true
gpg-private-key: ${{ secrets.XLABS_CI_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.XLABS_CI_GPG_PASSWORD }}
- name: Create Pull Request
uses: repo-sync/pull-request@v2
with:
github_token: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
source_branch: "develop"
destination_branch: "master"
pr_allow_empty: true
pr_body: |
This Pull Request is for the release of IW4x ${{ steps.normalize_version.outputs.version }} and was [automatically created by a workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) triggered by @${{ github.actor }}.
pr_title: Release ${{ steps.normalize_version.outputs.version }}
pr_label: release

View File

@ -1,83 +0,0 @@
name: Release
on:
pull_request:
branches:
- "master"
types: [closed]
jobs:
merge:
runs-on: ubuntu-latest
name: Merge Release
steps:
- name: Check out files
if: github.event.pull_request.merged
uses: actions/checkout@v3.5.2
with:
submodules: false
lfs: false
# Set up committer info and GPG key
- name: Import GPG key
if: github.event.pull_request.merged
id: import_gpg
uses: XLabsProject/ghaction-import-gpg@25d9d6ab99eb355c169c33c2306a72df85d9f516
with:
git-commit-gpgsign: true
git-committer-email: "${{ secrets.XLABS_CI_EMAIL }}"
git-committer-name: "${{ secrets.XLABS_CI_NAME }}"
git-push-gpgsign: false
git-tag-gpgsign: true
git-user-signingkey: true
gpg-private-key: ${{ secrets.XLABS_CI_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.XLABS_CI_GPG_PASSWORD }}
- name: Extract version from pull request
if: github.event.pull_request.merged
id: extract_version
run: |
title="${{ github.event.pull_request.title }}"
version="${title#Release }"
echo "::set-output name=version::$version"
- name: Create annotated tag
if: github.event.pull_request.merged
run: |
git tag -a -m "${{ github.event.pull_request.title }}" \
"${{ steps.extract_version.outputs.version }}" \
"${{ github.event.pull_request.merge_commit_sha }}"
git push origin --tags
- name: Create Pull Request
if: github.event.pull_request.merged
uses: repo-sync/pull-request@v2
with:
github_token: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
source_branch: "master"
destination_branch: "develop"
pr_allow_empty: true
pr_body: |
This Pull Request merges the release of IW4x ${{ steps.extract_version.outputs.version }} and was [automatically created by a workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) triggered by @${{ github.actor }}.
pr_title: Merge release ${{ steps.extract_version.outputs.version }}
pr_label: release
notify:
name: Notify Discord
runs-on: ubuntu-latest
if: |
github.repository_owner == 'XLabsProject' && (
(
github.event.pull_request.merged
) || (
github.event.push.ref == 'refs/heads/master' ||
github.event.push.ref == 'refs/heads/develop'
)
)
steps:
- name: Post CI status notification to Discord
uses: sarisia/actions-status-discord@v1.7.1
if: always()
with:
webhook: ${{ secrets.DISCORD_CI_BOT_WEBHOOK }}
title: "Build"

2
.gitmodules vendored
View File

@ -17,7 +17,7 @@
[submodule "deps/mongoose"] [submodule "deps/mongoose"]
path = deps/mongoose path = deps/mongoose
url = https://github.com/cesanta/mongoose.git url = https://github.com/cesanta/mongoose.git
branch = 7.9 branch = 7.10
[submodule "deps/protobuf"] [submodule "deps/protobuf"]
path = deps/protobuf path = deps/protobuf
url = https://github.com/google/protobuf.git url = https://github.com/google/protobuf.git

View File

@ -1,10 +1,5 @@
![license](https://img.shields.io/github/license/IW4x/iw4x-client.svg) ![license](https://img.shields.io/github/license/iw4x/iw4x-client.svg)
![forks](https://img.shields.io/github/forks/IW4x/iw4x-client.svg) [![build](https://github.com/iw4x/iw4x-client/workflows/Build/badge.svg)](https://github.com/iw4x/iw4x-client/actions)
![stars](https://img.shields.io/github/stars/IW4x/iw4x-client.svg)
![issues](https://img.shields.io/github/issues/IW4x/iw4x-client.svg)
[![build](https://github.com/XLabsProject/iw4x-client/workflows/Build/badge.svg)](https://github.com/XLabsProject/iw4x-client/actions)
[![discord](https://img.shields.io/endpoint?url=https://momo5502.com/iw4x/members-badge.php)](https://discord.gg/sKeVmR3)
[![patreon](https://img.shields.io/badge/patreon-support-blue.svg?logo=patreon)](https://www.patreon.com/xlabsproject)
# IW4x: Client # IW4x: Client
@ -37,10 +32,9 @@
| `-dump` | Write info of loaded assets to the raw folder as they are being loaded. | | `-dump` | Write info of loaded assets to the raw folder as they are being loaded. |
| `-nointro` | Skip game's cinematic intro. | | `-nointro` | Skip game's cinematic intro. |
| `-version` | Print IW4x build info on startup. | | `-version` | Print IW4x build info on startup. |
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
| `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. | | `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. |
| `-unprotect-dvars` | Allow the server to modify saved/archive dvars. | | `-unprotect-dvars` | Allow the server to modify saved/archive dvars. |
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
## Disclaimer ## Disclaimer

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit fae62af0ab16f469c2512ec04575dd60ca018657 Subproject commit 93f5348c47d3578091a4ee5b90f4add216b46d1b

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 0df542cb70f621bbeec207be1949832fb1442479 Subproject commit c6a00c26ca2192c713a36227fdd84d126cdc95b9

2
deps/mongoose vendored

@ -1 +1 @@
Subproject commit 4236405b90e051310aadda818e21c811e404b4d8 Subproject commit b379816178abdcd59135aa32f990a4b3bbbfb54b

View File

@ -273,8 +273,7 @@ workspace "iw4x"
dependencies.imports() dependencies.imports()
-- Pre-build -- Pre-build
prebuildcommands prebuildcommands {
{
"pushd %{_MAIN_SCRIPT_DIR}", "pushd %{_MAIN_SCRIPT_DIR}",
"tools\\premake5 generate-buildinfo", "tools\\premake5 generate-buildinfo",
"popd", "popd",

View File

@ -371,7 +371,7 @@ namespace Components
MutedList.access([&](muteList& clients) MutedList.access([&](muteList& clients)
{ {
const nlohmann::json::array_t arr = list; const nlohmann::json::array_t arr = list;
for (auto& entry : arr) for (const auto& entry : arr)
{ {
if (entry.is_number_unsigned()) if (entry.is_number_unsigned())
{ {

View File

@ -243,7 +243,6 @@ namespace Components
// Intercept time wrapping // Intercept time wrapping
Utils::Hook(0x62737D, TimeWrapStub, HOOK_CALL).install()->quick(); Utils::Hook(0x62737D, TimeWrapStub, HOOK_CALL).install()->quick();
//Utils::Hook::Set<DWORD>(0x62735C, 50'000); // Time wrap after 50 seconds (for testing - i don't want to wait 3 weeks)
if (!ZoneBuilder::IsEnabled()) if (!ZoneBuilder::IsEnabled())
{ {

View File

@ -5,6 +5,7 @@
#include "Download.hpp" #include "Download.hpp"
#include "Events.hpp" #include "Events.hpp"
#include "MapRotation.hpp" #include "MapRotation.hpp"
#include "Node.hpp"
#include "Party.hpp" #include "Party.hpp"
#include "ServerInfo.hpp" #include "ServerInfo.hpp"
@ -40,7 +41,7 @@ namespace Components
void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map) void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map)
{ {
if (CLDownload.running) return; if (CLDownload.running_) return;
Scheduler::Once([] Scheduler::Once([]
{ {
@ -61,26 +62,26 @@ namespace Components
return; return;
} }
CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), ""); CLDownload.hashedPassword_ = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), "");
} }
CLDownload.running = true; CLDownload.running_ = true;
CLDownload.isMap = map; CLDownload.isMap_ = map;
CLDownload.mod = mod; CLDownload.mod_ = mod;
CLDownload.terminateThread = false; CLDownload.terminateThread_ = false;
CLDownload.totalBytes = 0; CLDownload.totalBytes_ = 0;
CLDownload.lastTimeStamp = 0; CLDownload.lastTimeStamp_ = 0;
CLDownload.downBytes = 0; CLDownload.downBytes_ = 0;
CLDownload.timeStampBytes = 0; CLDownload.timeStampBytes_ = 0;
CLDownload.isPrivate = needPassword; CLDownload.isPrivate_ = needPassword;
CLDownload.target = Party::Target(); CLDownload.target_ = Party::Target();
CLDownload.thread = std::thread(ModDownloader, &CLDownload); CLDownload.thread_ = std::thread(ModDownloader, &CLDownload);
} }
bool Download::ParseModList(ClientDownload* download, const std::string& list) bool Download::ParseModList(ClientDownload* download, const std::string& list)
{ {
if (!download) return false; if (!download) return false;
download->files.clear(); download->files_.clear();
nlohmann::json listData; nlohmann::json listData;
try try
@ -98,7 +99,7 @@ namespace Components
return false; return false;
} }
download->totalBytes = 0; download->totalBytes_ = 0;
const nlohmann::json::array_t listDataArray = listData; const nlohmann::json::array_t listDataArray = listData;
for (auto& file : listDataArray) for (auto& file : listDataArray)
@ -118,8 +119,8 @@ namespace Components
if (!fileEntry.name.empty()) if (!fileEntry.name.empty())
{ {
download->files.push_back(fileEntry); download->files_.push_back(fileEntry);
download->totalBytes += fileEntry.size; download->totalBytes_ += fileEntry.size;
} }
} }
catch (const nlohmann::json::exception& ex) catch (const nlohmann::json::exception& ex)
@ -134,12 +135,12 @@ namespace Components
bool Download::DownloadFile(ClientDownload* download, unsigned int index) bool Download::DownloadFile(ClientDownload* download, unsigned int index)
{ {
if (!download || download->files.size() <= index) return false; if (!download || download->files_.size() <= index) return false;
auto file = download->files[index]; auto file = download->files_[index];
auto path = download->mod + "/" + file.name; auto path = download->mod_ + "/" + file.name;
if (download->isMap) if (download->isMap_)
{ {
path = "usermaps/" + path; path = "usermaps/" + path;
} }
@ -149,16 +150,16 @@ namespace Components
auto data = Utils::IO::ReadFile(path); auto data = Utils::IO::ReadFile(path);
if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash) if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash)
{ {
download->totalBytes += file.size; download->totalBytes_ += file.size;
return true; return true;
} }
} }
auto host = "http://" + download->target.getString(); auto host = "http://" + download->target_.getString();
auto fastHost = SV_wwwBaseUrl.get<std::string>(); auto fastHost = SV_wwwBaseUrl.get<std::string>();
if (Utils::String::StartsWith(fastHost, "https://")) if (Utils::String::StartsWith(fastHost, "https://"))
{ {
download->thread.detach(); download->thread_.detach();
download->clear(); download->clear();
Scheduler::Once([] Scheduler::Once([]
@ -197,8 +198,8 @@ namespace Components
} }
else else
{ {
url = host + "/file/" + (download->isMap ? "map/" : "") + file.name url = host + "/file/" + (download->isMap_ ? "map/" : "") + file.name
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : ""); + (download->isPrivate_ ? ("?password=" + download->hashedPassword_) : "");
} }
Logger::Print("Downloading from url {}\n", url); Logger::Print("Downloading from url {}\n", url);
@ -212,14 +213,14 @@ namespace Components
Utils::String::Replace(url, " ", "%20"); Utils::String::Replace(url, " ", "%20");
download->valid = true; download->valid_ = true;
fDownload.downloading = true; fDownload.downloading = true;
Utils::WebIO webIO; Utils::WebIO webIO;
webIO.setProgressCallback([&fDownload, &webIO](std::size_t bytes, std::size_t) webIO.setProgressCallback([&fDownload, &webIO](std::size_t bytes, std::size_t)
{ {
if(!fDownload.downloading || fDownload.download->terminateThread) if(!fDownload.downloading || fDownload.download->terminateThread_)
{ {
webIO.cancelDownload(); webIO.cancelDownload();
return; return;
@ -234,14 +235,14 @@ namespace Components
fDownload.downloading = false; fDownload.downloading = false;
download->valid = false; download->valid_ = false;
if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash) if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash)
{ {
return false; return false;
} }
if (download->isMap) Utils::IO::CreateDir("usermaps/" + download->mod); if (download->isMap_) Utils::IO::CreateDir("usermaps/" + download->mod_);
Utils::IO::WriteFile(path, fDownload.buffer); Utils::IO::WriteFile(path, fDownload.buffer);
return true; return true;
@ -251,16 +252,16 @@ namespace Components
{ {
if (!download) download = &CLDownload; if (!download) download = &CLDownload;
const auto host = "http://" + download->target.getString(); const auto host = "http://" + download->target_.getString();
const auto listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : ""); const auto listUrl = host + (download->isMap_ ? "/map" : "/list") + (download->isPrivate_ ? ("?password=" + download->hashedPassword_) : "");
const auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get(); const auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
if (list.empty()) if (list.empty())
{ {
if (download->terminateThread) return; if (download->terminateThread_) return;
download->thread.detach(); download->thread_.detach();
download->clear(); download->clear();
Scheduler::Once([] Scheduler::Once([]
@ -272,13 +273,13 @@ namespace Components
return; return;
} }
if (download->terminateThread) return; if (download->terminateThread_) return;
if (!ParseModList(download, list)) if (!ParseModList(download, list))
{ {
if (download->terminateThread) return; if (download->terminateThread_) return;
download->thread.detach(); download->thread_.detach();
download->clear(); download->clear();
Scheduler::Once([] Scheduler::Once([]
@ -290,21 +291,21 @@ namespace Components
return; return;
} }
if (download->terminateThread) return; if (download->terminateThread_) return;
static std::string mod; static std::string mod;
mod = download->mod; mod = download->mod_;
for (std::size_t i = 0; i < download->files.size(); ++i) for (std::size_t i = 0; i < download->files_.size(); ++i)
{ {
if (download->terminateThread) return; if (download->terminateThread_) return;
if (!DownloadFile(download, i)) if (!DownloadFile(download, i))
{ {
if (download->terminateThread) return; if (download->terminateThread_) return;
mod = std::format("Failed to download file: {}!", download->files[i].name); mod = std::format("Failed to download file: {}!", download->files_[i].name);
download->thread.detach(); download->thread_.detach();
download->clear(); download->clear();
Scheduler::Once([] Scheduler::Once([]
@ -320,12 +321,12 @@ namespace Components
} }
} }
if (download->terminateThread) return; if (download->terminateThread_) return;
download->thread.detach(); download->thread_.detach();
download->clear(); download->clear();
if (download->isMap) if (download->isMap_)
{ {
Scheduler::Once([] Scheduler::Once([]
{ {
@ -357,22 +358,22 @@ namespace Components
void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes) void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes)
{ {
fDownload->receivedBytes += bytes; fDownload->receivedBytes += bytes;
fDownload->download->downBytes += bytes; fDownload->download->downBytes_ += bytes;
fDownload->download->timeStampBytes += bytes; fDownload->download->timeStampBytes_ += bytes;
static volatile bool framePushed = false; static volatile bool framePushed = false;
if (!framePushed) if (!framePushed)
{ {
double progress = 0; double progress = 0;
if (fDownload->download->totalBytes) if (fDownload->download->totalBytes_)
{ {
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes; progress = (100.0 / fDownload->download->totalBytes_) * fDownload->download->downBytes_;
} }
static std::uint32_t dlIndex, dlSize, dlProgress; static std::uint32_t dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1; dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size(); dlSize = fDownload->download->files_.size();
dlProgress = static_cast<std::uint32_t>(progress); dlProgress = static_cast<std::uint32_t>(progress);
framePushed = true; framePushed = true;
@ -383,18 +384,18 @@ namespace Components
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
} }
auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp; auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp_;
if (delta > 300) if (delta > 300)
{ {
const auto doFormat = fDownload->download->lastTimeStamp != 0; const auto doFormat = fDownload->download->lastTimeStamp_ != 0;
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds(); fDownload->download->lastTimeStamp_ = Game::Sys_Milliseconds();
const auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes; const auto dataLeft = fDownload->download->totalBytes_ - fDownload->download->downBytes_;
int timeLeft = 0; int timeLeft = 0;
if (fDownload->download->timeStampBytes) if (fDownload->download->timeStampBytes_)
{ {
const double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta; const double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes_) * delta;
timeLeft = static_cast<int>(timeLeftD); timeLeft = static_cast<int>(timeLeftD);
} }
@ -404,7 +405,7 @@ namespace Components
static int dlDelta, dlTimeLeft; static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft; dlTimeLeft = timeLeft;
dlDelta = delta; dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes; dlTsBytes = fDownload->download->timeStampBytes_;
Scheduler::Once([] Scheduler::Once([]
{ {
@ -413,7 +414,7 @@ namespace Components
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
} }
fDownload->download->timeStampBytes = 0; fDownload->download->timeStampBytes_ = 0;
} }
} }
@ -434,7 +435,7 @@ namespace Components
MongooseLogBuffer.push_back(c); MongooseLogBuffer.push_back(c);
} }
static std::string InfoHandler() static std::optional<std::string> InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{ {
const auto status = ServerInfo::GetInfo(); const auto status = ServerInfo::GetInfo();
const auto host = ServerInfo::GetHostInfo(); const auto host = ServerInfo::GetHostInfo();
@ -480,10 +481,12 @@ namespace Components
} }
info["players"] = players; info["players"] = players;
return nlohmann::json(info).dump(); std::string out = nlohmann::json(info).dump();
return { out };
} }
static std::string ListHandler() static std::optional<std::string> ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{ {
static nlohmann::json jsonList; static nlohmann::json jsonList;
static std::filesystem::path fsGamePre; static std::filesystem::path fsGamePre;
@ -526,10 +529,12 @@ namespace Components
jsonList = fileList; jsonList = fileList;
} }
return jsonList.dump(); std::string out = jsonList.dump();
return { out };
} }
static std::string MapHandler() static std::optional<std::string> MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{ {
static std::string mapNamePre; static std::string mapNamePre;
static nlohmann::json jsonList; static nlohmann::json jsonList;
@ -570,10 +575,12 @@ namespace Components
jsonList = fileList; jsonList = fileList;
} }
return jsonList.dump(); std::string out = jsonList.dump();
return { out };
} }
static void FileHandler(mg_connection* c, const mg_http_message* hm) static std::optional<std::string> FileHandler(mg_connection* c, const mg_http_message* hm)
{ {
std::string url(hm->uri.ptr, hm->uri.len); std::string url(hm->uri.ptr, hm->uri.len);
@ -602,7 +609,7 @@ namespace Components
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile) if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
{ {
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden"); mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
return; return {};
} }
url = std::format("usermaps\\{}\\{}", mapName, url); url = std::format("usermaps\\{}\\{}", mapName, url);
@ -612,7 +619,7 @@ namespace Components
if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos) 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"); mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
return; return {};
} }
} }
@ -633,10 +640,44 @@ namespace Components
mg_printf(c, "%s", "\r\n"); mg_printf(c, "%s", "\r\n");
mg_send(c, file.data(), file.size()); mg_send(c, file.data(), file.size());
} }
return {};
}
static std::optional<std::string> ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{
std::vector<std::string> servers;
const auto nodes = Node::GetNodes();
for (const auto& node : nodes)
{
const auto address = node.address.getString();
servers.emplace_back(address);
}
nlohmann::json jsonList = servers;
std::string out = jsonList.dump();
return { out };
} }
static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data) static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data)
{ {
using callback = std::function<std::optional<std::string>(mg_connection*, const mg_http_message*)>;
static const auto handlers = []() -> std::unordered_map<std::string, callback>
{
std::unordered_map<std::string, callback> f;
f["/file"] = FileHandler;
f["/info"] = InfoHandler;
f["/list"] = ListHandler;
f["/map"] = MapHandler;
f["/serverlist"] = ServerListHandler;
return f;
}();
if (ev != MG_EV_HTTP_MSG) if (ev != MG_EV_HTTP_MSG)
{ {
return; return;
@ -645,26 +686,24 @@ namespace Components
auto* hm = static_cast<mg_http_message*>(ev_data); auto* hm = static_cast<mg_http_message*>(ev_data);
const std::string url(hm->uri.ptr, hm->uri.len); const std::string url(hm->uri.ptr, hm->uri.len);
if (url.starts_with("/info")) auto handled = false;
for (auto i = handlers.begin(); i != handlers.end();)
{ {
const auto reply = InfoHandler(); if (url.starts_with(i->first))
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data()); {
if (const auto reply = i->second(c, hm))
{
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.value().data());
}
handled = true;
break;
}
++i;
} }
else if (url.starts_with("/list"))
{ if (!handled)
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
{ {
mg_http_serve_opts opts = { .root_dir = "iw4x/html" }; // Serve local dir mg_http_serve_opts opts = { .root_dir = "iw4x/html" }; // Serve local dir
mg_http_serve_dir(c, hm, &opts); mg_http_serve_dir(c, hm, &opts);

View File

@ -24,24 +24,24 @@ namespace Components
class ClientDownload class ClientDownload
{ {
public: public:
ClientDownload(bool _isMap = false) : running(false), valid(false), terminateThread(false), isMap(_isMap), totalBytes(0), downBytes(0), lastTimeStamp(0), timeStampBytes(0) {} ClientDownload(bool isMap = false) : running_(false), valid_(false), terminateThread_(false), isMap_(isMap), totalBytes_(0), downBytes_(0), lastTimeStamp_(0), timeStampBytes_(0) {}
~ClientDownload() { this->clear(); } ~ClientDownload() { this->clear(); }
bool running; bool running_;
bool valid; bool valid_;
bool terminateThread; bool terminateThread_;
bool isMap; bool isMap_;
bool isPrivate; bool isPrivate_;
Network::Address target; Network::Address target_;
std::string hashedPassword; std::string hashedPassword_;
std::string mod; std::string mod_;
std::thread thread; std::thread thread_;
std::size_t totalBytes; std::size_t totalBytes_;
std::size_t downBytes; std::size_t downBytes_;
int lastTimeStamp; int lastTimeStamp_;
std::size_t timeStampBytes; std::size_t timeStampBytes_;
class File class File
{ {
@ -51,24 +51,24 @@ namespace Components
std::size_t size; std::size_t size;
}; };
std::vector<File> files; std::vector<File> files_;
void clear() void clear()
{ {
this->terminateThread = true; this->terminateThread_ = true;
if (this->thread.joinable()) if (this->thread_.joinable())
{ {
this->thread.join(); this->thread_.join();
} }
this->running = false; this->running_ = false;
this->mod.clear(); this->mod_.clear();
this->files.clear(); this->files_.clear();
if (this->valid) if (this->valid_)
{ {
this->valid = false; this->valid_ = false;
} }
} }
}; };

View File

@ -767,16 +767,7 @@ namespace Components
UIScript::Add("downloadDLC", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("downloadDLC", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
int dlc = token.get<int>(); const auto dlc = token.get<int>();
for (const auto& pack : Maps::DlcPacks)
{
if (pack.index == dlc)
{
ShellExecuteW(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW);
return;
}
}
Game::ShowMessageBox(Utils::String::VA("DLC %d does not exist!", dlc), "ERROR"); Game::ShowMessageBox(Utils::String::VA("DLC %d does not exist!", dlc), "ERROR");
}); });

View File

@ -14,7 +14,6 @@ namespace Components
static std::thread Thread; static std::thread Thread;
static bool Terminate; static bool Terminate;
static bool DownloadUpdater();
static const char* GetNewsText(); static const char* GetNewsText();
}; };

View File

@ -15,6 +15,8 @@ namespace Components
bool Node::WasIngame = false; bool Node::WasIngame = false;
const Game::dvar_t* Node::net_natFix;
bool Node::Entry::isValid() const bool Node::Entry::isValid() const
{ {
return (this->lastResponse.has_value() && !this->lastResponse->elapsed(NODE_HALFLIFE * 2)); return (this->lastResponse.has_value() && !this->lastResponse->elapsed(NODE_HALFLIFE * 2));
@ -48,7 +50,7 @@ namespace Components
this->lastRequest->update(); this->lastRequest->update();
Session::Send(this->address, "nodeListRequest"); Session::Send(this->address, "nodeListRequest");
Node::SendList(this->address); SendList(this->address);
#ifdef NODE_SYSTEM_DEBUG #ifdef NODE_SYSTEM_DEBUG
Logger::Debug("Sent request to {}", this->address.getString()); Logger::Debug("Sent request to {}", this->address.getString());
#endif #endif
@ -56,7 +58,6 @@ namespace Components
void Node::Entry::reset() void Node::Entry::reset()
{ {
// this->lastResponse.reset(); // This would invalidate the node, but maybe we don't want that?
this->lastRequest.reset(); this->lastRequest.reset();
} }
@ -69,28 +70,54 @@ namespace Components
for (auto i = 0; i < list.nodes_size(); ++i) for (auto i = 0; i < list.nodes_size(); ++i)
{ {
const std::string& addr = list.nodes(i); const auto& addr = list.nodes(i);
if (addr.size() == sizeof(sockaddr)) if (addr.size() == sizeof(sockaddr))
{ {
Node::Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data()))); Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
} }
} }
} }
void Node::LoadNodes() void Node::LoadNodes()
{ {
Proto::Node::List list; std::string data;
std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); if (!Utils::IO::ReadFile("players/nodes.json", &data) || data.empty())
if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return;
for (int i = 0; i < list.nodes_size(); ++i)
{ {
const std::string& addr = list.nodes(i); return;
}
if (addr.size() == sizeof(sockaddr)) nlohmann::json nodes;
try
{
nodes = nlohmann::json::parse(data);
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "JSON Parse Error: {}\n", ex.what());
return;
}
if (!nodes.contains("nodes"))
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "nodes.json contains invalid data\n");
return;
}
const auto& list = nodes["nodes"];
if (!list.is_array())
{
return;
}
const nlohmann::json::array_t arr = list;
Logger::Print("Parsing {} nodes from nodes.json\n", arr.size());
for (const auto& entry : arr)
{
if (entry.is_string())
{ {
Node::Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data()))); Network::Address address(entry.get<std::string>());
Add(address);
} }
} }
} }
@ -99,29 +126,32 @@ namespace Components
{ {
if (Dedicated::IsEnabled() && Dedicated::SVLanOnly.get<bool>()) return; if (Dedicated::IsEnabled() && Dedicated::SVLanOnly.get<bool>()) return;
std::vector<std::string> nodes;
static Utils::Time::Interval interval; static Utils::Time::Interval interval;
if (!force && !interval.elapsed(1min)) return; if (!force && !interval.elapsed(1min)) return;
interval.update(); interval.update();
Proto::Node::List list; Mutex.lock();
Node::Mutex.lock(); for (auto& node : Nodes)
for (auto& node : Node::Nodes)
{ {
if (node.isValid()) if (node.isValid() || force)
{ {
std::string* str = list.add_nodes(); const auto address = node.address.getString();
nodes.emplace_back(address);
sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
} }
} }
Node::Mutex.unlock();
Utils::IO::WriteFile("players/nodes.dat", Utils::Compression::ZLib::Compress(list.SerializeAsString())); Mutex.unlock();
nlohmann::json out;
out["nodes"] = nodes;
Utils::IO::WriteFile("players/nodes.json", out.dump());
} }
void Node::Add(Network::Address address) void Node::Add(const Network::Address& address)
{ {
#ifndef DEBUG #ifndef DEBUG
if (address.isLocal() || address.isSelf()) return; if (address.isLocal() || address.isSelf()) return;
@ -129,23 +159,23 @@ namespace Components
if (!address.isValid()) return; if (!address.isValid()) return;
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
for (auto& session : Node::Nodes) for (auto& session : Nodes)
{ {
if (session.address == address) return; if (session.address == address) return;
} }
Node::Entry node; Entry node;
node.address = address; node.address = address;
Node::Nodes.push_back(node); Nodes.push_back(node);
} }
std::vector<Node::Entry> Node::GetNodes() std::vector<Node::Entry> Node::GetNodes()
{ {
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
return Node::Nodes; return Nodes;
} }
void Node::RunFrame() void Node::RunFrame()
@ -165,34 +195,34 @@ namespace Components
if (WasIngame) // our last frame we were in-game and now we aren't so touch all nodes if (WasIngame) // our last frame we were in-game and now we aren't so touch all nodes
{ {
for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();++i) for (auto& entry : Nodes)
{ {
// clearing the last request and response times makes the // clearing the last request and response times makes the
// dispatcher think its a new node and will force a refresh // dispatcher think its a new node and will force a refresh
i->lastRequest.reset(); entry.lastRequest.reset();
i->lastResponse.reset(); entry.lastResponse.reset();
} }
WasIngame = false; WasIngame = false;
} }
static Utils::Time::Interval frameLimit; static Utils::Time::Interval frameLimit;
int interval = static_cast<int>(1000.0f / Dvar::Var("net_serverFrames").get<int>()); const auto interval = 1000 / ServerList::NETServerFrames.get<int>();
if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return; if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return;
frameLimit.update(); frameLimit.update();
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
Dvar::Var queryLimit("net_serverQueryLimit");
int sentRequests = 0; int sentRequests = 0;
for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();) for (auto i = Nodes.begin(); i != Nodes.end();)
{ {
if (i->isDead()) if (i->isDead())
{ {
i = Node::Nodes.erase(i); i = Nodes.erase(i);
continue; continue;
} }
if (sentRequests < queryLimit.get<int>() && i->requiresRequest())
if (sentRequests < ServerList::NETServerQueryLimit.get<int>() && i->requiresRequest())
{ {
++sentRequests; ++sentRequests;
i->sendRequest(); i->sendRequest();
@ -204,17 +234,16 @@ namespace Components
void Node::Synchronize() void Node::Synchronize()
{ {
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
for (auto& node : Node::Nodes) for (auto& node : Nodes)
{ {
//if (node.isValid()) // Comment out to simulate 'syncnodes' behaviour
{ {
node.reset(); node.reset();
} }
} }
} }
void Node::HandleResponse(Network::Address address, const std::string& data) void Node::HandleResponse(const Network::Address& address, const std::string& data)
{ {
Proto::Node::List list; Proto::Node::List list;
if (!list.ParseFromString(data)) return; if (!list.ParseFromString(data)) return;
@ -223,7 +252,7 @@ namespace Components
Logger::Debug("Received response from {}", address.getString()); Logger::Debug("Received response from {}", address.getString());
#endif #endif
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
for (int i = 0; i < list.nodes_size(); ++i) for (int i = 0; i < list.nodes_size(); ++i)
{ {
@ -231,7 +260,7 @@ namespace Components
if (addr.size() == sizeof(sockaddr)) if (addr.size() == sizeof(sockaddr))
{ {
Node::Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data()))); Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
} }
} }
@ -251,7 +280,7 @@ namespace Components
#endif #endif
} }
for (auto& node : Node::Nodes) for (auto& node : Nodes)
{ {
if (address == node.address) if (address == node.address)
{ {
@ -263,39 +292,41 @@ namespace Components
} }
} }
Node::Entry entry; Entry entry;
entry.address = address; entry.address = address;
entry.data.protocol = list.protocol(); entry.data.protocol = list.protocol();
entry.lastResponse.emplace(Utils::Time::Point()); entry.lastResponse.emplace(Utils::Time::Point());
Node::Nodes.push_back(entry); Nodes.push_back(entry);
} }
} }
void Node::SendList(const Network::Address& address) void Node::SendList(const Network::Address& address)
{ {
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
// need to keep the message size below 1404 bytes else recipient will just drop it // need to keep the message size below 1404 bytes else recipient will just drop it
std::vector<std::string> nodeListReponseMessages; std::vector<std::string> nodeListReponseMessages;
for (std::size_t curNode = 0; curNode < Node::Nodes.size();) for (std::size_t curNode = 0; curNode < Nodes.size();)
{ {
Proto::Node::List list; Proto::Node::List list;
list.set_isnode(Dedicated::IsEnabled()); list.set_isnode(Dedicated::IsEnabled());
list.set_protocol(PROTOCOL); list.set_protocol(PROTOCOL);
list.set_port(Node::GetPort()); list.set_port(GetPort());
for (std::size_t i = 0; i < NODE_MAX_NODES_TO_SEND;) for (std::size_t i = 0; i < NODE_MAX_NODES_TO_SEND;)
{ {
if (curNode >= Node::Nodes.size()) if (curNode >= Nodes.size())
{
break; break;
}
auto node = Node::Nodes.at(curNode++); auto& node = Nodes.at(curNode++);
if (node.isValid()) if (node.isValid())
{ {
std::string* str = list.add_nodes(); auto* str = list.add_nodes();
sockaddr addr = node.address.getSockAddr(); sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr)); str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
@ -320,65 +351,100 @@ namespace Components
} }
} }
unsigned short Node::GetPort() std::uint16_t Node::GetPort()
{ {
if (Dvar::Var("net_natFix").get<bool>()) return 0; if (net_natFix->current.enabled) return 0;
return Network::GetPort(); return Network::GetPort();
} }
void Node::Migrate()
{
Proto::Node::List list;
std::string nodes;
if (!Utils::IO::ReadFile("players/nodes.dat", &nodes) || nodes.empty())
{
return;
}
if (!list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes)))
{
return;
}
std::vector<std::string> data;
for (auto i = 0; i < list.nodes_size(); ++i)
{
const std::string& addr = list.nodes(i);
if (addr.size() == sizeof(sockaddr))
{
Network::Address address(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
data.emplace_back(address.getString());
}
}
nlohmann::json out;
out["nodes"] = data;
if (!Utils::IO::FileExists("players/nodes.json"))
{
Utils::IO::WriteFile("players/nodes.json", out.dump());
}
Utils::IO::RemoveFile("players/nodes.dat");
}
Node::Node() Node::Node()
{ {
if (ZoneBuilder::IsEnabled()) return; net_natFix = Game::Dvar_RegisterBool("net_natFix", false, 0, "Fix node registration for certain firewalls/routers");
Dvar::Register<bool>("net_natFix", false, 0, "Fix node registration for certain firewalls/routers");
Scheduler::Loop([] Scheduler::Loop([]
{ {
Node::StoreNodes(false); StoreNodes(false);
}, Scheduler::Pipeline::ASYNC); }, Scheduler::Pipeline::ASYNC, 5min);
Scheduler::Loop(Node::RunFrame, Scheduler::Pipeline::MAIN); Scheduler::Loop(RunFrame, Scheduler::Pipeline::MAIN);
Session::Handle("nodeListResponse", Node::HandleResponse); Session::Handle("nodeListResponse", HandleResponse);
Session::Handle("nodeListRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Session::Handle("nodeListRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{ {
Node::SendList(address); SendList(address);
}); });
// Load stored nodes Scheduler::OnGameInitialized([]
auto loadNodes = []
{ {
Node::LoadNodePreset(); Migrate();
Node::LoadNodes(); LoadNodePreset();
}; LoadNodes();
}, Scheduler::Pipeline::MAIN);
Scheduler::OnGameInitialized(loadNodes, Scheduler::Pipeline::MAIN); Command::Add("listNodes", [](const Command::Params*)
Command::Add("listnodes", [](const Command::Params*)
{ {
Logger::Print("Nodes: {}\n", Node::Nodes.size()); Logger::Print("Nodes: {}\n", Nodes.size());
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
for (auto& node : Node::Nodes) for (const auto& node : Nodes)
{ {
Logger::Print("{}\t({})\n", node.address.getString(), node.isValid() ? "Valid" : "Invalid"); Logger::Print("{}\t({})\n", node.address.getString(), node.isValid() ? "Valid" : "Invalid");
} }
}); });
Command::Add("addnode", [](const Command::Params* params) Command::Add("addNode", [](const Command::Params* params)
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
auto address = Network::Address{ params->get(1) }; auto address = Network::Address{ params->get(1) };
if (address.isValid()) if (address.isValid())
{ {
Node::Add(address); Add(address);
} }
}); });
} }
Node::~Node() void Node::preDestroy()
{ {
std::lock_guard _(Node::Mutex); std::lock_guard _(Mutex);
Node::StoreNodes(true); StoreNodes(true);
Node::Nodes.clear(); Nodes.clear();
} }
} }

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#define NODE_HALFLIFE (3 * 60 * 1000) //3min #define NODE_HALFLIFE (3 * 60 * 1000) // 3min
#define NODE_MAX_NODES_TO_SEND 64 #define NODE_MAX_NODES_TO_SEND 64
#define NODE_SEND_RATE 500ms #define NODE_SEND_RATE 500ms
@ -12,7 +12,7 @@ namespace Components
class Data class Data
{ {
public: public:
uint64_t protocol; std::uint64_t protocol;
}; };
class Entry class Entry
@ -34,9 +34,9 @@ namespace Components
}; };
Node(); Node();
~Node(); void preDestroy() override;
static void Add(Network::Address address); static void Add(const Network::Address& address);
static std::vector<Entry> GetNodes(); static std::vector<Entry> GetNodes();
static void RunFrame(); static void RunFrame();
static void Synchronize(); static void Synchronize();
@ -46,7 +46,9 @@ namespace Components
static std::vector<Entry> Nodes; static std::vector<Entry> Nodes;
static bool WasIngame; static bool WasIngame;
static void HandleResponse(Network::Address address, const std::string& data); static const Game::dvar_t* net_natFix;
static void HandleResponse(const Network::Address& address, const std::string& data);
static void SendList(const Network::Address& address); static void SendList(const Network::Address& address);
@ -54,6 +56,8 @@ namespace Components
static void LoadNodes(); static void LoadNodes();
static void StoreNodes(bool force); static void StoreNodes(bool force);
static unsigned short GetPort(); static std::uint16_t GetPort();
static void Migrate();
}; };
} }

View File

@ -245,10 +245,10 @@ namespace Components
} }
auto workingDir = std::filesystem::current_path().string(); auto workingDir = std::filesystem::current_path().string();
auto binary = FileSystem::GetAppdataPath() / "data" / "iw4x" / *Game::sys_exitCmdLine; const std::string binary = *Game::sys_exitCmdLine;
SetEnvironmentVariableA("XLABS_MW2_INSTALL", workingDir.data()); SetEnvironmentVariableA("MW2_INSTALL", workingDir.data());
Utils::Library::LaunchProcess(binary.string(), "-singleplayer", workingDir); Utils::Library::LaunchProcess(binary, "-singleplayer", workingDir);
} }
__declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub() __declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub()
@ -320,7 +320,7 @@ namespace Components
Utils::Hook::Set<void(*)(Game::XAssetHeader, void*)>(0x51FCDD, QuickPatch::R_AddImageToList_Hk); Utils::Hook::Set<void(*)(Game::XAssetHeader, void*)>(0x51FCDD, QuickPatch::R_AddImageToList_Hk);
Utils::Hook::Set<const char*>(0x41DB8C, "iw4x-sp.exe"); Utils::Hook::Set<const char*>(0x41DB8C, "iw4-sp.exe");
Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick();
// Fix crash as nullptr goes unchecked // Fix crash as nullptr goes unchecked

View File

@ -127,10 +127,10 @@ namespace Components
{ {
Utils::InfoString info; Utils::InfoString info;
info.set("admin", Dvar::Var("_Admin").get<const char*>()); info.set("admin", Dvar::Var("_Admin").get<std::string>());
info.set("website", Dvar::Var("_Website").get<const char*>()); info.set("website", Dvar::Var("_Website").get<std::string>());
info.set("email", Dvar::Var("_Email").get<const char*>()); info.set("email", Dvar::Var("_Email").get<std::string>());
info.set("location", Dvar::Var("_Location").get<const char*>()); info.set("location", Dvar::Var("_Location").get<std::string>());
return info; return info;
} }

View File

@ -54,6 +54,11 @@ namespace Components
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address); static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
static bool UseMasterServer; static bool UseMasterServer;
static Dvar::Var UIServerSelected;
static Dvar::Var UIServerSelectedMap;
static Dvar::Var NETServerQueryLimit;
static Dvar::Var NETServerFrames;
private: private:
enum class Column : int enum class Column : int
{ {
@ -150,11 +155,6 @@ namespace Components
static std::vector<unsigned int> VisibleList; static std::vector<unsigned int> VisibleList;
static Dvar::Var UIServerSelected;
static Dvar::Var UIServerSelectedMap;
static Dvar::Var NETServerQueryLimit;
static Dvar::Var NETServerFrames;
static bool IsServerListOpen(); static bool IsServerListOpen();
}; };
} }

View File

@ -149,159 +149,10 @@ namespace Components
{ {
if (Dedicated::IsEnabled()) return; if (Dedicated::IsEnabled()) return;
// Do not execute this when building zones // Correctly upgrade stats
if (!ZoneBuilder::IsEnabled()) Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick();
{
// Correctly upgrade stats
Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick();
// 15 or more custom classes // 15 or more custom classes
Utils::Hook::Set<BYTE>(0x60A2FE, NUM_CUSTOM_CLASSES); Utils::Hook::Set<BYTE>(0x60A2FE, NUM_CUSTOM_CLASSES);
return;
}
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& filename, bool* /*restrict*/)
{
// Only intercept playerdatadef loading
if (type != Game::ASSET_TYPE_STRUCTURED_DATA_DEF || filename != "mp/playerdata.def") return;
// Store asset
Game::StructuredDataDefSet* data = asset.structuredDataDefSet;
if (!data) return;
if (data->defCount != 1)
{
Logger::Error(Game::ERR_FATAL, "PlayerDataDefSet contains more than 1 definition!");
return;
}
if (data->defs[0].version != 155)
{
Logger::Error(Game::ERR_FATAL, "Initial PlayerDataDef is not version 155, patching not possible!");
return;
}
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)
{
// We're on DB thread (OnLoad) so use DB thread for FS
FileSystem::File definition(std::format("{}/{}.json", filename, i), Game::FsThread::FS_THREAD_DATABASE);
if (!definition.exists()) break;
std::vector<std::vector<std::string>> enumContainer;
std::unordered_map<std::string, std::string> otherPatches;
nlohmann::json defData;
try
{
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;
}
if (!defData.is_object())
{
Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} is invalid!", i);
return;
}
for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType)
{
auto enumData = defData[StructuredData::EnumTranslation[pType]];
std::vector<std::string> entryData;
if (enumData.is_array())
{
for (const auto& rawEntry : enumData)
{
if (rawEntry.is_string())
{
entryData.push_back(rawEntry.get<std::string>());
}
}
}
enumContainer.push_back(entryData);
}
auto other = defData["other"];
if (other.is_object())
{
for (auto& item : other.items())
{
if (item.value().is_string())
{
otherPatches[item.key()] = item.value().get<std::string>();
}
}
}
patchDefinitions[i] = enumContainer;
otherPatchDefinitions[i] = otherPatches;
}
// Nothing to patch
if (patchDefinitions.empty()) return;
// Reallocate the definition
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
for (unsigned int i = 0; i < patchDefinitions.size(); ++i)
{
std::memcpy(&newData[i], data->defs, sizeof Game::StructuredDataDef);
newData[i].version = (patchDefinitions.size() - i) + 155;
// Reallocate the enum array
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;
}
// Apply new data
data->defs = newData;
data->defCount += patchDefinitions.size();
// Patch the definition
for (unsigned int i = 0; i < data->defCount; ++i)
{
// No need to patch version 155
if (newData[i].version == 155) continue;
if (patchDefinitions.contains(newData[i].version))
{
auto patchData = patchDefinitions[newData[i].version];
auto otherData = otherPatchDefinitions[newData[i].version];
// Invalid patch data
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 (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType)
{
if (!patchData[pType].empty())
{
StructuredData::PatchPlayerDataEnum(&newData[i], static_cast<StructuredData::PlayerDataType>(pType), patchData[pType]);
}
}
StructuredData::PatchAdditionalData(&newData[i], otherData);
}
}
});
} }
} }

View File

@ -63,7 +63,7 @@ BEGIN
BEGIN BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "CompanyName", "XLabsProject" VALUE "CompanyName", "IW4x"
#ifdef _DEBUG #ifdef _DEBUG
VALUE "FileDescription", "IW4 client modification (DEBUG)" VALUE "FileDescription", "IW4 client modification (DEBUG)"
#else #else
@ -71,7 +71,7 @@ BEGIN
#endif #endif
VALUE "FileVersion", REVISION_STR VALUE "FileVersion", REVISION_STR
VALUE "InternalName", "iw4x" VALUE "InternalName", "iw4x"
VALUE "LegalCopyright", "Copyright 2023 The XLabsProject Team. All rights reserved." VALUE "LegalCopyright", "Copyright 2023 The IW4x Team. All rights reserved."
VALUE "OriginalFilename", "iw4x.dll" VALUE "OriginalFilename", "iw4x.dll"
VALUE "ProductName", "IW4x" VALUE "ProductName", "IW4x"
VALUE "ProductVersion", REVISION_STR VALUE "ProductVersion", REVISION_STR

View File

@ -143,6 +143,12 @@ using namespace std::literals;
#define BASEGAME_NAME "iw4mp_ceg.exe" #define BASEGAME_NAME "iw4mp_ceg.exe"
#define CLIENT_CONFIG "iw4x_config.cfg" #define CLIENT_CONFIG "iw4x_config.cfg"
#define XFILE_MAGIC_UNSIGNED 0x3030317566665749
#define XFILE_VERSION 276
#define XFILE_HEADER_IW4X 0x78345749 // 'IW4x'
#define XFILE_VERSION_IW4X 3
// Resource stuff // Resource stuff
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS

View File

@ -23,11 +23,7 @@ namespace Steam
if (!idBits) if (!idBits)
{ {
if (Components::ZoneBuilder::IsEnabled()) if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid
{
idBits = *reinterpret_cast<unsigned __int64*>(const_cast<char*>("DEDICATE"));
}
else if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid
{ {
idBits = Components::Auth::GetKeyHash(); idBits = Components::Auth::GetKeyHash();
} }

View File

@ -132,7 +132,7 @@ namespace Steam
void Proxy::RunGame() void Proxy::RunGame()
{ {
if (Steam::Enabled() && !Components::Dedicated::IsEnabled() && !Components::ZoneBuilder::IsEnabled()) if (Steam::Enabled() && !Components::Dedicated::IsEnabled())
{ {
SetEnvironmentVariableA("SteamAppId", ::Utils::String::VA("%lu", Proxy::AppId)); SetEnvironmentVariableA("SteamAppId", ::Utils::String::VA("%lu", Proxy::AppId));
SetEnvironmentVariableA("SteamGameId", ::Utils::String::VA("%llu", Proxy::AppId & 0xFFFFFF)); SetEnvironmentVariableA("SteamGameId", ::Utils::String::VA("%llu", Proxy::AppId & 0xFFFFFF));
@ -146,7 +146,7 @@ namespace Steam
void Proxy::SetMod(const std::string& mod) void Proxy::SetMod(const std::string& mod)
{ {
if (!Proxy::ClientUser || !Proxy::SteamApps || !Steam::Enabled() || Components::Dedicated::IsEnabled() || Components::ZoneBuilder::IsEnabled()) return; if (!Proxy::ClientUser || !Proxy::SteamApps || !Steam::Enabled() || Components::Dedicated::IsEnabled()) return;
if (!Proxy::SteamApps->BIsSubscribedApp(Proxy::AppId)) if (!Proxy::SteamApps->BIsSubscribedApp(Proxy::AppId))
{ {
@ -382,7 +382,7 @@ namespace Steam
SetDllDirectoryA(directoy.data()); SetDllDirectoryA(directoy.data());
if (!Components::Dedicated::IsEnabled() && !Components::ZoneBuilder::IsEnabled()) if (!Components::Dedicated::IsEnabled())
{ {
Proxy::LaunchWatchGuard(); Proxy::LaunchWatchGuard();

View File

@ -130,7 +130,7 @@ namespace Steam
} }
else else
{ {
Proxy::SetMod("IW4x: Modern Warfare 2"); Proxy::SetMod("IW4y: Modern Warfare 2");
Proxy::RunGame(); Proxy::RunGame();
} }

View File

@ -5,8 +5,8 @@ namespace Utils
{ {
const char* Cache::Urls[] = const char* Cache::Urls[] =
{ {
"https://raw.githubusercontent.com/XLabsProject/iw4x-client", "https://raw.githubusercontent.com/diamante0018/iw4x-client",
"https://xlabs.dev", "https://alterware.dev",
}; };
std::string Cache::ValidUrl; std::string Cache::ValidUrl;

View File

@ -61,14 +61,14 @@ namespace Utils
{ {
if (!key.isValid()) return {}; if (!key.isValid()) return {};
std::uint8_t buffer[512]; std::uint8_t buffer[512]{};
unsigned long length = sizeof(buffer); unsigned long length = sizeof(buffer);
ltc_mp = ltm_desc; ltc_mp = ltm_desc;
register_prng(&sprng_desc); register_prng(&sprng_desc);
ecc_sign_hash(reinterpret_cast<const std::uint8_t*>(message.data()), message.size(), buffer, &length, nullptr, find_prng("sprng"), key.getKeyPtr()); ecc_sign_hash(reinterpret_cast<const std::uint8_t*>(message.data()), message.size(), buffer, &length, nullptr, find_prng("sprng"), key.getKeyPtr());
return std::string{ reinterpret_cast<char*>(buffer), length }; return std::string{ reinterpret_cast<char*>(buffer), length };
} }
bool ECC::VerifyMessage(Key key, const std::string& message, const std::string& signature) bool ECC::VerifyMessage(Key key, const std::string& message, const std::string& signature)
@ -92,7 +92,6 @@ namespace Utils
Key key; Key key;
register_prng(&sprng_desc); register_prng(&sprng_desc);
register_hash(&sha1_desc);
ltc_mp = ltm_desc; ltc_mp = ltm_desc;
@ -105,29 +104,37 @@ namespace Utils
{ {
if (!key.isValid()) return {}; if (!key.isValid()) return {};
std::uint8_t buffer[512]; std::uint8_t buffer[512]{};
unsigned long length = sizeof(buffer); unsigned long length = sizeof(buffer);
register_prng(&sprng_desc); const auto hash = SHA512::Compute(message);
register_hash(&sha1_desc);
const ltc_hash_descriptor& hash_desc = sha512_desc;
const int hash_index = register_hash(&hash_desc);
ltc_mp = ltm_desc; ltc_mp = ltm_desc;
rsa_sign_hash(reinterpret_cast<const std::uint8_t*>(message.data()), message.size(), buffer, &length, NULL, find_prng("sprng"), find_hash("sha1"), 0, key.getKeyPtr()); rsa_sign_hash_ex(reinterpret_cast<const std::uint8_t*>(hash.data()), hash.size(),
buffer, &length, LTC_PKCS_1_V1_5, nullptr, 0, hash_index, 0, key.getKeyPtr());
return std::string{ reinterpret_cast<char*>(buffer), length }; return std::string{ reinterpret_cast<char*>(buffer), length };
} }
bool RSA::VerifyMessage(Key key, const std::string& message, const std::string& signature) bool RSA::VerifyMessage(Key key, const std::string& message, const std::string& signature)
{ {
if (!key.isValid()) return false; if (!key.isValid()) return false;
register_hash(&sha1_desc); const auto hash = SHA512::Compute(message);
const ltc_hash_descriptor& hash_desc = sha512_desc;
const int hash_index = register_hash(&hash_desc);
ltc_mp = ltm_desc; ltc_mp = ltm_desc;
int result = 0; auto result = 0;
return (rsa_verify_hash(reinterpret_cast<const std::uint8_t*>(signature.data()), signature.size(), reinterpret_cast<const std::uint8_t*>(message.data()), message.size(), find_hash("sha1"), 0, &result, key.getKeyPtr()) == CRYPT_OK && result != 0); return (rsa_verify_hash_ex(reinterpret_cast<const std::uint8_t*>(signature.data()), signature.size(),
reinterpret_cast<const std::uint8_t*>(hash.data()), hash.size(), LTC_PKCS_1_V1_5,
hash_index, 0, &result, key.getKeyPtr()) == CRYPT_OK && result != 0);
} }
#pragma endregion #pragma endregion
@ -145,9 +152,9 @@ namespace Utils
encData.resize(text.size()); encData.resize(text.size());
symmetric_CBC cbc; symmetric_CBC cbc;
int des3 = find_cipher("3des"); const auto des3 = find_cipher("3des");
cbc_start(des3, reinterpret_cast<const std::uint8_t*>(iv.data()), reinterpret_cast<const std::uint8_t*>(key.data()), key.size(), 0, &cbc); cbc_start(des3, reinterpret_cast<const std::uint8_t*>(iv.data()), reinterpret_cast<const std::uint8_t*>(key.data()), static_cast<int>(key.size()), 0, &cbc);
cbc_encrypt(reinterpret_cast<const std::uint8_t*>(text.data()), reinterpret_cast<uint8_t*>(encData.data()), text.size(), &cbc); cbc_encrypt(reinterpret_cast<const std::uint8_t*>(text.data()), reinterpret_cast<uint8_t*>(encData.data()), text.size(), &cbc);
cbc_done(&cbc); cbc_done(&cbc);
@ -160,9 +167,9 @@ namespace Utils
decData.resize(data.size()); decData.resize(data.size());
symmetric_CBC cbc; symmetric_CBC cbc;
int des3 = find_cipher("3des"); const auto des3 = find_cipher("3des");
cbc_start(des3, reinterpret_cast<const std::uint8_t*>(iv.data()), reinterpret_cast<const std::uint8_t*>(key.data()), key.size(), 0, &cbc); cbc_start(des3, reinterpret_cast<const std::uint8_t*>(iv.data()), reinterpret_cast<const std::uint8_t*>(key.data()), static_cast<int>(key.size()), 0, &cbc);
cbc_decrypt(reinterpret_cast<const std::uint8_t*>(data.data()), reinterpret_cast<std::uint8_t*>(decData.data()), data.size(), &cbc); cbc_decrypt(reinterpret_cast<const std::uint8_t*>(data.data()), reinterpret_cast<std::uint8_t*>(decData.data()), data.size(), &cbc);
cbc_done(&cbc); cbc_done(&cbc);
@ -269,20 +276,21 @@ namespace Utils
#pragma region JenkinsOneAtATime #pragma region JenkinsOneAtATime
unsigned int JenkinsOneAtATime::Compute(const std::string& data) std::size_t JenkinsOneAtATime::Compute(const std::string& data)
{ {
return Compute(data.data(), data.size()); return Compute(data.data(), data.size());
} }
unsigned int JenkinsOneAtATime::Compute(const char* key, std::size_t len) std::size_t JenkinsOneAtATime::Compute(const char* key, const std::size_t len)
{ {
unsigned int hash, i; std::size_t hash, i;
for (hash = i = 0; i < len; ++i) for (hash = i = 0; i < len; ++i)
{ {
hash += key[i]; hash += key[i];
hash += (hash << 10); hash += (hash << 10);
hash ^= (hash >> 6); hash ^= (hash >> 6);
} }
hash += (hash << 3); hash += (hash << 3);
hash ^= (hash >> 11); hash ^= (hash >> 11);
hash += (hash << 15); hash += (hash << 15);

View File

@ -9,16 +9,16 @@ namespace Utils
class Token class Token
{ {
public: public:
Token() { this->tokenString.clear(); }; Token() { this->tokenString.clear(); }
Token(const Token& obj) : tokenString(obj.tokenString) { }; Token(const Token& obj) : tokenString(obj.tokenString) { }
Token(const std::string& token) : tokenString(token.begin(), token.end()) { }; Token(const std::string& token) : tokenString(token.begin(), token.end()) { }
Token(const std::basic_string<uint8_t>& token) : tokenString(token.begin(), token.end()) { }; Token(const std::basic_string<std::uint8_t>& token) : tokenString(token.begin(), token.end()) { }
Token& operator++ () Token& operator++ ()
{ {
if (this->tokenString.empty()) if (this->tokenString.empty())
{ {
this->tokenString.append(reinterpret_cast<uint8_t*>(const_cast<char *>("\0")), 1); this->tokenString.append(reinterpret_cast<std::uint8_t*>(const_cast<char *>("\0")), 1);
} }
else else
{ {
@ -30,8 +30,7 @@ namespace Utils
if (!i) if (!i)
{ {
// Prepend here, as /dev/urandom says so ;) https://github.com/IW4x/iw4x-client-node/wikis/technical-information#incrementing-the-token this->tokenString = std::basic_string{ reinterpret_cast<std::uint8_t*>(const_cast<char*>("\0")), 1 } + this->tokenString;
this->tokenString = std::basic_string<std::uint8_t>(reinterpret_cast<std::uint8_t*>(const_cast<char*>("\0")), 1) + this->tokenString;
break; break;
} }
} }
@ -69,25 +68,25 @@ namespace Utils
{ {
return false; return false;
} }
else if (this->toString().size() < token.toString().size())
if (this->toString().size() < token.toString().size())
{ {
return true; return true;
} }
else if (this->toString().size() > token.toString().size())
if (this->toString().size() > token.toString().size())
{ {
return false; return false;
} }
else
{
auto lStr = this->toString();
auto rStr = token.toString();
for (unsigned int i = 0; i < lStr.size(); ++i) auto lStr = this->toString();
auto rStr = token.toString();
for (std::size_t i = 0; i < lStr.size(); ++i)
{
if (lStr[i] < rStr[i])
{ {
if (lStr[i] < rStr[i]) return true;
{
return true;
}
} }
} }
@ -109,17 +108,12 @@ namespace Utils
return !(*this < token); return !(*this < token);
} }
std::string toString() [[nodiscard]] std::string toString() const
{ {
return std::string(this->tokenString.begin(), this->tokenString.end()); return std::string{ this->tokenString.begin(), this->tokenString.end() };
} }
std::string toString() const [[nodiscard]] std::basic_string<std::uint8_t> toUnsignedString() const
{
return std::string(this->tokenString.begin(), this->tokenString.end());
}
std::basic_string<uint8_t> toUnsignedString()
{ {
return this->tokenString; return this->tokenString;
} }
@ -137,7 +131,7 @@ namespace Utils
{ {
public: public:
static std::string GenerateChallenge(); static std::string GenerateChallenge();
static uint32_t GenerateInt(); static std::uint32_t GenerateInt();
static void Initialize(); static void Initialize();
private: private:
@ -153,45 +147,47 @@ namespace Utils
Key() : keyStorage(new ecc_key) Key() : keyStorage(new ecc_key)
{ {
ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr())); ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr()));
}; }
Key(ecc_key* key) : Key() { if (key) std::memmove(this->getKeyPtr(), key, sizeof(*key)); };
Key(ecc_key key) : Key(&key) {}; Key(ecc_key* key) : Key() { if (key) std::memmove(this->getKeyPtr(), key, sizeof(*key)); }
Key(ecc_key key) : Key(&key) {}
~Key() ~Key()
{ {
if (this->keyStorage.use_count() <= 1) if (this->keyStorage.use_count() <= 1)
{ {
this->free(); this->free();
} }
};
bool isValid()
{
return (!Utils::Memory::IsSet(this->getKeyPtr(), 0, sizeof(*this->getKeyPtr())));
} }
ecc_key* getKeyPtr() [[nodiscard]] bool isValid()
{
return (!Memory::IsSet(this->getKeyPtr(), 0, sizeof(*this->getKeyPtr())));
}
[[nodiscard]] ecc_key* getKeyPtr()
{ {
return this->keyStorage.get(); return this->keyStorage.get();
} }
std::string getPublicKey() [[nodiscard]] std::string getPublicKey()
{ {
uint8_t buffer[512] = { 0 }; std::uint8_t buffer[512]{};
DWORD length = sizeof(buffer); unsigned long length = sizeof(buffer);
if (ecc_ansi_x963_export(this->getKeyPtr(), buffer, &length) == CRYPT_OK) if (ecc_ansi_x963_export(this->getKeyPtr(), buffer, &length) == CRYPT_OK)
{ {
return std::string(reinterpret_cast<char*>(buffer), length); return std::string{ reinterpret_cast<char*>(buffer), length };
} }
return ""; return std::string{};
} }
void set(const std::string& pubKeyBuffer) void set(const std::string& pubKeyBuffer)
{ {
this->free(); this->free();
if (ecc_ansi_x963_import(reinterpret_cast<const uint8_t*>(pubKeyBuffer.data()), pubKeyBuffer.size(), this->getKeyPtr()) != CRYPT_OK) if (ecc_ansi_x963_import(reinterpret_cast<const std::uint8_t*>(pubKeyBuffer.data()), pubKeyBuffer.size(), this->getKeyPtr()) != CRYPT_OK)
{ {
ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr())); ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr()));
} }
@ -201,23 +197,23 @@ namespace Utils
{ {
this->free(); this->free();
if (ecc_import(reinterpret_cast<const uint8_t*>(key.data()), key.size(), this->getKeyPtr()) != CRYPT_OK) if (ecc_import(reinterpret_cast<const std::uint8_t*>(key.data()), key.size(), this->getKeyPtr()) != CRYPT_OK)
{ {
ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr())); ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr()));
} }
} }
std::string serialize(int type = PK_PRIVATE) [[nodiscard]] std::string serialize(int type = PK_PRIVATE)
{ {
uint8_t buffer[4096] = { 0 }; std::uint8_t buffer[4096]{};
DWORD length = sizeof(buffer); unsigned long length = sizeof(buffer);
if (ecc_export(buffer, &length, type, this->getKeyPtr()) == CRYPT_OK) if (ecc_export(buffer, &length, type, this->getKeyPtr()) == CRYPT_OK)
{ {
return std::string(reinterpret_cast<char*>(buffer), length); return std::string{ reinterpret_cast<char*>(buffer), length };
} }
return ""; return std::string{};
} }
void free() void free()
@ -266,14 +262,50 @@ namespace Utils
} }
} }
rsa_key* getKeyPtr() [[nodiscard]] bool isValid()
{
return (!Memory::IsSet(this->getKeyPtr(), 0, sizeof(*this->getKeyPtr())));
}
[[nodiscard]] rsa_key* getKeyPtr()
{ {
return this->keyStorage.get(); return this->keyStorage.get();
} }
bool isValid() [[nodiscard]] std::string getPublicKey()
{ {
return (!Memory::IsSet(this->getKeyPtr(), 0, sizeof(*this->getKeyPtr()))); std::uint8_t buffer[4096]{};
unsigned long length = sizeof(buffer);
if (rsa_export(buffer, &length, PK_PUBLIC, this->getKeyPtr()) == CRYPT_OK)
{
return std::string{ reinterpret_cast<char*>(buffer), length };
}
return std::string{};
}
void set(const std::string& keyBuffer)
{
this->free();
if (rsa_import(reinterpret_cast<const std::uint8_t*>(keyBuffer.data()), keyBuffer.size(), this->getKeyPtr()) != CRYPT_OK)
{
ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr()));
}
}
[[nodiscard]] std::string serialize(int type = PK_PRIVATE)
{
std::uint8_t buffer[4096]{};
unsigned long length = sizeof(buffer);
if (rsa_export(buffer, &length, type, this->getKeyPtr()) == CRYPT_OK)
{
return std::string{ reinterpret_cast<char*>(buffer), length };
}
return std::string{};
} }
void free() void free()
@ -334,8 +366,8 @@ namespace Utils
class JenkinsOneAtATime class JenkinsOneAtATime
{ {
public: public:
static unsigned int Compute(const std::string& data); static std::size_t Compute(const std::string& data);
static unsigned int Compute(const char* key, std::size_t len); static std::size_t Compute(const char* key, std::size_t len);
}; };
} }
} }

View File

@ -112,15 +112,15 @@ namespace Utils
auto* exeBaseName = std::wcsrchr(binaryPath, L'\\'); auto* exeBaseName = std::wcsrchr(binaryPath, L'\\');
exeBaseName[0] = L'\0'; exeBaseName[0] = L'\0';
// Make the game work without the xlabs launcher // Make the game work without the AlterWare launcher
SetCurrentDirectoryW(binaryPath); SetCurrentDirectoryW(binaryPath);
} }
void SetEnvironment() void SetEnvironment()
{ {
wchar_t* buffer{}; char* buffer{};
std::size_t size{}; std::size_t size{};
if (_wdupenv_s(&buffer, &size, L"XLABS_MW2_INSTALL") != 0 || buffer == nullptr) if (_dupenv_s(&buffer, &size, "MW2_INSTALL") != 0 || buffer == nullptr)
{ {
SetLegacyEnvironment(); SetLegacyEnvironment();
return; return;
@ -128,8 +128,8 @@ namespace Utils
const auto _0 = gsl::finally([&] { std::free(buffer); }); const auto _0 = gsl::finally([&] { std::free(buffer); });
SetCurrentDirectoryW(buffer); SetCurrentDirectoryA(buffer);
SetDllDirectoryW(buffer); SetDllDirectoryA(buffer);
} }
HMODULE GetNTDLL() HMODULE GetNTDLL()