diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..e962961e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,119 @@ +name: Build + +on: + push: + branches: + - "*" + pull_request: + branches: + - "*" + types: [opened, synchronize, reopened] + +jobs: + build: + name: Build binaries + runs-on: windows-latest + strategy: + matrix: + configuration: + - Debug + - Release + steps: + - name: Wait for previous workflows + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: softprops/turnstyle@v1 + with: + poll-interval-seconds: 10 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check out files + uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 0 + # NOTE - If LFS ever starts getting used during builds, switch this to true! + lfs: false + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Generate project files + #run: tools/premake5 vs2019 --ci-build + run: tools/premake5 vs2019 --ac-disable + + - name: Set up problem matching + uses: ammaraskar/msvc-problem-matcher@master + + - name: Build ${{matrix.configuration}} binaries + run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} build/iw4x.sln + + - name: Upload ${{matrix.configuration}} binaries + uses: actions/upload-artifact@v2 + with: + name: ${{matrix.configuration}} binaries + path: | + build/bin/${{matrix.configuration}}/iw4x.dll + build/bin/${{matrix.configuration}}/iw4x.pdb + +# - name: Upload ${{matrix.configuration}} data artifacts +# uses: actions/upload-artifact@v2 +# with: +# name: ${{matrix.configuration}} data artifacts +# path: | +# data/* + + + deploy: + name: Deploy artifacts + needs: build + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || 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 + + - name: Download Release binaries + uses: actions/download-artifact@v2 + with: + name: Release binaries + +# - name: Download Release data artifacts +# uses: actions/download-artifact@v2 +# with: +# name: Release data artifacts +# path: data + + # Set up committer info and GPG key + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.XLABS_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: Wait for previous workflows + uses: softprops/turnstyle@v1 + with: + poll-interval-seconds: 10 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +# - 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 + 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 }} \ No newline at end of file diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml new file mode 100644 index 00000000..35f0f8cc --- /dev/null +++ b/.github/workflows/draft-new-release.yml @@ -0,0 +1,48 @@ +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@v2 + + - name: Normalize version + id: normalize_version + run: | + version="${{ github.event.inputs.version }}" + version="v${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 new file mode 100644 index 00000000..70fbf24c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,83 @@ +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@v2 + 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/README.md b/README.md index fe82b30b..f04e11dc 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,13 @@ ![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) [![build status](https://ci.appveyor.com/api/projects/status/rvljq0ooxen0oexm/branch/develop?svg=true)](https://ci.appveyor.com/project/iw4x/iw4x-client/branch/develop) [![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 -## Commit message style - -``` -[Module] Imperative summary - -- points or text - -[ci skip] -``` - -`[ci skip]` is optional. - ## How to compile - Run `premake5 vs2019` or use the delivered `generate.bat`. diff --git a/deps/libtommath b/deps/libtommath index 8355b88d..2cec6add 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 8355b88db088e41d6f7e19a8d58d46c9ed0333d3 +Subproject commit 2cec6addaf4acbd8250611ec4cecadf1f4d655ee diff --git a/deps/pdcurses b/deps/pdcurses index 4bc97e28..6c31f226 160000 --- a/deps/pdcurses +++ b/deps/pdcurses @@ -1 +1 @@ -Subproject commit 4bc97e287be5d8927405614c193fd0a039df027a +Subproject commit 6c31f226922bd8653e7ed40cd4b11cee9845ea97 diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 7b76cb02..3217dcd4 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -104,6 +104,8 @@ namespace Components Loader::Register(new Gamepad()); Loader::Register(new Chat()); Loader::Register(new TextRenderer()); + Loader::Register(new Movement()); + Loader::Register(new Elevators()); Loader::Register(new Client()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 2265b729..f9d1e6ed 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -132,6 +132,8 @@ namespace Components #include "Modules/SoundMutexFix.hpp" #include "Modules/Chat.hpp" #include "Modules/TextRenderer.hpp" +#include "Modules/Movement.hpp" +#include "Modules/Elevators.hpp" #include "Modules/Gamepad.hpp" #include "Modules/Client.hpp" diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 7e0f16ba..15e36cfb 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -160,7 +160,7 @@ namespace Components { Script::AddFunction("SetPing", [](Game::scr_entref_t id) // gsc: self SetPing() { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_INTEGER) { Game::Scr_Error("^1SetPing: Needs one integer parameter!\n"); return; @@ -250,7 +250,7 @@ namespace Components Script::AddFunction("botWeapon", [](Game::scr_entref_t id) // Usage: botWeapon(); { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1botWeapon: Needs one string parameter!\n"); return; @@ -293,7 +293,7 @@ namespace Components Script::AddFunction("botAction", [](Game::scr_entref_t id) // Usage: botAction(); { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1botAction: Needs one string parameter!\n"); return; @@ -346,7 +346,7 @@ namespace Components Script::AddFunction("botMovement", [](Game::scr_entref_t id) // Usage: botMovement(, ); { - if (Game::Scr_GetNumParam() != 2 || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER) { Game::Scr_Error("^1botMovement: Needs two integer parameters!\n"); return; diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 7ff6330c..dc856792 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -3,6 +3,7 @@ namespace Components { SteamID Dedicated::PlayerGuids[18][2]; + Dvar::Var Dedicated::SVRandomMapRotation; bool Dedicated::IsEnabled() { @@ -96,7 +97,7 @@ namespace Components Game::SV_GameSendServerCommand(-1, 0, list.data()); } - void Dedicated::TimeWrapStub(int code, const char* message) + void Dedicated::TimeWrapStub(Game::errorParm_t code, const char* message) { static bool partyEnable; static std::string mapname; @@ -119,6 +120,41 @@ namespace Components Game::Com_Error(code, message); } + void Dedicated::RandomizeMapRotation() + { + auto rotation = Dvar::Var("sv_mapRotation").get(); + + const auto tokens = Utils::String::Explode(rotation, ' '); + std::vector> mapRotationPair; + + for (auto i = 0u; i < (tokens.size() - 1); i += 2) + { + if (i + 1 >= tokens.size()) break; + + const auto& key = tokens[i]; + const auto& value = tokens[i + 1]; + mapRotationPair.push_back(std::make_pair(key, value)); + } + + const auto seed = Utils::Cryptography::Rand::GenerateInt(); + std::shuffle(std::begin(mapRotationPair), std::end(mapRotationPair), std::default_random_engine(seed)); + + // Rebuild map rotation using the randomized key/values + rotation.clear(); + for (auto j = 0u; j < mapRotationPair.size(); j++) + { + const auto& pair = mapRotationPair[j]; + rotation.append(pair.first); + rotation.append(" "); + rotation.append(pair.second); + + if (j != mapRotationPair.size() - 1) + rotation.append(" "); + } + + Dvar::Var("sv_mapRotationCurrent").set(rotation); + } + void Dedicated::MapRotate() { if (!Dedicated::IsEnabled() && Dvar::Var("sv_dontrotate").get()) @@ -134,9 +170,10 @@ namespace Components } Logger::Print("Rotating map...\n"); + const auto mapRotation = Dvar::Var("sv_mapRotation").get(); // if nothing, just restart - if (Dvar::Var("sv_mapRotation").get().empty()) + if (mapRotation.empty()) { Logger::Print("No rotation defined, restarting map.\n"); @@ -152,14 +189,23 @@ namespace Components return; } - // first, check if the string contains nothing + // First, check if the string contains nothing if (Dvar::Var("sv_mapRotationCurrent").get().empty()) { Logger::Print("Current map rotation has finished, reloading...\n"); - Dvar::Var("sv_mapRotationCurrent").set(Dvar::Var("sv_mapRotation").get()); + + if (Dedicated::SVRandomMapRotation.get()) + { + Logger::Print("Randomizing map rotation\n"); + Dedicated::RandomizeMapRotation(); + } + else + { + Dvar::Var("sv_mapRotationCurrent").set(mapRotation); + } } - std::string rotation = Dvar::Var("sv_mapRotationCurrent").get(); + auto rotation = Dvar::Var("sv_mapRotationCurrent").get(); auto tokens = Utils::String::Explode(rotation, ' '); @@ -346,6 +392,7 @@ namespace Components Dvar::OnInit([]() { + Dedicated::SVRandomMapRotation = Dvar::Register("sv_randomMapRotation", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Randomize map rotation when true"); Dvar::Register("sv_sayName", "^7Console", Game::dvar_flag::DVAR_FLAG_NONE, "The name to pose as for 'say' commands"); Dvar::Register("sv_motd", "", Game::dvar_flag::DVAR_FLAG_NONE, "A custom message of the day for servers"); diff --git a/src/Components/Modules/Dedicated.hpp b/src/Components/Modules/Dedicated.hpp index 162e1a8f..9515870b 100644 --- a/src/Components/Modules/Dedicated.hpp +++ b/src/Components/Modules/Dedicated.hpp @@ -15,6 +15,9 @@ namespace Components static void Heartbeat(); private: + static Dvar::Var SVRandomMapRotation; + + static void RandomizeMapRotation(); static void MapRotate(); static void InitDedicatedServer(); @@ -25,6 +28,6 @@ namespace Components static void TransmitGuids(); - static void TimeWrapStub(int code, const char* message); + static void TimeWrapStub(Game::errorParm_t code, const char* message); }; } diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index d2bb9649..7d1d6f27 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -967,7 +967,7 @@ namespace Components Script::AddFunction("httpGet", [](Game::scr_entref_t) { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; - if (Game::Scr_GetNumParam() < 1) return; + if (Game::Scr_GetNumParam() < 1u) return; std::string url = Game::Scr_GetString(0); unsigned int object = Game::AllocObject(); @@ -981,7 +981,7 @@ namespace Components Script::AddFunction("httpCancel", [](Game::scr_entref_t) { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; - if (Game::Scr_GetNumParam() < 1) return; + if (Game::Scr_GetNumParam() < 1u) return; unsigned int object = Game::Scr_GetObject(0); for (auto& download : Download::ScriptDownloads) diff --git a/src/Components/Modules/Elevators.cpp b/src/Components/Modules/Elevators.cpp new file mode 100644 index 00000000..f297f48a --- /dev/null +++ b/src/Components/Modules/Elevators.cpp @@ -0,0 +1,55 @@ +#include "STDInclude.hpp" + +namespace Components +{ + Game::dvar_t* Elevators::SV_DisableElevators; + + __declspec(naked) void Elevators::PM_GroundTraceStub() + { + __asm + { + push eax + mov eax, Elevators::SV_DisableElevators + cmp byte ptr [eax + 16], 1 + pop eax + + // Always skip PM_CorrectAllSolid if SV_DisableElevators is set to 1 + je noElevators + + // Original code + cmp byte ptr [esp + 0x50], 0 + rep movsd + mov esi, [esp + 0x58] + + // Original code flow + push 0x573694 + retn + + noElevators: + + // Original code + rep movsd + mov esi, [esp + 0x58] + + // Jump over call to PM_CorrectAllSolid + push 0x5736AE + retn + } + } + + Elevators::Elevators() + { + Dvar::OnInit([] + { + Elevators::SV_DisableElevators = Game::Dvar_RegisterBool("sv_disableElevators", + false, Game::DVAR_FLAG_REPLICATED, "Disable elevators"); + }); + + // Hook PM_GroundTrace so me way skip PM_CorrectAllSolid (disable elevators) + Utils::Hook(0x573689, Elevators::PM_GroundTraceStub, HOOK_JUMP).install()->quick(); + } + + Elevators::~Elevators() + { + } +} diff --git a/src/Components/Modules/Elevators.hpp b/src/Components/Modules/Elevators.hpp new file mode 100644 index 00000000..73af502a --- /dev/null +++ b/src/Components/Modules/Elevators.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace Components +{ + class Elevators : public Component + { + public: + Elevators(); + ~Elevators(); + + private: + static Game::dvar_t* SV_DisableElevators; + + static void PM_GroundTraceStub(); + }; +} diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 5ba7a66b..c3555fc7 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -108,7 +108,7 @@ namespace Components {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, }; - Game::keyname_t Gamepad::extendedLocalizedKeyNames[] + Game::keyname_t Gamepad::extendedLocalizedKeyNamesXenon[] { // Material text icons pattern: 0x01 width height material_name_len {"^\x01\x32\x32\x08""button_a", Game::K_BUTTON_A}, @@ -128,8 +128,30 @@ namespace Components {"^\x01\x32\x32\x09""dpad_left", Game::K_DPAD_LEFT}, {"^\x01\x32\x32\x0A""dpad_right", Game::K_DPAD_RIGHT}, }; + + Game::keyname_t Gamepad::extendedLocalizedKeyNamesPs3[] + { + // Material text icons pattern: 0x01 width height material_name_len + {"^\x01\x32\x32\x10""button_ps3_cross", Game::K_BUTTON_A}, + {"^\x01\x32\x32\x11""button_ps3_circle", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x11""button_ps3_square", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x13""button_ps3_triangle", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_ps3_l1", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_ps3_r1", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x10""button_ps3_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0F""button_ps3_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_ps3_l3", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_ps3_r3", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0D""button_ps3_l2", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0D""button_ps3_r2", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x0B""dpad_ps3_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x0D""dpad_ps3_down", Game::K_DPAD_DOWN}, + {"^\x01\x32\x32\x0D""dpad_ps3_left", Game::K_DPAD_LEFT}, + {"^\x01\x32\x32\x0E""dpad_ps3_right", Game::K_DPAD_RIGHT}, + }; Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v + 1]; Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] { @@ -155,6 +177,7 @@ namespace Components Dvar::Var Gamepad::gpad_debug; Dvar::Var Gamepad::gpad_present; Dvar::Var Gamepad::gpad_in_use; + Dvar::Var Gamepad::gpad_style; Dvar::Var Gamepad::gpad_sticksConfig; Dvar::Var Gamepad::gpad_buttonConfig; Dvar::Var Gamepad::gpad_menu_scroll_delay_first; @@ -1694,6 +1717,7 @@ namespace Components gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_FLAG_NONE, "Game pad debugging"); gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_FLAG_NONE, "Game pad present"); gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_FLAG_NONE, "A game pad is in use"); + gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_FLAG_SAVED, "Switch between Xbox and PS HUD"); gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_FLAG_SAVED, "Game pad stick configuration"); gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_FLAG_SAVED, "Game pad button configuration"); gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_FLAG_SAVED, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); @@ -1830,21 +1854,53 @@ namespace Components return cl_bypassMouseInput.get() || IsGamePadInUse(); } + Game::keyname_t* Gamepad::GetLocalizedKeyNameMap() + { + if(gpad_style.get()) + return combinedLocalizedKeyNamesPs3; + + return combinedLocalizedKeyNamesXenon; + } + + void __declspec(naked) Gamepad::GetLocalizedKeyName_Stub() + { + __asm + { + push eax + pushad + + call GetLocalizedKeyNameMap + mov [esp + 0x20], eax + + popad + pop eax + + // Re-execute last instruction from game to set flags again for upcoming jump + test edi, edi + ret + } + } + void Gamepad::CreateKeyNameMap() { memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; - memcpy(combinedLocalizedKeyNames, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNames[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNames, - sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNames[std::extent_v - 1] = {nullptr, 0}; + memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; + + memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; Utils::Hook::Set(0x4A780A, combinedKeyNames); Utils::Hook::Set(0x4A7810, combinedKeyNames); Utils::Hook::Set(0x435C9F, combinedKeyNames); - Utils::Hook::Set(0x435C98, combinedLocalizedKeyNames); + Utils::Hook(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); } Gamepad::Gamepad() @@ -1870,6 +1926,9 @@ namespace Components Utils::Hook(0x60B264, Com_WriteConfiguration_Modified_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(0x60B223, Key_WriteBindings_Hk, HOOK_CALL).install()->quick(); + // Add hold time to gamepad usereload on hold prompts + Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); + CreateKeyNameMap(); Command::Add("bindaxis", Axis_Bind_f); @@ -1896,9 +1955,6 @@ namespace Components // Only return gamepad keys when gamepad enabled and only non gamepad keys when not Utils::Hook(0x5A7A23, Key_GetCommandAssignmentInternal_Hk, HOOK_CALL).install()->quick(); - // Add hold time to gamepad usereload on hold prompts - Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); - // Add gamepad inputs to remote control (eg predator) handling Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index 9808909f..5c55325c 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -51,9 +51,11 @@ namespace Components static const char* gamePadMappingTypeNames[]; static Game::keyNum_t menuScrollButtonList[]; static Game::keyname_t extendedKeyNames[]; - static Game::keyname_t extendedLocalizedKeyNames[]; + static Game::keyname_t extendedLocalizedKeyNamesXenon[]; + static Game::keyname_t extendedLocalizedKeyNamesPs3[]; static Game::keyname_t combinedKeyNames[]; - static Game::keyname_t combinedLocalizedKeyNames[]; + static Game::keyname_t combinedLocalizedKeyNamesXenon[]; + static Game::keyname_t combinedLocalizedKeyNamesPs3[]; static ControllerMenuKeyMapping controllerMenuKeyMappings[]; static GamePad gamePads[Game::MAX_GAMEPADS]; @@ -65,6 +67,7 @@ namespace Components static Dvar::Var gpad_debug; static Dvar::Var gpad_present; static Dvar::Var gpad_in_use; + static Dvar::Var gpad_style; static Dvar::Var gpad_sticksConfig; static Dvar::Var gpad_buttonConfig; static Dvar::Var gpad_menu_scroll_delay_first; @@ -192,6 +195,9 @@ namespace Components static void CL_KeyEvent_Hk(int localClientNum, int key, int down, unsigned int time); static int CL_MouseEvent_Hk(int x, int y, int dx, int dy); static bool UI_RefreshViewport_Hk(); + + static Game::keyname_t* GetLocalizedKeyNameMap(); + static void GetLocalizedKeyName_Stub(); static void CreateKeyNameMap(); }; } diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index 3c5b82a7..1bdb421c 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -181,6 +181,7 @@ namespace Components "a231", "AmateurHailbut", "Aoki", + "Chase", "civil", "Dasfonia", "Deity", diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index 22ad6e09..10a38fc9 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -51,7 +51,7 @@ namespace Components } } - void Logger::ErrorPrint(int error, const std::string& message) + void Logger::ErrorPrint(Game::errorParm_t error, const std::string& message) { #ifdef DEBUG if (IsDebuggerPresent()) __debugbreak(); @@ -60,19 +60,19 @@ namespace Components return Game::Com_Error(error, "%s", message.data()); } - void Logger::Error(int error, const char* message, ...) + void Logger::Error(Game::errorParm_t error, const char* message, ...) { return Logger::ErrorPrint(error, Logger::Format(&message)); } void Logger::Error(const char* message, ...) { - return Logger::ErrorPrint(0, Logger::Format(&message)); + return Logger::ErrorPrint(Game::ERR_FATAL, Logger::Format(&message)); } void Logger::SoftError(const char* message, ...) { - return Logger::ErrorPrint(2, Logger::Format(&message)); + return Logger::ErrorPrint(Game::ERR_SERVERDISCONNECT, Logger::Format(&message)); } std::string Logger::Format(const char** message) diff --git a/src/Components/Modules/Logger.hpp b/src/Components/Modules/Logger.hpp index 26625324..a6799b96 100644 --- a/src/Components/Modules/Logger.hpp +++ b/src/Components/Modules/Logger.hpp @@ -11,9 +11,9 @@ namespace Components static void MessagePrint(int channel, const std::string& message); static void Print(int channel, const char* message, ...); static void Print(const char* message, ...); - static void ErrorPrint(int error, const std::string& message); + static void ErrorPrint(Game::errorParm_t error, const std::string& message); static void Error(const char* message, ...); - static void Error(int error, const char* message, ...); + static void Error(Game::errorParm_t error, const char* message, ...); static void SoftError(const char* message, ...); static bool IsConsoleReady(); diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 012b1d41..97ec8f61 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -752,6 +752,7 @@ namespace Components int16 Maps::CM_TriggerModelBounds(int modelPointer, Game::Bounds* bounds) { #ifdef DEBUG Game::MapEnts* ents = *reinterpret_cast(0x1AA651C); // Use me for debugging + (void)ents; #endif return Utils::Hook::Call(0x4416C0)(modelPointer, bounds); } diff --git a/src/Components/Modules/Movement.cpp b/src/Components/Modules/Movement.cpp new file mode 100644 index 00000000..f71c9699 --- /dev/null +++ b/src/Components/Modules/Movement.cpp @@ -0,0 +1,161 @@ +#include "STDInclude.hpp" + +namespace Components +{ + Dvar::Var Movement::PlayerDuckedSpeedScale; + Dvar::Var Movement::PlayerLastStandCrawlSpeedScale; + Dvar::Var Movement::PlayerProneSpeedScale; + + int Movement::PMGetEffectiveStance(Game::playerState_s* ps) + { + auto heightTarget = ps->viewHeightTarget; + + if (heightTarget == 0x16) + return Game::PM_EFF_STANCE_LASTSTANDCRAWL; + + if (heightTarget == 0x28) + return Game::PM_EFF_STANCE_DUCKED; + + if (heightTarget == 0xB) + return Game::PM_EFF_STANCE_PRONE; + + return Game::PM_EFF_STANCE_DEFAULT; + } + + float Movement::PMCmdScaleForStance(Game::pmove_s* move) + { + auto* playerState = move->ps; + float scale; + + if (playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0xB) + { + scale = move->cmd.serverTime - playerState->viewHeightLerpTime / 400.0f; + + if (0.0f <= scale) + { + auto flags = 0; + + if (scale < 1.0f) + { + flags |= 1 << 8; + } + + if (scale == 1.0f) + { + flags |= 1 << 14; + } + + if (flags == 0) + { + scale = 1.0f; + return scale * 0.15f + (1.0f - scale) * 0.65f; + } + + if (scale != 0.0f) + { + return scale * 0.15f + (1.0f - scale) * 0.65f; + } + } + } + + if ((playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0x28) && + playerState->viewHeightLerpDown == 0) + { + scale = 400.0f / move->cmd.serverTime - playerState->viewHeightLerpTime; + + if (0.0f <= scale) + { + auto flags = 0; + + if (scale < 1.0f) + { + flags |= 1 << 8; + } + + if (scale == 1.0f) + { + flags |= 1 << 14; + } + + if (flags == 0) + { + scale = 1.0f; + } + else if (scale != 0.0f) + { + return scale * 0.65f + (1.0f - scale) * 0.15f; + } + } + } + + scale = 1.0f; + auto stance = Movement::PMGetEffectiveStance(playerState); + + if (stance == Game::PM_EFF_STANCE_PRONE) + { + scale = Movement::PlayerProneSpeedScale.get(); + } + + else if (stance == Game::PM_EFF_STANCE_DUCKED) + { + scale = Movement::PlayerDuckedSpeedScale.get(); + } + + else if (stance == Game::PM_EFF_STANCE_LASTSTANDCRAWL) + { + scale = Movement::PlayerLastStandCrawlSpeedScale.get(); + } + + return scale; + } + + __declspec(naked) void Movement::PMCmdScaleForStanceStub() + { + __asm + { + pushad + + push edx + call Movement::PMCmdScaleForStance + add esp, 4 + + popad + ret + } + } + + Game::dvar_t* Movement::Dvar_RegisterLastStandSpeedScale(const char* name, float defaultVal, float min, float max, int, const char* desc) + { + Movement::PlayerLastStandCrawlSpeedScale = Dvar::Register(name, defaultVal, + min, max, Game::DVAR_FLAG_CHEAT | Game::DVAR_FLAG_REPLICATED, desc); + + return Movement::PlayerLastStandCrawlSpeedScale.get(); + } + + Movement::Movement() + { + Dvar::OnInit([] + { + Movement::PlayerDuckedSpeedScale = Dvar::Register("player_duckedSpeedScale", + 0.65f, 0.0f, 5.0f, Game::DVAR_FLAG_CHEAT | Game::DVAR_FLAG_REPLICATED, + "The scale applied to the player speed when ducking"); + + Movement::PlayerProneSpeedScale = Dvar::Register("player_proneSpeedScale", + 0.15f, 0.0f, 5.0f, Game::DVAR_FLAG_CHEAT | Game::DVAR_FLAG_REPLICATED, + "The scale applied to the player speed when crawling"); + }); + + // Hook PM_CmdScaleForStance in PM_CmdScale_Walk + Utils::Hook(0x572F34, Movement::PMCmdScaleForStanceStub, HOOK_CALL).install()->quick(); + + //Hook PM_CmdScaleForStance in PM_GetMaxSpeed + Utils::Hook(0x57395F, Movement::PMCmdScaleForStanceStub, HOOK_CALL).install()->quick(); + + // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. + Utils::Hook(0x448B66, Movement::Dvar_RegisterLastStandSpeedScale, HOOK_CALL).install()->quick(); + } + + Movement::~Movement() + { + } +} diff --git a/src/Components/Modules/Movement.hpp b/src/Components/Modules/Movement.hpp new file mode 100644 index 00000000..07ad6894 --- /dev/null +++ b/src/Components/Modules/Movement.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace Components +{ + class Movement : public Component + { + public: + Movement(); + ~Movement(); + + private: + static Dvar::Var PlayerDuckedSpeedScale; + static Dvar::Var PlayerLastStandCrawlSpeedScale; + static Dvar::Var PlayerProneSpeedScale; + + static int PMGetEffectiveStance(Game::playerState_s* ps); + static float PMCmdScaleForStance(Game::pmove_s* move); + static void PMCmdScaleForStanceStub(); + + static Game::dvar_t* Dvar_RegisterLastStandSpeedScale(const char* name, float defaultVal, float min, float max, int flags, const char* desc); + }; +} diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 527f5b8b..4d7bc515 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -151,7 +151,7 @@ namespace Components Party::Party() { static Game::dvar_t* partyEnable = Dvar::Register("party_enable", Dedicated::IsEnabled(), Game::dvar_flag::DVAR_FLAG_NONE, "Enable party system").get(); - Dvar::Register("xblive_privatematch", true, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED, "").get(); + Dvar::Register("xblive_privatematch", true, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED, ""); // various changes to SV_DirectConnect-y stuff to allow non-party joinees Utils::Hook::Set(0x460D96, 0x90E9); diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 1ab8479b..59691749 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -3,6 +3,7 @@ namespace Components { int QuickPatch::FrameTime = 0; + Dvar::Var QuickPatch::r_customAspectRatio; void QuickPatch::UnlockStats() { @@ -139,16 +140,15 @@ namespace Components } } - bool QuickPatch::InvalidNameCheck(char *dest, char *source, int size) + bool QuickPatch::InvalidNameCheck(char* dest, const char* source, int size) { - strncpy(dest, source, size - 1); - dest[size - 1] = 0; + Utils::Hook::Call(0x4D6F80)(dest, source, size); // I_strncpyz for (int i = 0; i < size - 1; i++) { if (!dest[i]) break; - if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') + if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') { return false; } @@ -172,7 +172,7 @@ namespace Components push 1; push kick_reason; push edi; - mov eax, 0x004D1600; + mov eax, 0x004D1600; // SV_DropClientInternal call eax; add esp, 12; popad; @@ -184,7 +184,6 @@ namespace Components } Game::dvar_t* QuickPatch::g_antilag; - __declspec(naked) void QuickPatch::ClientEventsFireWeaponStub() { __asm @@ -268,30 +267,31 @@ namespace Components } } - Game::dvar_t* QuickPatch::r_customAspectRatio; Game::dvar_t* QuickPatch::Dvar_RegisterAspectRatioDvar(const char* name, char**, int defaultVal, int flags, const char* description) { - static std::vector < char * > values = + static const char* r_aspectRatioEnum[] = { - const_cast("auto"), - const_cast("standard"), - const_cast("wide 16:10"), - const_cast("wide 16:9"), - const_cast("custom"), - nullptr, + "auto", + "standard", + "wide 16:10", + "wide 16:9", + "custom", + nullptr }; // register custom aspect ratio dvar - r_customAspectRatio = Game::Dvar_RegisterFloat("r_customAspectRatio", 16.0f / 9.0f, 4.0f / 3.0f, 63.0f / 9.0f, flags, "Screen aspect ratio. Divide the width by the height in order to get the aspect ratio value. For example: 16 / 9 = 1,77"); + QuickPatch::r_customAspectRatio = Dvar::Register("r_customAspectRatio", + 16.0f / 9.0f, 4.0f / 3.0f, 63.0f / 9.0f, flags, + "Screen aspect ratio. Divide the width by the height in order to get the aspect ratio value. For example: 16 / 9 = 1,77"); // register enumeration dvar - return Game::Dvar_RegisterEnum(name, values.data(), defaultVal, flags, description); + return Game::Dvar_RegisterEnum(name, r_aspectRatioEnum, defaultVal, flags, description); } void QuickPatch::SetAspectRatio() { // set the aspect ratio - Utils::Hook::Set(0x66E1C78, r_customAspectRatio->current.value); + Utils::Hook::Set(0x66E1C78, r_customAspectRatio.get()); } __declspec(naked) void QuickPatch::SetAspectRatioStub() @@ -483,8 +483,8 @@ namespace Components Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick(); // Add ultrawide support - Utils::Hook(0x0051B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick(); - Utils::Hook(0x005063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x51B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick(); + Utils::Hook(0x5063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick(); // Make sure preDestroy is called when the game shuts down Scheduler::OnShutdown(Loader::PreDestroy); diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index 008f4a05..6b13a5a1 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -28,13 +28,13 @@ namespace Components static void JavelinResetHookStub(); - static bool InvalidNameCheck(char *dest, char *source, int size); + static bool InvalidNameCheck(char* dest, const char* source, int size); static void InvalidNameStub(); static Game::dvar_t* sv_enableBounces; static void BounceStub(); - static Game::dvar_t* r_customAspectRatio; + static Dvar::Var r_customAspectRatio; static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* name, char** enumValues, int defaultVal, int flags, const char* description); static void SetAspectRatioStub(); static void SetAspectRatio(); diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index 2422fde2..984474b0 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -108,7 +108,7 @@ namespace Components void Renderer::R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state) { - Game::Com_Error(0, "Tried to use sampler '%s' when it isn't valid for material '%s' and technique '%s'", + Game::Com_Error(Game::ERR_FATAL, "Tried to use sampler '%s' when it isn't valid for material '%s' and technique '%s'", sampler, state->material->info.name, state->technique->name); } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 72995ce6..d97c1486 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -9,6 +9,8 @@ namespace Components unsigned short Script::FunctionName; std::unordered_map Script::ScriptStorage; std::unordered_map Script::ScriptBaseProgramNum; + std::unordered_map Script::ReplacedFunctions; + const char* Script::ReplacedPos = 0; int Script::LastFrameTime = -1; Utils::Signal Script::VMShutdownSignal; @@ -24,7 +26,7 @@ namespace Components Logger::Print(23, "Error: unknown function %s in %s\n", funcName.data(), Script::ScriptName.data()); Logger::Print(23, "************************************\n"); - Logger::Error(5, "script compile error\nunknown function %s\n%s\n\n", funcName.data(), Script::ScriptName.data()); + Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function %s\n%s\n\n", funcName.data(), Script::ScriptName.data()); } __declspec(naked) void Script::StoreFunctionNameStub() @@ -168,7 +170,7 @@ namespace Components Script::PrintSourcePos(Script::ScriptName.data(), offset); Logger::Print(23, "************************************\n\n"); - Logger::Error(5, "script compile error\n%s\n%s\n(see console for actual details)\n", msgbuf, Script::ScriptName.data()); + Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n%s\n%s\n(see console for actual details)\n", msgbuf, Script::ScriptName.data()); } int Script::LoadScriptAndLabel(const std::string& script, const std::string& label) @@ -178,7 +180,7 @@ namespace Components if (!Game::Scr_LoadScript(script.data())) { Logger::Print("Script %s encountered an error while loading. (doesn't exist?)", script.data()); - Logger::Error(1, reinterpret_cast(0x70B810), script.data()); + Logger::Error(Game::ERR_DROP, reinterpret_cast(0x70B810), script.data()); } else { @@ -382,21 +384,109 @@ namespace Components Utils::Hook::Call(0x421EE0)(num); } - int Script::SetExpFogStub() + unsigned int Script::SetExpFogStub() { - if (Game::Scr_GetNumParam() == 6) + if (Game::Scr_GetNumParam() == 6u) { - std::memmove(&Game::scriptContainer->stack[-4], &Game::scriptContainer->stack[-5], sizeof(Game::VariableValue) * 6); - Game::scriptContainer->stack += 1; - Game::scriptContainer->stack[-6].type = Game::VAR_FLOAT; - Game::scriptContainer->stack[-6].u.floatValue = 0; + std::memmove(&Game::scrVmPub->top[-4], &Game::scrVmPub->top[-5], sizeof(Game::VariableValue) * 6); + Game::scrVmPub->top += 1; + Game::scrVmPub->top[-6].type = Game::VAR_FLOAT; + Game::scrVmPub->top[-6].u.floatValue = 0.0f; - ++Game::scriptContainer->numParam; + ++Game::scrVmPub->outparamcount; } return Game::Scr_GetNumParam(); } + const char* Script::GetCodePosForParam(int index) + { + if (static_cast(index) >= Game::scrVmPub->outparamcount) + { + Game::Scr_Error("^1GetCodePosForParam: Index is out of range!\n"); + return ""; + } + + const auto value = &Game::scrVmPub->top[-index]; + + if (value->type != Game::VAR_FUNCTION) + { + Game::Scr_Error("^1GetCodePosForParam: Expects a function as parameter!\n"); + return ""; + } + + return value->u.codePosValue; + } + + void Script::GetReplacedPos(const char* pos) + { + if (Script::ReplacedFunctions.find(pos) != Script::ReplacedFunctions.end()) + { + Script::ReplacedPos = Script::ReplacedFunctions[pos]; + } + } + + void Script::SetReplacedPos(const char* what, const char* with) + { + if (what[0] == '\0' || with[0] == '\0') + { + Logger::Print("Warning: Invalid paramters passed to ReplacedFunctions\n"); + return; + } + + if (Script::ReplacedFunctions.find(what) != Script::ReplacedFunctions.end()) + { + Logger::Print("Warning: ReplacedFunctions already contains codePosValue for a function\n"); + } + + Script::ReplacedFunctions[what] = with; + } + + __declspec(naked) void Script::VMExecuteInternalStub() + { + __asm + { + pushad + + push edx + call Script::GetReplacedPos + + pop edx + popad + + cmp Script::ReplacedPos, 0 + jne SetPos + + movzx eax, byte ptr [edx] + inc edx + + Loc1: + cmp eax, 0x8B + + push ecx + + mov ecx, 0x2045094 + mov [ecx], eax + + mov ecx, 0x2040CD4 + mov [ecx], edx + + pop ecx + + push 0x61E944 + retn + + SetPos: + mov edx, Script::ReplacedPos + mov Script::ReplacedPos, 0 + + movzx eax, byte ptr [edx] + inc edx + + jmp Loc1 + } + } + Game::gentity_t* Script::getEntFromEntRef(Game::scr_entref_t entref) { Game::gentity_t* gentity = &Game::g_entities[entref]; @@ -407,13 +497,27 @@ namespace Components { if (!gentity->client) { - Logger::Error(5, "Entity: %i is not a client", gentity); + Logger::Error(Game::ERR_SCRIPT_DROP, "Entity: %i is not a client", gentity); } return &Game::svs_clients[gentity->s.number]; } void Script::AddFunctions() { + Script::AddFunction("ReplaceFunc", [](Game::scr_entref_t) // gsc: ReplaceFunc(, ) + { + if (Game::Scr_GetNumParam() != 2u) + { + Game::Scr_Error("^1ReplaceFunc: Needs two parameters!\n"); + return; + } + + const auto what = Script::GetCodePosForParam(0); + const auto with = Script::GetCodePosForParam(1); + + Script::SetReplacedPos(what, with); + }); + // System time Script::AddFunction("GetSystemTime", [](Game::scr_entref_t) // gsc: GetSystemTime() { @@ -434,7 +538,7 @@ namespace Components // Print to console, even without being in 'developer 1'. Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1PrintConsole: Needs one string parameter!\n"); return; @@ -448,7 +552,7 @@ namespace Components // Executes command to the console Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec() { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1Exec: Needs one string parameter!\n"); return; @@ -463,7 +567,7 @@ namespace Components // Script Storage Funcs Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); { - if (Game::Scr_GetNumParam() != 2 || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING) { Game::Scr_Error("^1StorageSet: Needs two string parameters!\n"); return; @@ -477,7 +581,7 @@ namespace Components Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(); { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1StorageRemove: Needs one string parameter!\n"); return; @@ -496,7 +600,7 @@ namespace Components Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(); { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1StorageGet: Needs one string parameter!\n"); return; @@ -516,7 +620,7 @@ namespace Components Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(); { - if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING) + if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { Game::Scr_Error("^1StorageHas: Needs one string parameter!\n"); return; @@ -531,6 +635,56 @@ namespace Components { Script::ScriptStorage.clear(); }); + + Script::AddFunction("Noclip", [](Game::scr_entref_t entref) + { + if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr) + { + Game::Scr_Error(Utils::String::VA("^1NoClip: entity %u is not a client\n", entref)); + return; + } + + if (Game::Scr_GetNumParam() == 1 && Game::Scr_GetType(0) == Game::VAR_INTEGER) + { + if (Game::Scr_GetInt(0)) + { + Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_NOCLIP; + } + else + { + Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_NOCLIP; + } + } + else + { + Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_NOCLIP; + } + }); + + Script::AddFunction("Ufo", [](Game::scr_entref_t entref) + { + if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr) + { + Game::Scr_Error(Utils::String::VA("^1Ufo: entity %u is not a client\n", entref)); + return; + } + + if (Game::Scr_GetNumParam() == 1 && Game::Scr_GetType(0) == Game::VAR_INTEGER) + { + if (Game::Scr_GetInt(0)) + { + Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_UFO; + } + else + { + Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_UFO; + } + } + else + { + Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_UFO; + } + }); } Script::Script() @@ -562,6 +716,9 @@ namespace Components Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick(); + Utils::Hook::Nop(0x61E933, 1); + Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); @@ -590,31 +747,10 @@ namespace Components Script::AddFunctions(); - // Script::AddFunction("playviewmodelfx", [](Game::scr_entref_t /*index*/) - // { - // /*auto Scr_Error = Utils::Hook::Call(0x42EF40); - // if (index >> 16) - // { - // Scr_Error("not an entity"); - // return; - // }*/ - - // // obtain FX name - // auto fxName = Game::Scr_GetString(0); - // auto fx = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_FX, fxName).fx; - - // auto tagName = Game::Scr_GetString(1); - // auto tagIndex = Game::SL_GetString(tagName, 0); - - // /*char boneIndex = -2; - // if (!Game::CG_GetBoneIndex(2048, tagIndex, &boneIndex)) - // { - // Scr_Error(Utils::String::VA("Unknown bone %s.\n", tagName)); - // return; - // }*/ - - // Game::CG_PlayBoltedEffect(0, fx, 2048, tagIndex); - // }); + Script::OnVMShutdown([] + { + Script::ReplacedFunctions.clear(); + }); } Script::~Script() @@ -623,6 +759,7 @@ namespace Components Script::ScriptHandles.clear(); Script::ScriptNameStack.clear(); Script::ScriptFunctions.clear(); + Script::ReplacedFunctions.clear(); Script::VMShutdownSignal.clear(); Script::ScriptStorage.clear(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index dd429cde..c9125831 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -40,6 +40,8 @@ namespace Components static unsigned short FunctionName; static std::unordered_map ScriptStorage; static std::unordered_map ScriptBaseProgramNum; + static std::unordered_map ReplacedFunctions; + static const char* ReplacedPos; static int LastFrameTime; static Utils::Signal VMShutdownSignal; @@ -68,7 +70,12 @@ namespace Components static void Scr_PrintPrevCodePosStub(); static void Scr_PrintPrevCodePos(int); - static int SetExpFogStub(); + static unsigned int SetExpFogStub(); + + static const char* GetCodePosForParam(int index); + static void GetReplacedPos(const char* pos); + static void SetReplacedPos(const char* what, const char* with); + static void VMExecuteInternalStub(); static void AddFunctions(); }; diff --git a/src/Components/Modules/Slowmotion.cpp b/src/Components/Modules/Slowmotion.cpp index 0a15b94d..4879e142 100644 --- a/src/Components/Modules/Slowmotion.cpp +++ b/src/Components/Modules/Slowmotion.cpp @@ -38,12 +38,12 @@ namespace Components float start = Game::Scr_GetFloat(0); float end = 1.0f; - if (Game::Scr_GetNumParam() >= 2) + if (Game::Scr_GetNumParam() >= 2u) { end = Game::Scr_GetFloat(1); } - if (Game::Scr_GetNumParam() >= 3) + if (Game::Scr_GetNumParam() >= 3u) { duration = static_cast(Game::Scr_GetFloat(2) * 1000.0); } diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index efff6316..f8f45fc4 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -851,7 +851,7 @@ namespace Components Command::Add("error", [](Command::Params*) { - Game::Com_Error(0, "This is a test %s\n", "error"); + Game::Com_Error(Game::ERR_FATAL, "This is a test %s\n", "error"); }); // now load default assets and shaders @@ -1412,7 +1412,7 @@ namespace Components // build final techsets fastfile if (subCount > 24) { - Logger::ErrorPrint(1, "How did you have 576 fastfiles?\n"); + Logger::ErrorPrint(Game::ERR_DROP, "How did you have 576 fastfiles?\n"); } curTechsets_list.clear(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 462c6d9c..d61e41fa 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -460,7 +460,7 @@ namespace Game XZone* g_zones = reinterpret_cast(0x14C0F80); unsigned short* db_hashTable = reinterpret_cast(0x12412B0); - ScriptContainer* scriptContainer = reinterpret_cast(0x2040D00); + scrVmPub_t* scrVmPub = reinterpret_cast(0x2040CF0); clientstate_t* clcState = reinterpret_cast(0xB2C540); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 2569dbcf..2f2ef11b 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -121,7 +121,7 @@ namespace Game typedef void(__cdecl * Com_ClientPacketEvent_t)(); extern Com_ClientPacketEvent_t Com_ClientPacketEvent; - typedef void(__cdecl * Com_Error_t)(int type, const char* message, ...); + typedef void(__cdecl * Com_Error_t)(errorParm_t type, const char* message, ...); extern Com_Error_t Com_Error; typedef void(__cdecl * Com_Printf_t)(int channel, const char *fmt, ...); @@ -247,7 +247,7 @@ namespace Game typedef dvar_t* (__cdecl * Dvar_RegisterInt_t)(const char* name, int defaultVal, int min, int max, int flags, const char* description); extern Dvar_RegisterInt_t Dvar_RegisterInt; - typedef dvar_t* (__cdecl * Dvar_RegisterEnum_t)(const char* name, char** enumValues, int defaultVal, int flags, const char* description); + typedef dvar_t* (__cdecl * Dvar_RegisterEnum_t)(const char* name, const char** enumValues, int defaultVal, int flags, const char* description); extern Dvar_RegisterEnum_t Dvar_RegisterEnum; typedef dvar_t* (__cdecl * Dvar_RegisterString_t)(const char* name, const char* defaultVal, int, const char*); @@ -660,7 +660,7 @@ namespace Game typedef unsigned int(__cdecl * Scr_GetObject_t)(int); extern Scr_GetObject_t Scr_GetObject; - typedef int(__cdecl * Scr_GetNumParam_t)(); + typedef unsigned int(__cdecl * Scr_GetNumParam_t)(); extern Scr_GetNumParam_t Scr_GetNumParam; typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char*, const char*); @@ -687,7 +687,7 @@ namespace Game typedef bool(__cdecl * Scr_IsSystemActive_t)(); extern Scr_IsSystemActive_t Scr_IsSystemActive; - typedef int(__cdecl* Scr_GetType_t)(int); + typedef int(__cdecl* Scr_GetType_t)(unsigned int); extern Scr_GetType_t Scr_GetType; typedef void(__cdecl* Scr_Error_t)(const char*); @@ -931,6 +931,7 @@ namespace Game extern int* demoRecording; extern int* serverMessageSequence; + constexpr auto MAX_GENTITIES = 2048u; extern gentity_t* g_entities; extern netadr_t* connectedHost; @@ -968,7 +969,7 @@ namespace Game extern XZone* g_zones; extern unsigned short* db_hashTable; - extern ScriptContainer* scriptContainer; + extern scrVmPub_t* scrVmPub; extern clientstate_t* clcState; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 7be1e8d7..bb2bd6f3 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -109,7 +109,7 @@ namespace Game IMG_CATEGORY_WATER = 0x5, IMG_CATEGORY_RENDERTARGET = 0x6, IMG_CATEGORY_TEMP = 0x7, - } ; + }; enum buttons_t { @@ -177,6 +177,18 @@ namespace Game CS_ACTIVE = 0x5, } clientstate_t; + typedef enum + { + ERR_FATAL = 0x0, + ERR_DROP = 0x1, + ERR_SERVERDISCONNECT = 0x2, + ERR_DISCONNECT = 0x3, + ERR_SCRIPT = 0x4, + ERR_SCRIPT_DROP = 0x5, + ERR_LOCALIZATION = 0x6, + ERR_MAPLOADERRORSUMMARY = 0x7 + } errorParm_t; + struct FxEffectDef; struct pathnode_t; struct pathnode_tree_t; @@ -4886,18 +4898,38 @@ namespace Game struct VariableValue { VariableUnion u; - int type; + VariableType type; }; - struct ScriptContainer + struct function_stack_t { - VariableValue* stack; - char unk1; - char unk2; - char unk3; - char pad; - DWORD unk4; - int numParam; + const char* pos; + unsigned int localId; + unsigned int localVarCount; + VariableValue* top; + VariableValue* startTop; + }; + + struct function_frame_t + { + function_stack_t fs; + int topType; + }; + + struct scrVmPub_t + { + unsigned int* localVars; + VariableValue* maxStack; + int function_count; + function_frame_t* function_frame; + VariableValue* top; + bool debugCode; + bool abort_on_error; + bool terminal_error; + unsigned int inparamcount; + unsigned int outparamcount; + function_frame_t function_frame_start[32]; + VariableValue stack[2048]; }; enum UILocalVarType @@ -6876,6 +6908,37 @@ namespace Game const char* args[9]; }; + struct pmove_s + { + playerState_s* ps; + usercmd_s cmd; + usercmd_s oldcmd; + int tracemask; + int numtouch; + int touchents[32]; + char __pad0[24]; + float xyspeed; + int proneChange; + float maxSprintTimeMultiplier; + bool mantleStarted; + float mantleEndPos[3]; + int mantleDuration; + int viewChangeTime; + float viewChange; + float fTorsoPitch; + float fWaistPitch; + unsigned char handler; + }; + + enum EffectiveStance + { + PM_EFF_STANCE_DEFAULT = 0, + PM_EFF_STANCE_PRONE = 1, + PM_EFF_STANCE_DUCKED = 2, + PM_EFF_STANCE_LASTSTANDCRAWL = 3, + PM_EFF_STANCE_COUNT = 4 + }; + #pragma endregion #ifndef IDA diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 208e0cab..4ae48092 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -42,10 +42,9 @@ #include #include #include - -// Experimental C++17 features #include #include +#include #pragma warning(pop)