diff --git a/.github/ISSUE_TEMPLATE/get-help.md b/.github/ISSUE_TEMPLATE/get-help.md index 7426ade0..f7dea0a1 100644 --- a/.github/ISSUE_TEMPLATE/get-help.md +++ b/.github/ISSUE_TEMPLATE/get-help.md @@ -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. -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/sKeVmR3) server if you still have problems. +Ask in `iw4x-support` or `iw4x-modding` channels on the [Discord](https://discord.gg/tKTQJxcCkk) server if you still have problems. If this does not apply, please continue by filling in the template below._ **What are you trying to do?** diff --git a/.github/ISSUE_TEMPLATE/request-a-feature.md b/.github/ISSUE_TEMPLATE/request-a-feature.md index 37d72635..31d54448 100644 --- a/.github/ISSUE_TEMPLATE/request-a-feature.md +++ b/.github/ISSUE_TEMPLATE/request-a-feature.md @@ -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._ **What problem will this solve?** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 951fd023..f3692290 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - Release steps: - name: Check out files - uses: actions/checkout@v3.5.2 + uses: actions/checkout@v3.5.3 with: submodules: true fetch-depth: 0 @@ -55,15 +55,11 @@ jobs: name: Deploy artifacts needs: build 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: - - 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 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 uses: actions/download-artifact@v3.0.2 @@ -74,20 +70,11 @@ jobs: - name: Install SSH key uses: shimataro/ssh-key-action@v2.5.1 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' - name: Add known hosts - run: ssh-keyscan -H ${{ secrets.XLABS_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/* + run: ssh-keyscan -H ${{ secrets.DIAMANTE_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts - - name: Upload iw4x binary - run: rsync -avz iw4x.dll ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }}:${{ env.XLABS_MASTER_PATH }}/iw4x/ - -# - 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 }} + - name: Upload iw4x-client binary + run: rsync -avz iw4x.dll ${{ secrets.DIAMANTE_MASTER_SSH_USER }}@${{ secrets.DIAMANTE_MASTER_SSH_ADDRESS }}:${{ env.DIAMANTE_MASTER_PATH }}/legacy/ diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml deleted file mode 100644 index d80352fb..00000000 --- a/.github/workflows/discord-notify.yml +++ /dev/null @@ -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 }} diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml deleted file mode 100644 index f3c13068..00000000 --- a/.github/workflows/draft-new-release.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index fb5e9814..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -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" diff --git a/.gitmodules b/.gitmodules index d32a35d3..0459fca8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,7 +17,7 @@ [submodule "deps/mongoose"] path = deps/mongoose url = https://github.com/cesanta/mongoose.git - branch = 7.9 + branch = 7.10 [submodule "deps/protobuf"] path = deps/protobuf url = https://github.com/google/protobuf.git diff --git a/README.md b/README.md index d7a32313..229b8703 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ -![license](https://img.shields.io/github/license/IW4x/iw4x-client.svg) -![forks](https://img.shields.io/github/forks/IW4x/iw4x-client.svg) -![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) +![license](https://img.shields.io/github/license/iw4x/iw4x-client.svg) +[![build](https://github.com/iw4x/iw4x-client/workflows/Build/badge.svg)](https://github.com/iw4x/iw4x-client/actions) # IW4x: Client @@ -37,10 +32,9 @@ | `-dump` | Write info of loaded assets to the raw folder as they are being loaded. | | `-nointro` | Skip game's cinematic intro. | | `-version` | Print IW4x build info on startup. | -| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. | | `-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. | - +| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. | ## Disclaimer diff --git a/deps/libtomcrypt b/deps/libtomcrypt index fae62af0..93f5348c 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit fae62af0ab16f469c2512ec04575dd60ca018657 +Subproject commit 93f5348c47d3578091a4ee5b90f4add216b46d1b diff --git a/deps/libtommath b/deps/libtommath index 0df542cb..c6a00c26 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 0df542cb70f621bbeec207be1949832fb1442479 +Subproject commit c6a00c26ca2192c713a36227fdd84d126cdc95b9 diff --git a/deps/mongoose b/deps/mongoose index 4236405b..b3798161 160000 --- a/deps/mongoose +++ b/deps/mongoose @@ -1 +1 @@ -Subproject commit 4236405b90e051310aadda818e21c811e404b4d8 +Subproject commit b379816178abdcd59135aa32f990a4b3bbbfb54b diff --git a/premake5.lua b/premake5.lua index 41cda692..35554fdf 100644 --- a/premake5.lua +++ b/premake5.lua @@ -273,8 +273,7 @@ workspace "iw4x" dependencies.imports() -- Pre-build - prebuildcommands - { + prebuildcommands { "pushd %{_MAIN_SCRIPT_DIR}", "tools\\premake5 generate-buildinfo", "popd", diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index 7e0b0290..fe659ec8 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -371,7 +371,7 @@ namespace Components MutedList.access([&](muteList& clients) { const nlohmann::json::array_t arr = list; - for (auto& entry : arr) + for (const auto& entry : arr) { if (entry.is_number_unsigned()) { diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index a758e58c..30fe4245 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -243,7 +243,6 @@ namespace Components // Intercept time wrapping Utils::Hook(0x62737D, TimeWrapStub, HOOK_CALL).install()->quick(); - //Utils::Hook::Set(0x62735C, 50'000); // Time wrap after 50 seconds (for testing - i don't want to wait 3 weeks) if (!ZoneBuilder::IsEnabled()) { diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index eee8c80d..282f6e86 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -5,6 +5,7 @@ #include "Download.hpp" #include "Events.hpp" #include "MapRotation.hpp" +#include "Node.hpp" #include "Party.hpp" #include "ServerInfo.hpp" @@ -40,7 +41,7 @@ namespace Components void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map) { - if (CLDownload.running) return; + if (CLDownload.running_) return; Scheduler::Once([] { @@ -61,26 +62,26 @@ namespace Components 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.isMap = map; - CLDownload.mod = mod; - CLDownload.terminateThread = false; - CLDownload.totalBytes = 0; - CLDownload.lastTimeStamp = 0; - CLDownload.downBytes = 0; - CLDownload.timeStampBytes = 0; - CLDownload.isPrivate = needPassword; - CLDownload.target = Party::Target(); - CLDownload.thread = std::thread(ModDownloader, &CLDownload); + CLDownload.running_ = true; + CLDownload.isMap_ = map; + CLDownload.mod_ = mod; + CLDownload.terminateThread_ = false; + CLDownload.totalBytes_ = 0; + CLDownload.lastTimeStamp_ = 0; + CLDownload.downBytes_ = 0; + CLDownload.timeStampBytes_ = 0; + CLDownload.isPrivate_ = needPassword; + CLDownload.target_ = Party::Target(); + CLDownload.thread_ = std::thread(ModDownloader, &CLDownload); } bool Download::ParseModList(ClientDownload* download, const std::string& list) { if (!download) return false; - download->files.clear(); + download->files_.clear(); nlohmann::json listData; try @@ -98,7 +99,7 @@ namespace Components return false; } - download->totalBytes = 0; + download->totalBytes_ = 0; const nlohmann::json::array_t listDataArray = listData; for (auto& file : listDataArray) @@ -118,8 +119,8 @@ namespace Components if (!fileEntry.name.empty()) { - download->files.push_back(fileEntry); - download->totalBytes += fileEntry.size; + download->files_.push_back(fileEntry); + download->totalBytes_ += fileEntry.size; } } catch (const nlohmann::json::exception& ex) @@ -134,12 +135,12 @@ namespace Components 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; - if (download->isMap) + auto path = download->mod_ + "/" + file.name; + if (download->isMap_) { path = "usermaps/" + path; } @@ -149,16 +150,16 @@ namespace Components auto data = Utils::IO::ReadFile(path); 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; } } - auto host = "http://" + download->target.getString(); + auto host = "http://" + download->target_.getString(); auto fastHost = SV_wwwBaseUrl.get(); if (Utils::String::StartsWith(fastHost, "https://")) { - download->thread.detach(); + download->thread_.detach(); download->clear(); Scheduler::Once([] @@ -197,8 +198,8 @@ namespace Components } else { - url = host + "/file/" + (download->isMap ? "map/" : "") + file.name - + (download->isPrivate ? ("?password=" + download->hashedPassword) : ""); + url = host + "/file/" + (download->isMap_ ? "map/" : "") + file.name + + (download->isPrivate_ ? ("?password=" + download->hashedPassword_) : ""); } Logger::Print("Downloading from url {}\n", url); @@ -212,14 +213,14 @@ namespace Components Utils::String::Replace(url, " ", "%20"); - download->valid = true; + download->valid_ = true; fDownload.downloading = true; Utils::WebIO webIO; 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(); return; @@ -234,14 +235,14 @@ namespace Components fDownload.downloading = false; - download->valid = false; + download->valid_ = false; if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash) { 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); return true; @@ -251,16 +252,16 @@ namespace Components { 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(); if (list.empty()) { - if (download->terminateThread) return; + if (download->terminateThread_) return; - download->thread.detach(); + download->thread_.detach(); download->clear(); Scheduler::Once([] @@ -272,13 +273,13 @@ namespace Components return; } - if (download->terminateThread) return; + if (download->terminateThread_) return; if (!ParseModList(download, list)) { - if (download->terminateThread) return; + if (download->terminateThread_) return; - download->thread.detach(); + download->thread_.detach(); download->clear(); Scheduler::Once([] @@ -290,21 +291,21 @@ namespace Components return; } - if (download->terminateThread) return; + if (download->terminateThread_) return; 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 (download->terminateThread) return; + if (download->terminateThread_) return; - mod = std::format("Failed to download file: {}!", download->files[i].name); - download->thread.detach(); + mod = std::format("Failed to download file: {}!", download->files_[i].name); + download->thread_.detach(); download->clear(); Scheduler::Once([] @@ -320,12 +321,12 @@ namespace Components } } - if (download->terminateThread) return; + if (download->terminateThread_) return; - download->thread.detach(); + download->thread_.detach(); download->clear(); - if (download->isMap) + if (download->isMap_) { Scheduler::Once([] { @@ -357,22 +358,22 @@ namespace Components void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes) { fDownload->receivedBytes += bytes; - fDownload->download->downBytes += bytes; - fDownload->download->timeStampBytes += bytes; + fDownload->download->downBytes_ += bytes; + fDownload->download->timeStampBytes_ += bytes; static volatile bool framePushed = false; if (!framePushed) { 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; dlIndex = fDownload->index + 1; - dlSize = fDownload->download->files.size(); + dlSize = fDownload->download->files_.size(); dlProgress = static_cast(progress); framePushed = true; @@ -383,18 +384,18 @@ namespace Components }, Scheduler::Pipeline::MAIN); } - auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp; + auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp_; if (delta > 300) { - const auto doFormat = fDownload->download->lastTimeStamp != 0; - fDownload->download->lastTimeStamp = Game::Sys_Milliseconds(); + const auto doFormat = fDownload->download->lastTimeStamp_ != 0; + 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; - 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(timeLeftD); } @@ -404,7 +405,7 @@ namespace Components static int dlDelta, dlTimeLeft; dlTimeLeft = timeLeft; dlDelta = delta; - dlTsBytes = fDownload->download->timeStampBytes; + dlTsBytes = fDownload->download->timeStampBytes_; Scheduler::Once([] { @@ -413,7 +414,7 @@ namespace Components }, Scheduler::Pipeline::MAIN); } - fDownload->download->timeStampBytes = 0; + fDownload->download->timeStampBytes_ = 0; } } @@ -434,7 +435,7 @@ namespace Components MongooseLogBuffer.push_back(c); } - static std::string InfoHandler() + static std::optional InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { const auto status = ServerInfo::GetInfo(); const auto host = ServerInfo::GetHostInfo(); @@ -480,10 +481,12 @@ namespace Components } info["players"] = players; - return nlohmann::json(info).dump(); + std::string out = nlohmann::json(info).dump(); + + return { out }; } - static std::string ListHandler() + static std::optional ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { static nlohmann::json jsonList; static std::filesystem::path fsGamePre; @@ -526,10 +529,12 @@ namespace Components jsonList = fileList; } - return jsonList.dump(); + std::string out = jsonList.dump(); + + return { out }; } - static std::string MapHandler() + static std::optional MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { static std::string mapNamePre; static nlohmann::json jsonList; @@ -570,10 +575,12 @@ namespace Components 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 FileHandler(mg_connection* c, const mg_http_message* hm) { std::string url(hm->uri.ptr, hm->uri.len); @@ -602,7 +609,7 @@ namespace Components if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile) { mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden"); - return; + return {}; } 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) { 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_send(c, file.data(), file.size()); } + + return {}; + } + + static std::optional ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) + { + std::vector 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) { + using callback = std::function(mg_connection*, const mg_http_message*)>; + + static const auto handlers = []() -> std::unordered_map + { + std::unordered_map f; + + f["/file"] = FileHandler; + f["/info"] = InfoHandler; + f["/list"] = ListHandler; + f["/map"] = MapHandler; + f["/serverlist"] = ServerListHandler; + + return f; + }(); + if (ev != MG_EV_HTTP_MSG) { return; @@ -645,26 +686,24 @@ namespace Components auto* hm = static_cast(ev_data); 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(); - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data()); + if (url.starts_with(i->first)) + { + 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")) - { - 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 + + if (!handled) { mg_http_serve_opts opts = { .root_dir = "iw4x/html" }; // Serve local dir mg_http_serve_dir(c, hm, &opts); diff --git a/src/Components/Modules/Download.hpp b/src/Components/Modules/Download.hpp index fe8ab847..84ee7584 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -24,24 +24,24 @@ namespace Components class ClientDownload { 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(); } - bool running; - bool valid; - bool terminateThread; - bool isMap; - bool isPrivate; - Network::Address target; - std::string hashedPassword; - std::string mod; - std::thread thread; + bool running_; + bool valid_; + bool terminateThread_; + bool isMap_; + bool isPrivate_; + Network::Address target_; + std::string hashedPassword_; + std::string mod_; + std::thread thread_; - std::size_t totalBytes; - std::size_t downBytes; + std::size_t totalBytes_; + std::size_t downBytes_; - int lastTimeStamp; - std::size_t timeStampBytes; + int lastTimeStamp_; + std::size_t timeStampBytes_; class File { @@ -51,24 +51,24 @@ namespace Components std::size_t size; }; - std::vector files; + std::vector files_; 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->mod.clear(); - this->files.clear(); + this->running_ = false; + this->mod_.clear(); + this->files_.clear(); - if (this->valid) + if (this->valid_) { - this->valid = false; + this->valid_ = false; } } }; diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 46ff7ff4..aeb62f5e 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -767,16 +767,7 @@ namespace Components UIScript::Add("downloadDLC", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { - int dlc = token.get(); - - 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; - } - } + const auto dlc = token.get(); Game::ShowMessageBox(Utils::String::VA("DLC %d does not exist!", dlc), "ERROR"); }); diff --git a/src/Components/Modules/News.hpp b/src/Components/Modules/News.hpp index a69aa27d..9170dcf6 100644 --- a/src/Components/Modules/News.hpp +++ b/src/Components/Modules/News.hpp @@ -14,7 +14,6 @@ namespace Components static std::thread Thread; static bool Terminate; - static bool DownloadUpdater(); static const char* GetNewsText(); }; diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 3fa2217a..4f170bb4 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -15,6 +15,8 @@ namespace Components bool Node::WasIngame = false; + const Game::dvar_t* Node::net_natFix; + bool Node::Entry::isValid() const { return (this->lastResponse.has_value() && !this->lastResponse->elapsed(NODE_HALFLIFE * 2)); @@ -48,7 +50,7 @@ namespace Components this->lastRequest->update(); Session::Send(this->address, "nodeListRequest"); - Node::SendList(this->address); + SendList(this->address); #ifdef NODE_SYSTEM_DEBUG Logger::Debug("Sent request to {}", this->address.getString()); #endif @@ -56,7 +58,6 @@ namespace Components void Node::Entry::reset() { - // this->lastResponse.reset(); // This would invalidate the node, but maybe we don't want that? this->lastRequest.reset(); } @@ -69,28 +70,54 @@ namespace Components 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)) { - Node::Add(reinterpret_cast(const_cast(addr.data()))); + Add(reinterpret_cast(const_cast(addr.data()))); } } } void Node::LoadNodes() { - Proto::Node::List list; - std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); - if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return; - - for (int i = 0; i < list.nodes_size(); ++i) + std::string data; + if (!Utils::IO::ReadFile("players/nodes.json", &data) || data.empty()) { - 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(const_cast(addr.data()))); + Network::Address address(entry.get()); + Add(address); } } } @@ -99,29 +126,32 @@ namespace Components { if (Dedicated::IsEnabled() && Dedicated::SVLanOnly.get()) return; + std::vector nodes; + static Utils::Time::Interval interval; if (!force && !interval.elapsed(1min)) return; interval.update(); - Proto::Node::List list; + Mutex.lock(); - Node::Mutex.lock(); - for (auto& node : Node::Nodes) + for (auto& node : Nodes) { - if (node.isValid()) + if (node.isValid() || force) { - std::string* str = list.add_nodes(); - - sockaddr addr = node.address.getSockAddr(); - str->append(reinterpret_cast(&addr), sizeof(addr)); + const auto address = node.address.getString(); + nodes.emplace_back(address); } } - 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 if (address.isLocal() || address.isSelf()) return; @@ -129,23 +159,23 @@ namespace Components if (!address.isValid()) return; - std::lock_guard _(Node::Mutex); - for (auto& session : Node::Nodes) + std::lock_guard _(Mutex); + for (auto& session : Nodes) { if (session.address == address) return; } - Node::Entry node; + Entry node; node.address = address; - Node::Nodes.push_back(node); + Nodes.push_back(node); } std::vector Node::GetNodes() { - std::lock_guard _(Node::Mutex); + std::lock_guard _(Mutex); - return Node::Nodes; + return Nodes; } 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 { - for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();++i) + for (auto& entry : Nodes) { // clearing the last request and response times makes the // dispatcher think its a new node and will force a refresh - i->lastRequest.reset(); - i->lastResponse.reset(); + entry.lastRequest.reset(); + entry.lastResponse.reset(); } WasIngame = false; } static Utils::Time::Interval frameLimit; - int interval = static_cast(1000.0f / Dvar::Var("net_serverFrames").get()); + const auto interval = 1000 / ServerList::NETServerFrames.get(); if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return; frameLimit.update(); - std::lock_guard _(Node::Mutex); - Dvar::Var queryLimit("net_serverQueryLimit"); + std::lock_guard _(Mutex); int sentRequests = 0; - for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();) + for (auto i = Nodes.begin(); i != Nodes.end();) { if (i->isDead()) { - i = Node::Nodes.erase(i); + i = Nodes.erase(i); continue; } - if (sentRequests < queryLimit.get() && i->requiresRequest()) + + if (sentRequests < ServerList::NETServerQueryLimit.get() && i->requiresRequest()) { ++sentRequests; i->sendRequest(); @@ -204,17 +234,16 @@ namespace Components void Node::Synchronize() { - std::lock_guard _(Node::Mutex); - for (auto& node : Node::Nodes) + std::lock_guard _(Mutex); + for (auto& node : Nodes) { - //if (node.isValid()) // Comment out to simulate 'syncnodes' behaviour { 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; if (!list.ParseFromString(data)) return; @@ -223,7 +252,7 @@ namespace Components Logger::Debug("Received response from {}", address.getString()); #endif - std::lock_guard _(Node::Mutex); + std::lock_guard _(Mutex); for (int i = 0; i < list.nodes_size(); ++i) { @@ -231,7 +260,7 @@ namespace Components if (addr.size() == sizeof(sockaddr)) { - Node::Add(reinterpret_cast(const_cast(addr.data()))); + Add(reinterpret_cast(const_cast(addr.data()))); } } @@ -251,7 +280,7 @@ namespace Components #endif } - for (auto& node : Node::Nodes) + for (auto& node : Nodes) { if (address == node.address) { @@ -263,39 +292,41 @@ namespace Components } } - Node::Entry entry; + Entry entry; entry.address = address; entry.data.protocol = list.protocol(); entry.lastResponse.emplace(Utils::Time::Point()); - Node::Nodes.push_back(entry); + Nodes.push_back(entry); } } 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 std::vector nodeListReponseMessages; - for (std::size_t curNode = 0; curNode < Node::Nodes.size();) + for (std::size_t curNode = 0; curNode < Nodes.size();) { Proto::Node::List list; list.set_isnode(Dedicated::IsEnabled()); 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;) { - if (curNode >= Node::Nodes.size()) + if (curNode >= Nodes.size()) + { break; + } - auto node = Node::Nodes.at(curNode++); + auto& node = Nodes.at(curNode++); if (node.isValid()) { - std::string* str = list.add_nodes(); + auto* str = list.add_nodes(); sockaddr addr = node.address.getSockAddr(); str->append(reinterpret_cast(&addr), sizeof(addr)); @@ -320,65 +351,100 @@ namespace Components } } - unsigned short Node::GetPort() + std::uint16_t Node::GetPort() { - if (Dvar::Var("net_natFix").get()) return 0; + if (net_natFix->current.enabled) return 0; 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 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(const_cast(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() { - if (ZoneBuilder::IsEnabled()) return; - Dvar::Register("net_natFix", false, 0, "Fix node registration for certain firewalls/routers"); + net_natFix = Game::Dvar_RegisterBool("net_natFix", false, 0, "Fix node registration for certain firewalls/routers"); Scheduler::Loop([] { - Node::StoreNodes(false); - }, Scheduler::Pipeline::ASYNC); + StoreNodes(false); + }, 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) { - Node::SendList(address); + SendList(address); }); - // Load stored nodes - auto loadNodes = [] + Scheduler::OnGameInitialized([] { - Node::LoadNodePreset(); - Node::LoadNodes(); - }; + Migrate(); + 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); - for (auto& node : Node::Nodes) + std::lock_guard _(Mutex); + for (const auto& node : Nodes) { 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; auto address = Network::Address{ params->get(1) }; if (address.isValid()) { - Node::Add(address); + Add(address); } }); } - Node::~Node() + void Node::preDestroy() { - std::lock_guard _(Node::Mutex); - Node::StoreNodes(true); - Node::Nodes.clear(); + std::lock_guard _(Mutex); + StoreNodes(true); + Nodes.clear(); } } diff --git a/src/Components/Modules/Node.hpp b/src/Components/Modules/Node.hpp index b585df3f..d1e72a2e 100644 --- a/src/Components/Modules/Node.hpp +++ b/src/Components/Modules/Node.hpp @@ -1,6 +1,6 @@ #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_SEND_RATE 500ms @@ -12,7 +12,7 @@ namespace Components class Data { public: - uint64_t protocol; + std::uint64_t protocol; }; class Entry @@ -34,9 +34,9 @@ namespace Components }; Node(); - ~Node(); + void preDestroy() override; - static void Add(Network::Address address); + static void Add(const Network::Address& address); static std::vector GetNodes(); static void RunFrame(); static void Synchronize(); @@ -46,7 +46,9 @@ namespace Components static std::vector Nodes; 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); @@ -54,6 +56,8 @@ namespace Components static void LoadNodes(); static void StoreNodes(bool force); - static unsigned short GetPort(); + static std::uint16_t GetPort(); + + static void Migrate(); }; } diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 8ea1cafb..fb3a1561 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -245,10 +245,10 @@ namespace Components } 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()); - Utils::Library::LaunchProcess(binary.string(), "-singleplayer", workingDir); + SetEnvironmentVariableA("MW2_INSTALL", workingDir.data()); + Utils::Library::LaunchProcess(binary, "-singleplayer", workingDir); } __declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub() @@ -320,7 +320,7 @@ namespace Components Utils::Hook::Set(0x51FCDD, QuickPatch::R_AddImageToList_Hk); - Utils::Hook::Set(0x41DB8C, "iw4x-sp.exe"); + Utils::Hook::Set(0x41DB8C, "iw4-sp.exe"); Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick(); // Fix crash as nullptr goes unchecked diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 824ead48..5a7b5c3c 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -127,10 +127,10 @@ namespace Components { Utils::InfoString info; - info.set("admin", Dvar::Var("_Admin").get()); - info.set("website", Dvar::Var("_Website").get()); - info.set("email", Dvar::Var("_Email").get()); - info.set("location", Dvar::Var("_Location").get()); + info.set("admin", Dvar::Var("_Admin").get()); + info.set("website", Dvar::Var("_Website").get()); + info.set("email", Dvar::Var("_Email").get()); + info.set("location", Dvar::Var("_Location").get()); return info; } diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index a0ebfc28..a3820f14 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -54,6 +54,11 @@ namespace Components static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address); static bool UseMasterServer; + static Dvar::Var UIServerSelected; + static Dvar::Var UIServerSelectedMap; + static Dvar::Var NETServerQueryLimit; + static Dvar::Var NETServerFrames; + private: enum class Column : int { @@ -150,11 +155,6 @@ namespace Components static std::vector VisibleList; - static Dvar::Var UIServerSelected; - static Dvar::Var UIServerSelectedMap; - static Dvar::Var NETServerQueryLimit; - static Dvar::Var NETServerFrames; - static bool IsServerListOpen(); }; } diff --git a/src/Components/Modules/StructuredData.cpp b/src/Components/Modules/StructuredData.cpp index c3a2e6ff..7036559e 100644 --- a/src/Components/Modules/StructuredData.cpp +++ b/src/Components/Modules/StructuredData.cpp @@ -149,159 +149,10 @@ namespace Components { if (Dedicated::IsEnabled()) return; - // Do not execute this when building zones - if (!ZoneBuilder::IsEnabled()) - { - // Correctly upgrade stats - 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 - Utils::Hook::Set(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>> patchDefinitions; - std::unordered_map> 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> enumContainer; - std::unordered_map 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 entryData; - - if (enumData.is_array()) - { - for (const auto& rawEntry : enumData) - { - if (rawEntry.is_string()) - { - entryData.push_back(rawEntry.get()); - } - } - } - - 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(); - } - } - } - - patchDefinitions[i] = enumContainer; - otherPatchDefinitions[i] = otherPatches; - } - - // Nothing to patch - if (patchDefinitions.empty()) return; - - // Reallocate the definition - auto* newData = StructuredData::MemAllocator.allocateArray(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(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(pType), patchData[pType]); - } - } - - StructuredData::PatchAdditionalData(&newData[i], otherData); - } - } - }); + // 15 or more custom classes + Utils::Hook::Set(0x60A2FE, NUM_CUSTOM_CLASSES); } } diff --git a/src/Resource.rc b/src/Resource.rc index 8cbfe655..fddc1f89 100644 --- a/src/Resource.rc +++ b/src/Resource.rc @@ -63,7 +63,7 @@ BEGIN BEGIN BLOCK "040904b0" BEGIN - VALUE "CompanyName", "XLabsProject" + VALUE "CompanyName", "IW4x" #ifdef _DEBUG VALUE "FileDescription", "IW4 client modification (DEBUG)" #else @@ -71,7 +71,7 @@ BEGIN #endif VALUE "FileVersion", REVISION_STR 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 "ProductName", "IW4x" VALUE "ProductVersion", REVISION_STR diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 59bbb4ea..03aad46b 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -143,6 +143,12 @@ using namespace std::literals; #define BASEGAME_NAME "iw4mp_ceg.exe" #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 #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS diff --git a/src/Steam/Interfaces/SteamUser.cpp b/src/Steam/Interfaces/SteamUser.cpp index 46b42887..d040b3d4 100644 --- a/src/Steam/Interfaces/SteamUser.cpp +++ b/src/Steam/Interfaces/SteamUser.cpp @@ -23,11 +23,7 @@ namespace Steam if (!idBits) { - if (Components::ZoneBuilder::IsEnabled()) - { - idBits = *reinterpret_cast(const_cast("DEDICATE")); - } - else if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid + if (Components::Singleton::IsFirstInstance() && !Components::Dedicated::IsEnabled()) // ECDSA guid { idBits = Components::Auth::GetKeyHash(); } diff --git a/src/Steam/Proxy.cpp b/src/Steam/Proxy.cpp index 9d546d20..c4710b4a 100644 --- a/src/Steam/Proxy.cpp +++ b/src/Steam/Proxy.cpp @@ -132,7 +132,7 @@ namespace Steam 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("SteamGameId", ::Utils::String::VA("%llu", Proxy::AppId & 0xFFFFFF)); @@ -146,7 +146,7 @@ namespace Steam 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)) { @@ -382,7 +382,7 @@ namespace Steam SetDllDirectoryA(directoy.data()); - if (!Components::Dedicated::IsEnabled() && !Components::ZoneBuilder::IsEnabled()) + if (!Components::Dedicated::IsEnabled()) { Proxy::LaunchWatchGuard(); diff --git a/src/Steam/Steam.cpp b/src/Steam/Steam.cpp index 37ec9361..e55b8c1f 100644 --- a/src/Steam/Steam.cpp +++ b/src/Steam/Steam.cpp @@ -130,7 +130,7 @@ namespace Steam } else { - Proxy::SetMod("IW4x: Modern Warfare 2"); + Proxy::SetMod("IW4y: Modern Warfare 2"); Proxy::RunGame(); } diff --git a/src/Utils/Cache.cpp b/src/Utils/Cache.cpp index b3240757..d7e6d7dc 100644 --- a/src/Utils/Cache.cpp +++ b/src/Utils/Cache.cpp @@ -5,8 +5,8 @@ namespace Utils { const char* Cache::Urls[] = { - "https://raw.githubusercontent.com/XLabsProject/iw4x-client", - "https://xlabs.dev", + "https://raw.githubusercontent.com/diamante0018/iw4x-client", + "https://alterware.dev", }; std::string Cache::ValidUrl; diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index 66b791a2..d07525ff 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -61,14 +61,14 @@ namespace Utils { if (!key.isValid()) return {}; - std::uint8_t buffer[512]; + std::uint8_t buffer[512]{}; unsigned long length = sizeof(buffer); ltc_mp = ltm_desc; register_prng(&sprng_desc); ecc_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, nullptr, find_prng("sprng"), key.getKeyPtr()); - return std::string{ reinterpret_cast(buffer), length }; + return std::string{ reinterpret_cast(buffer), length }; } bool ECC::VerifyMessage(Key key, const std::string& message, const std::string& signature) @@ -92,7 +92,6 @@ namespace Utils Key key; register_prng(&sprng_desc); - register_hash(&sha1_desc); ltc_mp = ltm_desc; @@ -105,29 +104,37 @@ namespace Utils { if (!key.isValid()) return {}; - std::uint8_t buffer[512]; + std::uint8_t buffer[512]{}; unsigned long length = sizeof(buffer); - register_prng(&sprng_desc); - 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; - rsa_sign_hash(reinterpret_cast(message.data()), message.size(), buffer, &length, NULL, find_prng("sprng"), find_hash("sha1"), 0, key.getKeyPtr()); + rsa_sign_hash_ex(reinterpret_cast(hash.data()), hash.size(), + buffer, &length, LTC_PKCS_1_V1_5, nullptr, 0, hash_index, 0, key.getKeyPtr()); - return std::string{ reinterpret_cast(buffer), length }; + return std::string{ reinterpret_cast(buffer), length }; } bool RSA::VerifyMessage(Key key, const std::string& message, const std::string& signature) { 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; - int result = 0; - return (rsa_verify_hash(reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(message.data()), message.size(), find_hash("sha1"), 0, &result, key.getKeyPtr()) == CRYPT_OK && result != 0); + auto result = 0; + return (rsa_verify_hash_ex(reinterpret_cast(signature.data()), signature.size(), + reinterpret_cast(hash.data()), hash.size(), LTC_PKCS_1_V1_5, + hash_index, 0, &result, key.getKeyPtr()) == CRYPT_OK && result != 0); } #pragma endregion @@ -145,9 +152,9 @@ namespace Utils encData.resize(text.size()); symmetric_CBC cbc; - int des3 = find_cipher("3des"); + const auto des3 = find_cipher("3des"); - cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), key.size(), 0, &cbc); + cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), static_cast(key.size()), 0, &cbc); cbc_encrypt(reinterpret_cast(text.data()), reinterpret_cast(encData.data()), text.size(), &cbc); cbc_done(&cbc); @@ -160,9 +167,9 @@ namespace Utils decData.resize(data.size()); symmetric_CBC cbc; - int des3 = find_cipher("3des"); + const auto des3 = find_cipher("3des"); - cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), key.size(), 0, &cbc); + cbc_start(des3, reinterpret_cast(iv.data()), reinterpret_cast(key.data()), static_cast(key.size()), 0, &cbc); cbc_decrypt(reinterpret_cast(data.data()), reinterpret_cast(decData.data()), data.size(), &cbc); cbc_done(&cbc); @@ -269,20 +276,21 @@ namespace Utils #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()); } - 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) { hash += key[i]; hash += (hash << 10); hash ^= (hash >> 6); } + hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); diff --git a/src/Utils/Cryptography.hpp b/src/Utils/Cryptography.hpp index f3b48560..46ac5e14 100644 --- a/src/Utils/Cryptography.hpp +++ b/src/Utils/Cryptography.hpp @@ -9,16 +9,16 @@ namespace Utils class Token { public: - Token() { this->tokenString.clear(); }; - Token(const Token& obj) : tokenString(obj.tokenString) { }; - Token(const std::string& token) : tokenString(token.begin(), token.end()) { }; - Token(const std::basic_string& token) : tokenString(token.begin(), token.end()) { }; + Token() { this->tokenString.clear(); } + Token(const Token& obj) : tokenString(obj.tokenString) { } + Token(const std::string& token) : tokenString(token.begin(), token.end()) { } + Token(const std::basic_string& token) : tokenString(token.begin(), token.end()) { } Token& operator++ () { if (this->tokenString.empty()) { - this->tokenString.append(reinterpret_cast(const_cast("\0")), 1); + this->tokenString.append(reinterpret_cast(const_cast("\0")), 1); } else { @@ -30,8 +30,7 @@ namespace Utils 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(const_cast("\0")), 1) + this->tokenString; + this->tokenString = std::basic_string{ reinterpret_cast(const_cast("\0")), 1 } + this->tokenString; break; } } @@ -69,25 +68,25 @@ namespace Utils { return false; } - else if (this->toString().size() < token.toString().size()) + + if (this->toString().size() < token.toString().size()) { return true; } - else if (this->toString().size() > token.toString().size()) + + if (this->toString().size() > token.toString().size()) { 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); } - 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 - { - return std::string(this->tokenString.begin(), this->tokenString.end()); - } - - std::basic_string toUnsignedString() + [[nodiscard]] std::basic_string toUnsignedString() const { return this->tokenString; } @@ -137,7 +131,7 @@ namespace Utils { public: static std::string GenerateChallenge(); - static uint32_t GenerateInt(); + static std::uint32_t GenerateInt(); static void Initialize(); private: @@ -153,45 +147,47 @@ namespace Utils Key() : keyStorage(new ecc_key) { 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() { if (this->keyStorage.use_count() <= 1) { 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(); } - std::string getPublicKey() + [[nodiscard]] std::string getPublicKey() { - uint8_t buffer[512] = { 0 }; - DWORD length = sizeof(buffer); + std::uint8_t buffer[512]{}; + unsigned long length = sizeof(buffer); if (ecc_ansi_x963_export(this->getKeyPtr(), buffer, &length) == CRYPT_OK) { - return std::string(reinterpret_cast(buffer), length); + return std::string{ reinterpret_cast(buffer), length }; } - return ""; + return std::string{}; } void set(const std::string& pubKeyBuffer) { this->free(); - if (ecc_ansi_x963_import(reinterpret_cast(pubKeyBuffer.data()), pubKeyBuffer.size(), this->getKeyPtr()) != CRYPT_OK) + if (ecc_ansi_x963_import(reinterpret_cast(pubKeyBuffer.data()), pubKeyBuffer.size(), this->getKeyPtr()) != CRYPT_OK) { ZeroMemory(this->getKeyPtr(), sizeof(*this->getKeyPtr())); } @@ -201,23 +197,23 @@ namespace Utils { this->free(); - if (ecc_import(reinterpret_cast(key.data()), key.size(), this->getKeyPtr()) != CRYPT_OK) + if (ecc_import(reinterpret_cast(key.data()), key.size(), this->getKeyPtr()) != CRYPT_OK) { 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 }; - DWORD length = sizeof(buffer); + std::uint8_t buffer[4096]{}; + unsigned long length = sizeof(buffer); if (ecc_export(buffer, &length, type, this->getKeyPtr()) == CRYPT_OK) { - return std::string(reinterpret_cast(buffer), length); + return std::string{ reinterpret_cast(buffer), length }; } - return ""; + return std::string{}; } 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(); } - 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(buffer), length }; + } + + return std::string{}; + } + + void set(const std::string& keyBuffer) + { + this->free(); + + if (rsa_import(reinterpret_cast(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(buffer), length }; + } + + return std::string{}; } void free() @@ -334,8 +366,8 @@ namespace Utils class JenkinsOneAtATime { public: - static unsigned int Compute(const std::string& data); - static unsigned int Compute(const char* key, std::size_t len); + static std::size_t Compute(const std::string& data); + static std::size_t Compute(const char* key, std::size_t len); }; } } diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index 6b789189..a1066804 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -112,15 +112,15 @@ namespace Utils auto* exeBaseName = std::wcsrchr(binaryPath, L'\\'); exeBaseName[0] = L'\0'; - // Make the game work without the xlabs launcher + // Make the game work without the AlterWare launcher SetCurrentDirectoryW(binaryPath); } void SetEnvironment() { - wchar_t* buffer{}; + char* buffer{}; 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(); return; @@ -128,8 +128,8 @@ namespace Utils const auto _0 = gsl::finally([&] { std::free(buffer); }); - SetCurrentDirectoryW(buffer); - SetDllDirectoryW(buffer); + SetCurrentDirectoryA(buffer); + SetDllDirectoryA(buffer); } HMODULE GetNTDLL()