Merge branch 'diamante_develop' into develop
# Conflicts: # .gitmodules
This commit is contained in:
commit
4b1aade899
3
.github/ISSUE_TEMPLATE/get-help.md
vendored
3
.github/ISSUE_TEMPLATE/get-help.md
vendored
@ -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?**
|
||||||
|
2
.github/ISSUE_TEMPLATE/request-a-feature.md
vendored
2
.github/ISSUE_TEMPLATE/request-a-feature.md
vendored
@ -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?**
|
||||||
|
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@ -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 }}
|
|
||||||
|
17
.github/workflows/discord-notify.yml
vendored
17
.github/workflows/discord-notify.yml
vendored
@ -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 }}
|
|
48
.github/workflows/draft-new-release.yml
vendored
48
.github/workflows/draft-new-release.yml
vendored
@ -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
|
|
83
.github/workflows/release.yml
vendored
83
.github/workflows/release.yml
vendored
@ -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
2
.gitmodules
vendored
@ -17,7 +17,7 @@
|
|||||||
[submodule "deps/mongoose"]
|
[submodule "deps/mongoose"]
|
||||||
path = deps/mongoose
|
path = deps/mongoose
|
||||||
url = https://github.com/cesanta/mongoose.git
|
url = https://github.com/cesanta/mongoose.git
|
||||||
branch = 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
|
||||||
|
12
README.md
12
README.md
@ -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
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
|||||||
Subproject commit fae62af0ab16f469c2512ec04575dd60ca018657
|
Subproject commit 93f5348c47d3578091a4ee5b90f4add216b46d1b
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 0df542cb70f621bbeec207be1949832fb1442479
|
Subproject commit c6a00c26ca2192c713a36227fdd84d126cdc95b9
|
2
deps/mongoose
vendored
2
deps/mongoose
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 4236405b90e051310aadda818e21c811e404b4d8
|
Subproject commit b379816178abdcd59135aa32f990a4b3bbbfb54b
|
@ -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",
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
@ -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())
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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");
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ namespace Steam
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Proxy::SetMod("IW4x: Modern Warfare 2");
|
Proxy::SetMod("IW4y: Modern Warfare 2");
|
||||||
Proxy::RunGame();
|
Proxy::RunGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user