diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..4fcd556e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: gitsubmodule + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 9b22b1b6..00000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,36 +0,0 @@ -"part: game client": - - src/Components/Modules/Stats* - -"part: game server": - - src/Components/Modules/Dedicated* - -"part: zonebuilder": - - src/Components/Modules/ZoneBuilder* - -"area: menus": - - src/Components/Modules/Menus* - -"area: anticheat": - - src/Components/Modules/AntiCheat* - -"area: serverlist": - - src/Components/Modules/ServerList* - -"area: weapons": - - src/Components/Modules/Weapon* - -"area: networking": - - src/Components/Modules/Auth* - - src/Components/Modules/Network* - - src/Components/Modules/Node* - - src/Components/Modules/PlayerName* - - src/Components/Modules/RCon* - - src/Components/Modules/Session* - -"area: continuous integration": - - "*appveyor*" - - ".github/workflows/**" - - Jenkinsfile - -"status: work in progress": - - "**" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..19cd75f0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,117 @@ +name: Build + +on: + push: + branches: + - "*" + pull_request: + branches: + - "*" + types: [opened, synchronize, reopened] + +jobs: + build: + name: Build binaries + runs-on: windows-2022 + 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@v3 + 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.1 + + - name: Generate project files + run: tools/premake5 vs2022 --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}} /p:Platform=Win32 build/iw4x.sln + + - name: Upload ${{matrix.configuration}} binaries + uses: actions/upload-artifact@v2 + with: + name: ${{matrix.configuration}} binaries + path: | + build/bin/Win32/${{matrix.configuration}}/iw4x.dll + build/bin/Win32/${{matrix.configuration}}/iw4x.pdb + +# - name: Upload ${{matrix.configuration}} data artifacts +# uses: actions/upload-artifact@v2 +# with: +# name: ${{matrix.configuration}} data artifacts +# path: | +# data/* + + deploy: + name: Deploy artifacts + needs: build + 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 }} diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml new file mode 100644 index 00000000..d80352fb --- /dev/null +++ b/.github/workflows/discord-notify.yml @@ -0,0 +1,17 @@ +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 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/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index e73c0588..00000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: "Pull Request Labeler" -on: -- pull_request - -jobs: - labeler: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v2 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" 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/.gitmodules b/.gitmodules index 07d163da..dd66b2bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,7 +25,7 @@ [submodule "deps/protobuf"] path = deps/protobuf url = https://github.com/google/protobuf.git - branch = 3.11.x + branch = 3.17.x [submodule "deps/udis86"] path = deps/udis86 url = https://github.com/vmt/udis86.git diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 0fb6a5ce..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,317 +0,0 @@ -#!groovy - -/* -This is our new pipeline script to do all of the building in, of and around IW4x. - -Here's what it is supposed to do: - -- Make sure Modern Warfare 2 is installed (CI should provide the folder like a custom tool) -- Check out code from iw4x-data -- Build the IW4x client library (this code repository) -- Use iw4x.exe from the iw4x-data repository in order to build the zone files in iw4x-data -- Package the IW4x client with the newly built data files - -At this point it is done building everything, however afterwards we want the build server to -also push the newly built files to an update repository, depending on the branch we're on. - -- For "develop", release to the "iw4x-dev" branch on the repository server. -- For "master", release to the "iw4x" branch on the repository server. - -I'm looking into how the logic of pipelining works in detail before deciding on whether to -throw in the IW4x Updater and the IW4x Node binaries in as well or not. -*/ - -/* -Note that this is just a rewrite of the jobs as they are currently set up on the production -Jenkins server. This will allow every developer to tinker around with how the build process -is set up. For those who want to play around with this, here's a bit of information: - -- This is a Groovy script. Essentially Java but with less bullshit (like brackets and verbose writing). -- This gets directly translated into a Jenkins pipeline. -- If you have no idea how to handle scripts, get your hands off this file. -- If you do not use Jenkins, get your hands off this file. -- If you fuck this script up, I will kill you. -*/ - -import groovy.transform.Field - -@Field def configurations = [ - "Debug": [ - WorkspaceID: "build@debug", - StashName: "iw4x-debug", - MSBuildConfiguration: "Debug", - PremakeArgs: "", - Archive: true, - ], - "Release": [ - WorkspaceID: "build@release", - StashName: "iw4x-release", - MSBuildConfiguration: "Release", - PremakeArgs: "", - Archive: true, - ], - "Release with unit tests": [ - WorkspaceID: "build@release+unittests", - StashName: "iw4x-release-unittests", - MSBuildConfiguration: "Release", - PremakeArgs: "--force-unit-tests", - Archive: false, - ], -].collect {k, v -> [k, v]} - -@Field def testing = [ - "Debug": [ - WorkspaceID: "testing@debug", - StashName: "iw4x-debug", - ], - "Release": [ - WorkspaceID: "testing@release", - StashName: "iw4x-release-unittests", - ], -].collect {k, v -> [k, v]} - -def jobWorkspace(id, f) { - ws("workspace/${env.JOB_NAME.replaceAll(/[%$]/, "_")}@$id", f) -} - -def useShippedPremake(f) { - def premakeHome = "${pwd()}\\tools" - - withEnv(["PATH+=${premakeHome}"], f) -} - -def getIW4xExecutable() { - step([ - $class: 'CopyArtifact', - filter: '*', - fingerprintArtifacts: true, - projectName: 'iw4x/iw4x-executable/' + iw4xExecutableBranch(), - selector: [ - $class: 'TriggeredBuildSelector', - allowUpstreamDependencies: false, - fallbackToLastSuccessful: true, - upstreamFilterStrategy: 'UseGlobalSetting' - ] - ]) -} - -// This will build the IW4x client. -// We need a Windows Server with Visual Studio 2015, Premake5 and Git on it. -def doBuild(cfg) { - retry(5) { - checkout scm - } - - useShippedPremake { - def outputDir = pwd() - def msbuild = tool "Microsoft.NET MSBuild 15.0" - bat "premake5 vs2017 ${cfg.PremakeArgs}" - bat "\"${msbuild}\" build\\iw4x.sln \"/p:OutDir=$outputDir\\\\\" \"/p:Configuration=${cfg.MSBuildConfiguration}\"" - } - - stash name: "${cfg.StashName}", includes: "*.dll,*.pdb" -} - -// This will run the unit tests for IW4x. -// We need a Windows Server with MW2 on it. -def doUnitTests(name) { - mw2dir = tool "Modern Warfare 2" - - unstash "$name" - - // Get installed localization for correct zonefiles directory junction - def localization = readFile("${tool "Modern Warfare 2"}/localization.txt").split("\r?\n")[0] - - try { - timeout(time: 10, unit: "MINUTES") { - // Set up environment - if (isUnix()) { - def mw2dir = tool "Modern Warfare 2" - sh """ - mkdir -p zone - for f in main zone/dlc \"zone/$localization\"; do - ln -sfv \"$mw2dir/\$f\" \"\$f\" - done - for f in \"$mw2dir\"/*.dll \"$mw2dir\"/*.txt \"$mw2dir\"/*.bmp; do - ln -sfv \"\$f\" \"\$(basename \"\$f\")\" - done - """ - } else { - def mw2dir = tool "Modern Warfare 2" - bat """ - mklink /J \"main\" \"$mw2dir\\main\" - mkdir \"zone\" - mklink /J \"zone\\dlc\" \"$mw2dir\\zone\\dlc\" - mklink /J \"zone\\$localization\" \"$mw2dir\\zone\\$localization\" - copy /y \"$mw2dir\\*.dll\" - copy /y \"$mw2dir\\*.txt\" - copy /y \"$mw2dir\\*.bmp\" - """ - } - - // Run tests - getIW4xExecutable() - retry(2) { - if (isUnix()) { - sh "WINEDEBUG=warn+all wine iw4x.exe -tests; wineserver -w" - } else { - bat "iw4x.exe -tests" - } - } - } - } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) { - currentBuild.result = 'UNSTABLE' - println("${name} unit test interrupted (ran too long?)") - } catch (Exception e) { - println("${name} unit test failed.") - if (isUnix()) { - currentBuild.result = 'UNSTABLE' - } else { - throw e - } - } finally { - // In all cases make sure to at least remove the directory junctions! - if (!isUnix()) { - bat """ - rmdir \"main\" - rmdir \"zone\\dlc\" - rmdir \"zone\\$localization\" - """ - } - deleteDir() - } -} - -// Returns the IW4x executable branch to use -def iw4xExecutableBranch() { - try { - return IW4X_EXECUTABLE_BRANCH; - } catch(MissingPropertyException) { - return "master"; - } -} - -// Job properties -properties([ - buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '30')), - disableConcurrentBuilds(), - gitLabConnection('iw4x'), - [$class: 'LeastLoadDisabledProperty', leastLoadDisabled: false] -]) - -gitlabBuilds(builds: ["Checkout & Versioning", "Build", "Testing", "Archiving"]) { - // First though let's give this build a proper name - stage("Checkout & Versioning") { - gitlabCommitStatus(name: "Checkout & Versioning") { - node("windows") { - jobWorkspace("versioning") { - if (env.BRANCH_NAME == 'master') - { - echo 'Reset build environment' - deleteDir() - } - - retry(5) { - checkout scm - } - - useShippedPremake { - def version = bat(returnStdout: true, script: '@premake5 version').split("\r?\n")[2] - - currentBuild.setDisplayName "$version (#${env.BUILD_NUMBER})" - } - - stash name: "jenkins-files", includes: "jenkins/**" - } - } - } - } - - // For each available configuration generate a normal build and a unit test build. - stage("Build") { - gitlabCommitStatus(name: "Build") { - def executions = [:] - for (int i = 0; i < configurations.size(); i++) { - def entry = configurations[i] - - def configName = entry[0] - def config = entry[1] - - executions[configName] = { - node("windows") { - jobWorkspace(config.WorkspaceID) { - doBuild(config) - } - } - } - } - parallel executions - } - } - - // Run unit tests on each configuration. - stage("Testing") { - gitlabCommitStatus(name: "Testing") { - executions = [:] - for (int i = 0; i < testing.size(); i++) { - def entry = testing.get(i) - - def testName = entry[0] - def test = entry[1] - - executions["$testName on Windows"] = { - node("windows") { - jobWorkspace(test.WorkspaceID) { - doUnitTests(test.StashName) - } - } - } - executions["$testName on Linux"] = { - node("docker && linux && amd64") { - timeout(time: 10, unit: "MINUTES") { - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) { - def image = null - dir("src") { - unstash "jenkins-files" - image = docker.build("github.com/IW4x/iw4x-client-testing-wine32", "--rm --force-rm -f jenkins/wine32.Dockerfile jenkins") - deleteDir() - } - image.inside { - doUnitTests(test.StashName) - } - } - } - } - } - parallel executions - } - } - } - - // Collect all the binaries and give each configuration its own subfolder - stage("Archiving") { - gitlabCommitStatus(name: "Archiving") { - node("windows") { // any node will do - jobWorkspace("archiving") { - try { - for (int i = 0; i < configurations.size(); i++) { - def entry = configurations[i] - - def configName = entry[0] - def config = entry[1] - - if (config.Archive) { - dir(configName) { - unstash config.StashName - } - } - } - archiveArtifacts artifacts: "**/*.dll,**/*.pdb", fingerprint: true - } finally { - deleteDir() - } - } - } - } - } -} diff --git a/README.md b/README.md index 186dc7b3..fb5dc10a 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,16 @@ ![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 status](https://ci.appveyor.com/api/projects/status/rvljq0ooxen0oexm/branch/develop?svg=true)](https://ci.appveyor.com/project/iw4x/iw4x-client/branch/develop) +[![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/iw4x) +[![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`. -- Build via solution file in `build\iw4x.sln`. (You can use the `build.bat` script to do it quick and easy.) +- Run `premake5 vs2022` or use the delivered `generate.bat`. +- Build via solution file in `build\iw4x.sln`. ## Premake arguments @@ -40,6 +28,27 @@ | `--disable-bitmessage` | Disable use of BitMessage completely. | | `--disable-base128` | Disable base128 encoding for minidumps. | | `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). | +| `--iw4x-zones` | Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches. | + +## Command line arguments + +| Argument | Description | +|:------------------------|:-----------------------------------------------| +| `-tests` | Perform unit tests. | +| `-entries` | Print to the console a list of every asset as they are loaded from zonefiles. | +| `-stdout` | Redirect all logging output to the terminal iw4x is started from, or if there is none, creates a new terminal window to write log information in. | +| `-console` | Allow the game to display its own separate interactive console window. | +| `-dedicated` | Starts the game as a headless dedicated server. | +| `-scriptablehttp` | Enable HTTP related gsc functions. | +| `-bigminidumps` | Include all code sections from loaded modules in the dump. | +| `-reallybigminidumps` | Include data sections from all loaded modules in the dump. | +| `-dump` | Write info of loaded assets to the raw folder as they are being loaded. | +| `-monitor` | This flag is for internal use and it is used to indicate if an external console is present. | +| `-nointro` | Skip game's cinematic intro. | +| `-version` | Print IW4x build info on startup. | +| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. | +| `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. | + ## Disclaimer diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index da4851e8..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,34 +0,0 @@ -# AppVeyor CI configuration - -version: "#{build} ({branch})" - -environment: - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PREMAKE_ACTION: vs2019 - -configuration: - - Debug - - Release - -platform: Win32 - -install: - - ps: | - Write-Host "Updating version information..." -ForegroundColor Cyan - - Update-AppveyorBuild -Version $(& tools/premake5.exe version | select -Last 1) - - git submodule update --init --recursive - - ps: | - Write-Host "Generating project files with premake..." -ForegroundColor Cyan - & "./tools/premake5.exe" $env:PREMAKE_ACTION - Write-Host "Generated" -ForegroundColor Green - -build: - project: build/iw4x.sln - parallel: true - verbosity: minimal - -artifacts: - - path: build/bin/**/*.dll - - path: build/bin/**/*.pdb diff --git a/build.bat b/build.bat deleted file mode 100644 index fa04e7f0..00000000 --- a/build.bat +++ /dev/null @@ -1,24 +0,0 @@ -@echo off & setlocal - -cd %~dp0 - -if exist "%PROGRAMFILES(x86)%\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsMSBuildCmd.bat" call "%PROGRAMFILES(x86)%\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsMSBuildCmd.bat" -call msbuild /version >NUL 2>NUL -if errorlevel 0 goto:build - -if exist "%PROGRAMFILES(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\15.0\Bin\msbuild.exe" path %PROGRAMFILES(x86)%\Microsoft Visual Studio\2019\Enterprise\MSBuild\15.0\Bin;%PATH% -call msbuild /version >NUL 2>NUL -if errorlevel 0 goto:build - -echo Couldn't find any MSBuild to build this project. -echo Make sure you have Visual C++ Build Tools 2019 or Visual Studio 2019 installed. -endlocal -exit /B 1 - -:build -call generate.bat -set PLATFORM=Win32 -set CONFIGURATION=Release -call msbuild /nologo /m /v:m %* build\iw4x.sln -endlocal -exit /B %ERRORLEVEL% diff --git a/deps/json11 b/deps/json11 index e2e3a11e..2df9473f 160000 --- a/deps/json11 +++ b/deps/json11 @@ -1 +1 @@ -Subproject commit e2e3a11e99672b018e0e0657867e6a3439e180cf +Subproject commit 2df9473fb3605980db55ecddf34392a2e832ad35 diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 1937f412..06a81aeb 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 1937f412605e1b04ddb41ef9c2f2f0aab7e61548 +Subproject commit 06a81aeb227424182125363f7554fad5146d6d2a diff --git a/deps/libtommath b/deps/libtommath index 6ac0b0c1..5108f123 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 6ac0b0c1b69b9a88e1b3b3002c2e3a9062ae99b4 +Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a diff --git a/deps/pdcurses b/deps/pdcurses index 618e0aaa..2fa0f10d 160000 --- a/deps/pdcurses +++ b/deps/pdcurses @@ -1 +1 @@ -Subproject commit 618e0aaa31b4728eb4df78ec4de6c2b873908eda +Subproject commit 2fa0f10dd844da47ee83c05a40a1ec541ceb95e1 diff --git a/deps/premake/dxsdk.lua b/deps/premake/dxsdk.lua new file mode 100644 index 00000000..2d7be9c9 --- /dev/null +++ b/deps/premake/dxsdk.lua @@ -0,0 +1,19 @@ +dxsdk = { + source = path.join(dependencies.basePath, "dxsdk"), +} + +function dxsdk.import() + libdirs {path.join(dxsdk.source, "Lib/x86")} + dxsdk.includes() +end + +function dxsdk.includes() + includedirs { + path.join(dxsdk.source, "Include"), + } +end + +function dxsdk.project() +end + +table.insert(dependencies, dxsdk) diff --git a/deps/premake/json11.lua b/deps/premake/json11.lua new file mode 100644 index 00000000..3437ba57 --- /dev/null +++ b/deps/premake/json11.lua @@ -0,0 +1,32 @@ +json11 = { + source = path.join(dependencies.basePath, "json11"), +} + +function json11.import() + links {"json11"} + + json11.includes() +end + +function json11.includes() + includedirs {json11.source} +end + +function json11.project() + project "json11" + language "C++" + + files + { + path.join(json11.source, "*.cpp"), + path.join(json11.source, "*.hpp"), + } + + warnings "Off" + + defines {"_LIB"} + removedefines {"_USRDLL", "_DLL"} + kind "StaticLib" +end + +table.insert(dependencies, json11) \ No newline at end of file diff --git a/deps/premake/libtomcrypt.lua b/deps/premake/libtomcrypt.lua new file mode 100644 index 00000000..51860cfa --- /dev/null +++ b/deps/premake/libtomcrypt.lua @@ -0,0 +1,59 @@ +libtomcrypt = { + source = path.join(dependencies.basePath, "libtomcrypt"), +} + +function libtomcrypt.import() + links {"libtomcrypt"} + + libtomcrypt.includes() +end + +function libtomcrypt.includes() + includedirs { + path.join(libtomcrypt.source, "src/headers") + } + + defines { + "LTC_NO_FAST", + "LTC_NO_PROTOTYPES", + "LTC_NO_RSA_BLINDING", + } +end + +function libtomcrypt.project() + project "libtomcrypt" + language "C" + + libtomcrypt.includes() + libtommath.import() + + files { + path.join(libtomcrypt.source, "src/**.c"), + } + + removefiles { + path.join(libtomcrypt.source, "src/**/*tab.c"), + path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"), + } + + defines { + "_CRT_SECURE_NO_WARNINGS", + "LTC_SOURCE", + "_LIB", + "USE_LTM" + } + + removedefines { + "_DLL", + "_USRDLL" + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, libtomcrypt) diff --git a/deps/premake/libtommath.lua b/deps/premake/libtommath.lua new file mode 100644 index 00000000..e6cc65e2 --- /dev/null +++ b/deps/premake/libtommath.lua @@ -0,0 +1,50 @@ +libtommath = { + source = path.join(dependencies.basePath, "libtommath"), +} + +function libtommath.import() + links {"libtommath"} + + libtommath.includes() +end + +function libtommath.includes() + includedirs { + libtommath.source + } + + defines { + "LTM_DESC", + "__STDC_IEC_559__", + "MP_NO_DEV_URANDOM", + } +end + +function libtommath.project() + project "libtommath" + language "C" + + libtommath.includes() + + files { + path.join(libtommath.source, "*.c"), + } + + defines { + "_LIB" + } + + removedefines { + "_DLL", + "_USRDLL" + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, libtommath) diff --git a/deps/premake/minizip.lua b/deps/premake/minizip.lua new file mode 100644 index 00000000..755f5dae --- /dev/null +++ b/deps/premake/minizip.lua @@ -0,0 +1,43 @@ +minizip = { + source = path.join(dependencies.basePath, "zlib/contrib/minizip"), +} + +function minizip.import() + links {"minizip"} + zlib.import() + minizip.includes() +end + +function minizip.includes() + includedirs { + minizip.source + } + + zlib.includes() +end + +function minizip.project() + project "minizip" + language "C" + + minizip.includes() + + files { + path.join(minizip.source, "*.h"), + path.join(minizip.source, "*.c"), + } + + removefiles { + path.join(minizip.source, "miniunz.c"), + path.join(minizip.source, "minizip.c"), + } + + defines { + "_CRT_SECURE_NO_DEPRECATE", + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, minizip) diff --git a/deps/premake/mongoose.lua b/deps/premake/mongoose.lua new file mode 100644 index 00000000..01935e94 --- /dev/null +++ b/deps/premake/mongoose.lua @@ -0,0 +1,32 @@ +mongoose = { + source = path.join(dependencies.basePath, "mongoose"), +} + +function mongoose.import() + links {"mongoose"} + + mongoose.includes() +end + +function mongoose.includes() + includedirs {mongoose.source} +end + +function mongoose.project() + project "mongoose" + language "C" + + mongoose.includes() + + files + { + path.join(mongoose.source, "*.c"), + path.join(mongoose.source, "*.h"), + } + + warnings "Off" + + kind "StaticLib" +end + +table.insert(dependencies, mongoose) diff --git a/deps/premake/pdcurses.lua b/deps/premake/pdcurses.lua new file mode 100644 index 00000000..7e427b6e --- /dev/null +++ b/deps/premake/pdcurses.lua @@ -0,0 +1,34 @@ +pdcurses = { + source = path.join(dependencies.basePath, "pdcurses"), +} + +function pdcurses.import() + links {"pdcurses"} + + pdcurses.includes() +end + +function pdcurses.includes() + includedirs {pdcurses.source} +end + +function pdcurses.project() + project "pdcurses" + language "C" + + pdcurses.includes() + + files + { + path.join(pdcurses.source, "pdcurses/*.c"), + path.join(pdcurses.source, "pdcurses/*.h"), + path.join(pdcurses.source, "wincon/*.c"), + path.join(pdcurses.source, "wincon/*.h"), + } + + warnings "Off" + + kind "StaticLib" +end + +table.insert(dependencies, pdcurses) diff --git a/deps/premake/protobuf.lua b/deps/premake/protobuf.lua new file mode 100644 index 00000000..a293576f --- /dev/null +++ b/deps/premake/protobuf.lua @@ -0,0 +1,50 @@ +protobuf = { + source = path.join(dependencies.basePath, "protobuf"), +} + +function protobuf.import() + links {"protobuf"} + + protobuf.includes() +end + +function protobuf.includes() + includedirs { + path.join(protobuf.source, "src"), + } +end + +function protobuf.project() + project "protobuf" + language "C++" + + protobuf.includes() + + files { + path.join(protobuf.source, "src/**.cc"), + "./src/**.proto", + } + + removefiles { + path.join(protobuf.source, "src/**/*test.cc"), + path.join(protobuf.source, "src/google/protobuf/*test*.cc"), + + path.join(protobuf.source, "src/google/protobuf/testing/**.cc"), + path.join(protobuf.source, "src/google/protobuf/compiler/**.cc"), + + path.join(protobuf.source, "src/google/protobuf/arena_nc.cc"), + path.join(protobuf.source, "src/google/protobuf/util/internal/error_listener.cc"), + path.join(protobuf.source, "**/*_gcc.cc"), + } + + rules {"ProtobufCompiler"} + + defines {"_SCL_SECURE_NO_WARNINGS"} + + linkoptions {"-IGNORE:4221"} + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, protobuf) diff --git a/deps/premake/udis86.lua b/deps/premake/udis86.lua new file mode 100644 index 00000000..f4cf46b2 --- /dev/null +++ b/deps/premake/udis86.lua @@ -0,0 +1,35 @@ +udis86 = { + source = path.join(dependencies.basePath, "udis86"), +} + +function udis86.import() + links {"udis86"} + + udis86.includes() +end + +function udis86.includes() + includedirs { + udis86.source, + path.join(udis86.source, "libudis86"), + path.join(dependencies.basePath, "extra/udis86"), + path.join(dependencies.basePath, "extra/udis86/libudis86"), + } +end + +function udis86.project() + project "udis86" + language "C" + + udis86.includes() + + files { + path.join(udis86.source, "libudis86/*.c"), + path.join(dependencies.basePath, "extra/udis86/libudis86/*.c"), + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, udis86) diff --git a/deps/premake/zlib.lua b/deps/premake/zlib.lua new file mode 100644 index 00000000..c166a160 --- /dev/null +++ b/deps/premake/zlib.lua @@ -0,0 +1,39 @@ +zlib = { + source = path.join(dependencies.basePath, "zlib"), +} + +function zlib.import() + links {"zlib"} + zlib.includes() +end + +function zlib.includes() + includedirs { + zlib.source + } + + defines { + "ZLIB_CONST", + } +end + +function zlib.project() + project "zlib" + language "C" + + zlib.includes() + + files { + path.join(zlib.source, "*.h"), + path.join(zlib.source, "*.c"), + } + + defines { + "_CRT_SECURE_NO_DEPRECATE", + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, zlib) diff --git a/deps/protobuf b/deps/protobuf index 63cfdafa..5500c72c 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit 63cfdafacba6141717a2df97fc123dc0c14ba7c4 +Subproject commit 5500c72c5b616da9f0125bcfab513987a1226e2b diff --git a/deps/zlib b/deps/zlib index d71dc66f..ec3df002 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit d71dc66fa8a153fb6e7c626847095d9697a6cf42 +Subproject commit ec3df00224d4b396e2ac6586ab5d25f673caa4c2 diff --git a/generate.bat b/generate.bat index 65a3b206..b3823287 100644 --- a/generate.bat +++ b/generate.bat @@ -1,4 +1,4 @@ @echo off echo Updating submodules... call git submodule update --init --recursive -call tools\premake5 %* vs2019 +call tools\premake5 %* vs2022 --ac-disable diff --git a/jenkins/wine32.Dockerfile b/jenkins/wine32.Dockerfile deleted file mode 100644 index b55626de..00000000 --- a/jenkins/wine32.Dockerfile +++ /dev/null @@ -1,46 +0,0 @@ -# Requires a decent modern Docker version (v1.10.x at least ideally) - -# Use semi-official Arch Linux image with fixed versioning -FROM archlinux/base - -# Environment variables -ENV WINEPREFIX /wine32 -ENV WINEARCH win32 -ENV WINEDEBUG -all - -# Install Wine (32-bit) -RUN \ - echo -e "#!/bin/sh\nwine \$@\nretval=\$?\nwineserver -w\nexit \$retval" > /usr/local/bin/wine-wrapper &&\ - chmod +x /usr/local/bin/wine-wrapper &&\ -\ - (\ - echo '' &&\ - echo '[multilib]' &&\ - echo 'Include = /etc/pacman.d/mirrorlist'\ - ) >> /etc/pacman.conf &&\ - pacman -Sy --noconfirm \ - awk \ - lib32-gnutls \ - wine \ - wget \ - xorg-server-xvfb \ - pacman-contrib \ - awk \ - &&\ -\ - wine-wrapper wineboot.exe -i &&\ - wget -Ovcredist_x86.exe https://download.microsoft.com/download/d/d/9/dd9a82d0-52ef-40db-8dab-795376989c03/vcredist_x86.exe &&\ - WINEDEBUG=+all-trace xvfb-run sh -c 'wine-wrapper vcredist_x86.exe /q' &&\ - rm vcredist_x86.exe &&\ -\ - pacman -Rs --noconfirm \ - xorg-server-xvfb \ - wget \ - &&\ -\ - find /. -name "*~" -type f -delete &&\ - rm -rf /tmp/* /var/tmp/* /usr/share/man/* /usr/share/info/* /usr/share/doc/* &&\ - pacman -Scc --noconfirm &&\ - rm -rf /var/lib/pacman/sync/* - -USER 0 diff --git a/lib/include/stb_truetype.h b/lib/include/stb_truetype.h new file mode 100644 index 00000000..f6ab5b01 --- /dev/null +++ b/lib/include/stb_truetype.h @@ -0,0 +1,5011 @@ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/premake/dxsdk.lua b/premake/dxsdk.lua deleted file mode 100644 index 08bcbc1a..00000000 --- a/premake/dxsdk.lua +++ /dev/null @@ -1,35 +0,0 @@ -dxsdk = { - settings = nil -} - -function dxsdk.setup(settings) - if not settings.source then error("Missing source.") end - - dxsdk.settings = settings - - if not dxsdk.settings.defines then dxsdk.settings.defines = {} end -end - -function dxsdk.import() - if not dxsdk.settings then error("You need to call dxsdk.setup first") end - - --filter "platforms:*32" - libdirs { path.join(dxsdk.settings.source, "Lib/x86") } - - --filter "platforms:*64" - -- libdirs { path.join(dxsdk.settings.source, "Lib/x64") } - - --filter {} - - dxsdk.includes() -end - -function dxsdk.includes() - if not dxsdk.settings then error("You need to call dxsdk.setup first") end - - includedirs { path.join(dxsdk.settings.source, "Include") } - defines(dxsdk.settings.defines) -end - -function dxsdk.project() -end \ No newline at end of file diff --git a/premake/iw4mvm.lua b/premake/iw4mvm.lua deleted file mode 100644 index c2823227..00000000 --- a/premake/iw4mvm.lua +++ /dev/null @@ -1,54 +0,0 @@ -iw4mvm = { - settings = nil -} - -function iw4mvm.setup(settings) - if not settings.source then error("Missing source.") end - - iw4mvm.settings = settings - - if not iw4mvm.settings.defines then iw4mvm.settings.defines = {} end -end - -function iw4mvm.import() - if not iw4mvm.settings then error("You need to call iw4mvm.setup first") end - - links { "iw4mvm" } - iw4mvm.includes() -end - -function iw4mvm.includes() - if not iw4mvm.settings then error("You need to call iw4mvm.setup first") end - - includedirs { iw4mvm.settings.source } - libdirs { path.join(iw4mvm.settings.source, "IW4MVM") } - defines(iw4mvm.settings.defines) -end - -function iw4mvm.project() - if not iw4mvm.settings then error("You need to call iw4mvm.setup first") end - - project "iw4mvm" - language "C++" - - characterset ("MBCS") - - defines("_CRT_SECURE_NO_WARNINGS") - - iw4mvm.includes() - files - { - path.join(iw4mvm.settings.source, "IW4MVM/*.h"), - path.join(iw4mvm.settings.source, "IW4MVM/*.cpp"), - } - - removefiles - { - --path.join(iw4mvm.settings.source, "IW4MVM/detours.cpp"), - path.join(iw4mvm.settings.source, "IW4MVM/DllMain.cpp"), - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - kind "StaticLib" -end \ No newline at end of file diff --git a/premake/json11.lua b/premake/json11.lua deleted file mode 100644 index 325f5240..00000000 --- a/premake/json11.lua +++ /dev/null @@ -1,51 +0,0 @@ -json11 = { - settings = nil, -} - -function json11.setup(settings) - if not settings.source then error("Missing source.") end - - json11.settings = settings -end - -function json11.import() - if not json11.settings then error("Run json11.setup first") end - - links { "json11" } - json11.includes() -end - -function json11.includes() - if not json11.settings then error("Run json11.setup first") end - - includedirs { json11.settings.source } -end - -function json11.project() - if not json11.settings then error("Run json11.setup first") end - - project "json11" - language "C++" - - includedirs - { - json11.settings.source, - } - - files - { - path.join(json11.settings.source, "*.cpp"), - path.join(json11.settings.source, "*.hpp"), - } - removefiles - { - path.join(json11.settings.source, "test*"), - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - defines { "_LIB" } - removedefines { "_USRDLL", "_DLL" } - kind "StaticLib" -end diff --git a/premake/libtomcrypt.lua b/premake/libtomcrypt.lua deleted file mode 100644 index f955b59a..00000000 --- a/premake/libtomcrypt.lua +++ /dev/null @@ -1,62 +0,0 @@ -libtomcrypt = { - settings = nil -} - -function libtomcrypt.setup(settings) - if not settings.source then error("Missing source") end - - libtomcrypt.settings = settings - - if not libtomcrypt.settings.defines then libtomcrypt.settings.defines = {} end -end - -function libtomcrypt.import() - if not libtomcrypt.settings then error("Run libtomcrypt.setup first") end - - links { "libtomcrypt" } - libtomcrypt.includes() -end - -function libtomcrypt.includes() - if not libtomcrypt.settings then error("Run libtomcrypt.setup first") end - - defines(libtomcrypt.settings.defines) - includedirs { path.join(libtomcrypt.settings.source, "src/headers") } -end - -function libtomcrypt.project() - if not libtomcrypt.settings then error("Run libtomcrypt.setup first") end - - project "libtomcrypt" - language "C" - - libtomcrypt.includes() - files - { - path.join(libtomcrypt.settings.source, "src/**.c"), - } - removefiles - { - path.join(libtomcrypt.settings.source, "src/**/*tab.c"), -- included by files as necessary already afaik - path.join(libtomcrypt.settings.source, "src/encauth/ocb3/**.c"), -- fails in Visual Studio with invalid syntax - } - defines - { - "_CRT_SECURE_NO_WARNINGS", - "LTC_SOURCE", -- we are compiling from source code - } - - -- dependencies - if libtommath and libtommath.settings then - defines { "USE_LTM" } - libtommath.import() - end - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - defines { "_LIB" } - removedefines { "_DLL", "_USRDLL" } - linkoptions { "-IGNORE:4221" } - kind "StaticLib" -end diff --git a/premake/libtommath.lua b/premake/libtommath.lua deleted file mode 100644 index 910c1873..00000000 --- a/premake/libtommath.lua +++ /dev/null @@ -1,46 +0,0 @@ -libtommath = { - settings = nil -} - -function libtommath.setup(settings) - if not settings.source then error("Missing source") end - - libtommath.settings = settings - - if not libtommath.settings.defines then libtommath.settings.defines = {} end -end - -function libtommath.import() - if not libtommath.settings then error("Run libtommath.setup first") end - - links { "libtommath" } - libtommath.includes() -end - -function libtommath.includes() - if not libtommath.settings then error("Run libtommath.setup first") end - - defines(libtommath.settings.defines) - includedirs { libtommath.settings.source } -end - -function libtommath.project() - if not libtommath.settings then error("Run libtommath.setup first") end - - project "libtommath" - language "C" - - libtommath.includes() - files - { - path.join(libtommath.settings.source, "*.c"), - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - defines { "_LIB" } - removedefines { "_DLL", "_USRDLL" } - linkoptions { "-IGNORE:4221" } - kind "StaticLib" -end diff --git a/premake/mongoose.lua b/premake/mongoose.lua deleted file mode 100644 index 52e39478..00000000 --- a/premake/mongoose.lua +++ /dev/null @@ -1,42 +0,0 @@ -mongoose = { - settings = nil, -} - -function mongoose.setup(settings) - if not settings.source then error("Missing source.") end - - mongoose.settings = settings -end - -function mongoose.import() - if not mongoose.settings then error("Run mongoose.setup first") end - - links { "mongoose" } - mongoose.includes() -end - -function mongoose.includes() - if not mongoose.settings then error("Run mongoose.setup first") end - - includedirs { mongoose.settings.source } -end - -function mongoose.project() - if not mongoose.settings then error("Run mongoose.setup first") end - - project "mongoose" - language "C" - - mongoose.includes() - files - { - path.join(mongoose.settings.source, "*.c"), - path.join(mongoose.settings.source, "*.h"), - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as mongoose doesn't export anything - kind "StaticLib" -end diff --git a/premake/pdcurses.lua b/premake/pdcurses.lua deleted file mode 100644 index d8a8cd6d..00000000 --- a/premake/pdcurses.lua +++ /dev/null @@ -1,48 +0,0 @@ -pdcurses = { - settings = nil, -} - -function pdcurses.setup(settings) - if not settings.source then error("Missing source.") end - - pdcurses.settings = settings -end - -function pdcurses.import() - if not pdcurses.settings then error("Run pdcurses.setup first") end - - links { "pdcurses" } - pdcurses.includes() -end - -function pdcurses.includes() - if not pdcurses.settings then error("Run pdcurses.setup first") end - - includedirs { pdcurses.settings.source } -end - -function pdcurses.project() - if not pdcurses.settings then error("Run pdcurses.setup first") end - - project "pdcurses" - language "C" - - includedirs - { - pdcurses.settings.source, - } - - files - { - path.join(pdcurses.settings.source, "pdcurses/*.c"), - path.join(pdcurses.settings.source, "pdcurses/*.h"), - path.join(pdcurses.settings.source, "wincon/*.c"), - path.join(pdcurses.settings.source, "wincon/*.h"), - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - -- always build as static lib, as pdcurses doesn't export anything - kind "StaticLib" -end diff --git a/premake/protobuf.lua b/premake/protobuf.lua deleted file mode 100644 index 256b1b4f..00000000 --- a/premake/protobuf.lua +++ /dev/null @@ -1,68 +0,0 @@ -protobuf = { - settings = nil, -} - -function protobuf.setup(settings) - if not settings.source then error("Missing source.") end - - protobuf.settings = settings -end - -function protobuf.import() - if not protobuf.settings then error("Run protobuf.setup first") end - - links { "protobuf" } - protobuf.includes() -end - -function protobuf.includes() - if not protobuf.settings then error("Run protobuf.setup first") end - - includedirs - { - path.join(protobuf.settings.source, "src"), - } -end - -function protobuf.project() - if not protobuf.settings then error("Run protobuf.setup first") end - - project "protobuf" - language "C++" - - includedirs - { - path.join(protobuf.settings.source, "src"), - } - files - { - path.join(protobuf.settings.source, "src/**.cc"), - "./src/**.proto", - } - removefiles - { - path.join(protobuf.settings.source, "src/**/*test.cc"), - path.join(protobuf.settings.source, "src/google/protobuf/*test*.cc"), - - path.join(protobuf.settings.source, "src/google/protobuf/testing/**.cc"), - path.join(protobuf.settings.source, "src/google/protobuf/compiler/**.cc"), - - path.join(protobuf.settings.source, "src/google/protobuf/arena_nc.cc"), - path.join(protobuf.settings.source, "src/google/protobuf/util/internal/error_listener.cc"), - path.join(protobuf.settings.source, "**/*_gcc.cc"), - } - - -- Generate source code from protobuf definitions - rules { "ProtobufCompiler" } - - -- dependencies - zlib.import() - - -- not our code, ignore POSIX usage warnings for now - defines { "_SCL_SECURE_NO_WARNINGS" } - warnings "Off" - linkoptions { "-IGNORE:4221" } - - -- always build as static lib, as we include our custom classes and therefore can't perform shared linking - kind "StaticLib" -end diff --git a/premake/udis86.lua b/premake/udis86.lua deleted file mode 100644 index 46049fbd..00000000 --- a/premake/udis86.lua +++ /dev/null @@ -1,51 +0,0 @@ -udis86 = { - settings = nil -} - -function udis86.setup(settings) - if not settings.source then error("Missing source.") end - - udis86.settings = settings - - if not udis86.settings.defines then udis86.settings.defines = {} end -end - -function udis86.import() - if not udis86.settings then error("You need to call udis86.setup first") end - - links { "udis86" } - udis86.includes() -end - -function udis86.includes() - if not udis86.settings then error("You need to call udis86.setup first") end - - includedirs - { - udis86.settings.source, - path.join(udis86.settings.source, "libudis86/"), - path.join(udis86.settings.source, "../extra/udis86/"), - path.join(udis86.settings.source, "../extra/udis86/libudis86/") - } - defines(udis86.settings.defines) -end - -function udis86.project() - if not udis86.settings then error("You need to call udis86.setup first") end - - project "udis86" - language "C" - - udis86.includes() - files - { - path.join(udis86.settings.source, "libudis86/*.h"), - path.join(udis86.settings.source, "libudis86/*.c"), - path.join(udis86.settings.source, "../extra/udis86/libudis86/*.c"), - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - kind "StaticLib" -end \ No newline at end of file diff --git a/premake/zlib.lua b/premake/zlib.lua deleted file mode 100644 index 08b55e7a..00000000 --- a/premake/zlib.lua +++ /dev/null @@ -1,48 +0,0 @@ -zlib = { - settings = nil -} - -function zlib.setup(settings) - if not settings.source then error("Missing source.") end - - zlib.settings = settings - - if not zlib.settings.defines then zlib.settings.defines = {} end -end - -function zlib.import() - if not zlib.settings then error("You need to call zlib.setup first") end - - links { "zlib" } - zlib.includes() -end - -function zlib.includes() - if not zlib.settings then error("You need to call zlib.setup first") end - - includedirs { zlib.settings.source } - defines(zlib.settings.defines) -end - -function zlib.project() - if not zlib.settings then error("You need to call zlib.setup first") end - - project "zlib" - language "C" - - zlib.includes() - files - { - path.join(zlib.settings.source, "*.h"), - path.join(zlib.settings.source, "*.c"), - } - defines - { - "_CRT_SECURE_NO_DEPRECATE", - } - - -- not our code, ignore POSIX usage warnings for now - warnings "Off" - - kind "StaticLib" -end \ No newline at end of file diff --git a/premake5.lua b/premake5.lua index 28eafda6..93fa8ce9 100644 --- a/premake5.lua +++ b/premake5.lua @@ -3,6 +3,9 @@ gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD" -- Quote the given string input as a C string function cstrquote(value) + if value == nil then + return "\"\"" + end result = value:gsub("\\", "\\\\") result = result:gsub("\"", "\\\"") result = result:gsub("\n", "\\n") @@ -27,18 +30,42 @@ function vertonumarr(value, vernumber) return vernum end --- Option to allow copying the DLL file to a custom folder after build +dependencies = { + basePath = "./deps" +} + +function dependencies.load() + dir = path.join(dependencies.basePath, "premake/*.lua") + deps = os.matchfiles(dir) + + for i, dep in pairs(deps) do + dep = dep:gsub(".lua", "") + require(dep) + end +end + +function dependencies.imports() + for i, proj in pairs(dependencies) do + if type(i) == 'number' then + proj.import() + end + end +end + +function dependencies.projects() + for i, proj in pairs(dependencies) do + if type(i) == 'number' then + proj.project() + end + end +end + newoption { trigger = "copy-to", description = "Optional, copy the DLL to a custom folder after build, define the path here if wanted.", value = "PATH" } -newoption { - trigger = "no-new-structure", - description = "Do not use new virtual path structure (separating headers and source files)." -} - newoption { trigger = "copy-pdb", description = "Copy debug information for binaries as well to the path given via --copy-to." @@ -74,6 +101,11 @@ newoption { description = "Upload minidumps even for Debug builds." } +newoption { + trigger = "iw4x-zones", + description = "Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches." +} + newaction { trigger = "version", description = "Returns the version string for the current commit of the source code.", @@ -178,111 +210,51 @@ newaction { end } -depsBasePath = "./deps" - -require "premake/json11" -require "premake/libtomcrypt" -require "premake/libtommath" -require "premake/mongoose" -require "premake/pdcurses" -require "premake/protobuf" -require "premake/zlib" -require "premake/udis86" -require "premake/iw4mvm" -require "premake/dxsdk" - -json11.setup -{ - source = path.join(depsBasePath, "json11"), -} -libtomcrypt.setup -{ - defines = { - "LTC_NO_FAST", - "LTC_NO_PROTOTYPES", - "LTC_NO_RSA_BLINDING", - }, - source = path.join(depsBasePath, "libtomcrypt"), -} -libtommath.setup -{ - defines = { - "LTM_DESC", - "__STDC_IEC_559__", - }, - source = path.join(depsBasePath, "libtommath"), -} -mongoose.setup -{ - source = path.join(depsBasePath, "mongoose"), -} -pdcurses.setup -{ - source = path.join(depsBasePath, "pdcurses"), -} -protobuf.setup -{ - source = path.join(depsBasePath, "protobuf"), -} -zlib.setup -{ - defines = { - "ZLIB_CONST" - }, - source = path.join(depsBasePath, "zlib"), -} -udis86.setup -{ - source = path.join(depsBasePath, "udis86"), -} -iw4mvm.setup -{ - defines = { - "IW4X", - "DETOURS_X86", - "DETOURS_32BIT", - }, - source = path.join(depsBasePath, "iw4mvm"), -} -dxsdk.setup -{ - source = path.join(depsBasePath, "dxsdk"), -} +dependencies.load() workspace "iw4x" startproject "iw4x" location "./build" objdir "%{wks.location}/obj" - targetdir "%{wks.location}/bin/%{cfg.buildcfg}" - buildlog "%{wks.location}/obj/%{cfg.architecture}/%{cfg.buildcfg}/%{prj.name}/%{prj.name}.log" - configurations { "Debug", "Release" } + targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" + + configurations {"Debug", "Release"} + + language "C++" + cppdialect "C++17" + architecture "x86" - platforms "x86" - --exceptionhandling ("SEH") + platforms "Win32" + systemversion "latest" + symbols "On" staticruntime "On" + editandcontinue "Off" + warnings "Extra" + characterset "ASCII" - configuration "windows" - defines { "_WINDOWS", "WIN32" } + flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"} - configuration "Release*" - defines { "NDEBUG" } - flags { "MultiProcessorCompile", "LinkTimeOptimization", "No64BitChecks" } - optimize "On" + filter "platforms:Win*" + defines {"_WINDOWS", "WIN32"} + filter {} + + filter "configurations:Release" + optimize "Size" + buildoptions {"/GL"} + linkoptions {"/IGNORE:4702", "/LTCG"} + defines {"NDEBUG"} + flags {"FatalCompileWarnings", "FatalLinkWarnings"} if not _OPTIONS["force-unit-tests"] then rtti ("Off") end + filter {} - configuration "Debug*" - defines { "DEBUG", "_DEBUG" } - flags { "MultiProcessorCompile", "No64BitChecks" } + filter "configurations:Debug" optimize "Debug" - if symbols ~= nil then - symbols "On" - else - flags { "Symbols" } - end + defines {"DEBUG", "_DEBUG"} + filter {} project "iw4x" kind "SharedLib" @@ -291,7 +263,6 @@ workspace "iw4x" "./src/**.rc", "./src/**.hpp", "./src/**.cpp", - --"./src/**.proto", } includedirs { "%{prj.location}/src", @@ -307,64 +278,32 @@ workspace "iw4x" -- Debug flags if _OPTIONS["ac-disable"] then - defines { "DISABLE_ANTICHEAT" } + defines {"DISABLE_ANTICHEAT"} end if _OPTIONS["ac-debug-detections"] then - defines { "DEBUG_DETECTIONS" } + defines {"DEBUG_DETECTIONS"} end if _OPTIONS["ac-debug-load-library"] then - defines { "DEBUG_LOAD_LIBRARY" } + defines {"DEBUG_LOAD_LIBRARY"} end if _OPTIONS["force-unit-tests"] then - defines { "FORCE_UNIT_TESTS" } + defines {"FORCE_UNIT_TESTS"} end if _OPTIONS["force-minidump-upload"] then - defines { "FORCE_MINIDUMP_UPLOAD" } + defines {"FORCE_MINIDUMP_UPLOAD"} end if _OPTIONS["force-exception-handler"] then - defines { "FORCE_EXCEPTION_HANDLER" } + defines {"FORCE_EXCEPTION_HANDLER"} + end + if _OPTIONS["iw4x-zones"] then + defines {"GENERATE_IW4X_SPECIFIC_ZONES"} end -- Pre-compiled header pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives pchsource "src/STDInclude.cpp" -- real path - buildoptions { "/Zm200" } - -- Dependency libraries - json11.import() - libtomcrypt.import() - libtommath.import() - mongoose.import() - pdcurses.import() - protobuf.import() - zlib.import() - udis86.import() - --iw4mvm.import() - dxsdk.import() - - -- fix vpaths for protobuf sources - vpaths - { - ["*"] = { "./src/**" }, - --["Proto/Generated"] = { "**.pb.*" }, -- meh. - } - - -- Virtual paths - if not _OPTIONS["no-new-structure"] then - vpaths - { - ["Headers/*"] = { "./src/**.hpp" }, - ["Sources/*"] = { "./src/**.cpp" }, - ["Resource/*"] = { "./src/**.rc" }, - --["Proto/Definitions/*"] = { "./src/Proto/**.proto" }, - --["Proto/Generated/*"] = { "**.pb.*" }, -- meh. - } - end - - vpaths - { - ["Docs/*"] = { "**.txt","**.md" }, - } + dependencies.imports() -- Pre-build prebuildcommands @@ -393,74 +332,9 @@ workspace "iw4x" } end - -- Specific configurations - flags { "UndefinedIdentifiers" } - warnings "Extra" - if symbols ~= nil then - symbols "On" - else - flags { "Symbols" } - end - - configuration "Release*" - flags { - "FatalCompileWarnings", - "FatalLinkWarnings", - } - configuration {} - - --[[ - -- Generate source code from protobuf definitions - rules { "ProtobufCompiler" } - - -- Workaround: Consume protobuf generated source files - matches = os.matchfiles(path.join("src/Proto/**.proto")) - for i, srcPath in ipairs(matches) do - basename = path.getbasename(srcPath) - files - { - string.format("%%{prj.location}/src/proto/%s.pb.h", basename), - string.format("%%{prj.location}/src/proto/%s.pb.cc", basename), - } - end - includedirs - { - "%{prj.location}/src/proto", - } - filter "files:**.pb.*" - flags { - "NoPCH", - } - buildoptions { - "/wd4100", -- "Unused formal parameter" - "/wd4389", -- "Signed/Unsigned mismatch" - "/wd6011", -- "Dereferencing NULL pointer" - "/wd4125", -- "Decimal digit terminates octal escape sequence" - } - defines { - "_SCL_SECURE_NO_WARNINGS", - } - filter {} - ]] - - group "External dependencies" - json11.project() - libtomcrypt.project() - libtommath.project() - mongoose.project() - pdcurses.project() - protobuf.project() - zlib.project() - udis86.project() - --iw4mvm.project() - -workspace "*" - buildoptions { - "/std:c++latest" - } - systemversion "latest" - defines { "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" } +group "External Dependencies" +dependencies.projects() rule "ProtobufCompiler" display "Protobuf compiler" diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 30819a9f..a591720a 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -47,11 +47,7 @@ namespace Components Loader::Register(new Toast()); Loader::Register(new Party()); Loader::Register(new Zones()); - Loader::Register(new Colors()); Loader::Register(new D3D9Ex()); -#if (!defined(VLD_RPTHOOK_INSTALL) || defined(VLDEnable)) && defined(COMPILE_IW4MVM) // IW4MVM uses detours which produces memory leaks, but those are not really relevant - Loader::Register(new IW4MVM()); -#endif Loader::Register(new Logger()); Loader::Register(new Script()); Loader::Register(new Weapon()); @@ -101,6 +97,13 @@ namespace Components Loader::Register(new StructuredData()); Loader::Register(new ConnectProtocol()); Loader::Register(new StartupMessages()); + Loader::Register(new SoundMutexFix()); + Loader::Register(new Gamepad()); + Loader::Register(new Chat()); + Loader::Register(new TextRenderer()); + Loader::Register(new Movement()); + Loader::Register(new Elevators()); + Loader::Register(new ClientCommand()); Loader::Register(new Client()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 589f256a..02ac2007 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -73,7 +73,6 @@ namespace Components #include "Modules/Menus.hpp" #include "Modules/Toast.hpp" #include "Modules/Zones.hpp" -#include "Modules/Colors.hpp" #include "Modules/D3D9Ex.hpp" #include "Modules/Script.hpp" #include "Modules/Weapon.hpp" @@ -89,7 +88,6 @@ namespace Components #include "Modules/Node.hpp" #include "Modules/RCon.hpp" #include "Modules/Party.hpp" // Destroys the order, but requires network classes :D -#include "Modules/IW4MVM.hpp" #include "Modules/Logger.hpp" #include "Modules/Friends.hpp" #include "Modules/IPCPipe.hpp" @@ -130,5 +128,12 @@ namespace Components #include "Modules/ConnectProtocol.hpp" #include "Modules/StartupMessages.hpp" #include "Modules/Stats.hpp" +#include "Modules/SoundMutexFix.hpp" +#include "Modules/Chat.hpp" +#include "Modules/TextRenderer.hpp" +#include "Modules/Movement.hpp" +#include "Modules/Elevators.hpp" +#include "Modules/ClientCommand.hpp" -#include "Modules/Client.hpp" \ No newline at end of file +#include "Modules/Gamepad.hpp" +#include "Modules/Client.hpp" diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp index d20b1925..496ef495 100644 --- a/src/Components/Modules/AntiCheat.cpp +++ b/src/Components/Modules/AntiCheat.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -634,7 +634,7 @@ namespace Components LUID luid; TOKEN_PRIVILEGES tp = { 0 }; DWORD cb = sizeof(TOKEN_PRIVILEGES); - if (!LookupPrivilegeValueW(nullptr, SE_DEBUG_NAME, &luid)) return; + if (!LookupPrivilegeValueA(nullptr, SE_DEBUG_NAME, &luid)) return; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; @@ -889,12 +889,8 @@ namespace Components time(nullptr); AntiCheat::Flags = NO_FLAG; -#ifdef DISABLE_ANTICHEAT - Command::Add("penis", [](Command::Params*) - { - AntiCheat::CrashClient(); - }); -#else +#ifndef DISABLE_ANTICHEAT + Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick(); Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick(); Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/ArenaLength.cpp b/src/Components/Modules/ArenaLength.cpp index c2efd804..d7a94f09 100644 --- a/src/Components/Modules/ArenaLength.cpp +++ b/src/Components/Modules/ArenaLength.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index aa3d2faf..b3c7057a 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -1,9 +1,9 @@ -#include "STDInclude.hpp" +#include namespace Components { thread_local int AssetHandler::BypassState = 0; - bool AssetHandler::ShouldSearchTempAssets = false; + bool AssetHandler::ShouldSearchTempAssets = false; std::map AssetHandler::AssetInterfaces; std::map> AssetHandler::TypeCallbacks; Utils::Signal AssetHandler::RestrictSignal; @@ -70,20 +70,20 @@ namespace Components return header; } - Game::XAssetHeader AssetHandler::FindTemporaryAsset(Game::XAssetType type, const char* filename) - { - Game::XAssetHeader header = { nullptr }; - if (type >= Game::XAssetType::ASSET_TYPE_COUNT) return header; + Game::XAssetHeader AssetHandler::FindTemporaryAsset(Game::XAssetType type, const char* filename) + { + Game::XAssetHeader header = { nullptr }; + if (type >= Game::XAssetType::ASSET_TYPE_COUNT) return header; - auto tempPool = &AssetHandler::TemporaryAssets[type]; - auto entry = tempPool->find(filename); - if (entry != tempPool->end()) - { - header = { entry->second }; - } + auto tempPool = &AssetHandler::TemporaryAssets[type]; + auto entry = tempPool->find(filename); + if (entry != tempPool->end()) + { + header = { entry->second }; + } - return header; - } + return header; + } int AssetHandler::HasThreadBypass() { @@ -119,7 +119,6 @@ namespace Components push esi push edi - push eax pushad @@ -130,14 +129,12 @@ namespace Components popad pop eax - test al, al jnz checkTempAssets mov ecx, [esp + 18h] // Asset type mov ebx, [esp + 1Ch] // Filename - push eax pushad @@ -152,31 +149,30 @@ namespace Components popad pop eax + test eax, eax + jnz finishFound + + checkTempAssets: + mov al, AssetHandler::ShouldSearchTempAssets // check to see if enabled + test eax, eax + jz finishOriginal + + mov ecx, [esp + 18h] // Asset type + mov ebx, [esp + 1Ch] // Filename + + push ebx + push ecx + + call AssetHandler::FindTemporaryAsset + + add esp, 8h test eax, eax jnz finishFound - - checkTempAssets: - mov al, AssetHandler::ShouldSearchTempAssets // check to see if enabled - test eax, eax - jz finishOriginal - mov ecx, [esp + 18h] // Asset type - mov ebx, [esp + 1Ch] // Filename - - push ebx - push ecx - - call AssetHandler::FindTemporaryAsset - - add esp, 8h - - test eax, eax - jnz finishFound - - finishOriginal: + finishOriginal: // Asset not found using custom handlers or in temp assets or bypasses were enabled - // redirect to DB_FindXAssetHeader + // redirect to DB_FindXAssetHeader mov ebx, ds:6D7190h // InterlockedDecrement mov eax, 40793Bh jmp eax @@ -286,7 +282,7 @@ namespace Components push [esp + 2Ch] push [esp + 2Ch] call AssetHandler::IsAssetEligible - add esp, 08h + add esp, 8h mov [esp + 20h], eax popad @@ -295,13 +291,13 @@ namespace Components test al, al jz doNotLoad - mov eax, [esp + 8] + mov eax, [esp + 8h] sub esp, 14h mov ecx, 5BB657h jmp ecx doNotLoad: - mov eax, [esp + 8] + mov eax, [esp + 8h] retn } } @@ -498,7 +494,7 @@ namespace Components { this->reallocateEntryPool(); - Dvar::Register("r_noVoid", false, Game::DVAR_FLAG_SAVED, "Disable void model (red fx)"); + Dvar::Register("r_noVoid", false, Game::DVAR_ARCHIVE, "Disable void model (red fx)"); AssetHandler::ClearTemporaryAssets(); @@ -546,10 +542,10 @@ namespace Components for (int i = 0; i < vertexdecl->streamCount; i++) { routingData.push_back(json11::Json::object - { - { "source", (int)vertexdecl->routing.data[i].source }, - { "dest", (int)vertexdecl->routing.data[i].dest }, - }); + { + { "source", (int)vertexdecl->routing.data[i].source }, + { "dest", (int)vertexdecl->routing.data[i].dest }, + }); } std::vector declData; @@ -770,7 +766,7 @@ namespace Components Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LEADERBOARD, 500); AssetHandler::RegisterInterface(new Assets::IFont_s()); - AssetHandler::RegisterInterface(new Assets::IWeapon()); + AssetHandler::RegisterInterface(new Assets::IWeapon()); AssetHandler::RegisterInterface(new Assets::IXModel()); AssetHandler::RegisterInterface(new Assets::IFxWorld()); AssetHandler::RegisterInterface(new Assets::IMapEnts()); @@ -781,9 +777,9 @@ namespace Components AssetHandler::RegisterInterface(new Assets::ISndCurve()); AssetHandler::RegisterInterface(new Assets::IMaterial()); AssetHandler::RegisterInterface(new Assets::IMenuList()); - AssetHandler::RegisterInterface(new Assets::IclipMap_t()); + AssetHandler::RegisterInterface(new Assets::IclipMap_t()); AssetHandler::RegisterInterface(new Assets::ImenuDef_t()); - AssetHandler::RegisterInterface(new Assets::ITracerDef()); + AssetHandler::RegisterInterface(new Assets::ITracerDef()); AssetHandler::RegisterInterface(new Assets::IPhysPreset()); AssetHandler::RegisterInterface(new Assets::IXAnimParts()); AssetHandler::RegisterInterface(new Assets::IFxEffectDef()); diff --git a/src/Components/Modules/AssetHandler.hpp b/src/Components/Modules/AssetHandler.hpp index 5207a0f7..5d0426b2 100644 --- a/src/Components/Modules/AssetHandler.hpp +++ b/src/Components/Modules/AssetHandler.hpp @@ -39,13 +39,13 @@ namespace Components static void ResetBypassState(); - static void ExposeTemporaryAssets(bool expose); + static void ExposeTemporaryAssets(bool expose); static void OffsetToAlias(Utils::Stream::Offset* offset); private: static thread_local int BypassState; - static bool ShouldSearchTempAssets; + static bool ShouldSearchTempAssets; static std::map TemporaryAssets[Game::XAssetType::ASSET_TYPE_COUNT]; @@ -60,7 +60,7 @@ namespace Components static void RegisterInterface(IAsset* iAsset); static Game::XAssetHeader FindAsset(Game::XAssetType type, const char* filename); - static Game::XAssetHeader FindTemporaryAsset(Game::XAssetType type, const char* filename); + static Game::XAssetHeader FindTemporaryAsset(Game::XAssetType type, const char* filename); static bool IsAssetEligible(Game::XAssetType type, Game::XAssetHeader* asset); static void FindAssetStub(); static void AddAssetStub(); diff --git a/src/Components/Modules/AssetInterfaces/IComWorld.cpp b/src/Components/Modules/AssetInterfaces/IComWorld.cpp index a12dd771..4854c3a6 100644 --- a/src/Components/Modules/AssetInterfaces/IComWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IComWorld.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_COMMAP_VERSION 0 diff --git a/src/Components/Modules/AssetInterfaces/IFont_s.cpp b/src/Components/Modules/AssetInterfaces/IFont_s.cpp index 0e154c1a..2a2d7fde 100644 --- a/src/Components/Modules/AssetInterfaces/IFont_s.cpp +++ b/src/Components/Modules/AssetInterfaces/IFont_s.cpp @@ -1,7 +1,82 @@ -#include "STDInclude.hpp" +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include namespace Assets { + namespace + { + int PackFonts(const uint8_t* data, std::vector& charset, Game::Glyph* glyphs, float pixel_height, unsigned char* pixels, int pw, int ph, int yOffset) + { + stbtt_fontinfo f; + f.userdata = NULL; + + if (!stbtt_InitFont(&f, data, 0)) + return -1; + + std::memset(pixels, 0, pw * ph); + + int x = 1, y = 1, bottom_y = 1; + + float scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + int i = 0; + + for (auto& ch : charset) + { + int advance, lsb, x0, y0, x1, y1, gw, gh; + + int g = stbtt_FindGlyphIndex(&f, ch); + + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale, scale, &x0, &y0, &x1, &y1); + + gw = x1 - x0; + gh = y1 - y0; + + if (x + gw + 1 >= pw) + { + // Advance to next row + y = bottom_y; + x = 1; + } + + if (y + gh + 1 >= ph) + { + // Check if we have ran out of the room + return -i; + } + + stbtt_MakeGlyphBitmap(&f, pixels + x + y * pw, gw, gh, pw, scale, scale, g); + + auto& glyph = glyphs[i++]; + + glyph.letter = ch; + glyph.s0 = x / static_cast(pw); + glyph.s1 = (x + gw) / static_cast(pw); + glyph.t0 = y / static_cast(ph); + glyph.t1 = (y + gh) / static_cast(ph); + glyph.pixelWidth = static_cast(gw); + glyph.pixelHeight = static_cast(gh); + glyph.x0 = static_cast(x0); + glyph.y0 = static_cast(y0 + yOffset); + glyph.dx = static_cast(std::roundf(scale * advance)); + + // Advance to next col + x = x + gw + 1; + + // Expand bottom of current row if current glyph is bigger + if (y + gh + 1 > bottom_y) + { + bottom_y = y + gh + 1; + } + } + + return bottom_y; + } + } + void IFont_s::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { Game::Font_s *asset = header.font; @@ -17,6 +92,164 @@ namespace Assets } } + void IFont_s::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) + { + Components::FileSystem::File fontDefFile(Utils::String::VA("%s.json", name.data())); + Components::FileSystem::File fontFile(Utils::String::VA("%s.ttf", name.data())); + + if (fontDefFile.exists() && fontFile.exists()) + { + std::string errors; + auto fontDef = json11::Json::parse(fontDefFile.getBuffer(), errors); + + if (!errors.empty()) + { + Components::Logger::Error("Font define %s is broken: %s.", name.data(), errors.data()); + return; + } + + if (!fontDef.is_object()) + { + Components::Logger::Error("Font define %s is invaild.", name.data(), errors.data()); + return; + } + + int w = fontDef["textureWidth"].int_value(); + int h = fontDef["textureHeight"].int_value(); + + int size = fontDef["size"].int_value(); + int yOffset = fontDef["yOffset"].int_value(); + + auto* pixels = builder->getAllocator()->allocateArray(w * h); + + // Setup assets + const auto* texName = builder->getAllocator()->duplicateString(Utils::String::VA("if_%s", name.data() + 6 /* skip "fonts/" */)); + const auto* fontName = builder->getAllocator()->duplicateString(name.data()); + const auto* glowMaterialName = builder->getAllocator()->duplicateString(Utils::String::VA("%s_glow", name.data())); + + auto* image = builder->getAllocator()->allocate(); + std::memcpy(image, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_IMAGE, "gamefonts_pc").image, sizeof(Game::GfxImage)); + + image->name = texName; + + auto* material = builder->getAllocator()->allocate(); + std::memcpy(material, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc").material, sizeof(Game::Material)); + + material->textureTable = builder->getAllocator()->allocate(); + material->textureTable->u.image = image; + material->info.name = fontName; + + auto* glowMaterial = builder->getAllocator()->allocate(); + std::memcpy(glowMaterial, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc_glow").material, sizeof(Game::Material)); + + glowMaterial->textureTable = material->textureTable; + glowMaterial->info.name = glowMaterialName; + + std::vector charset; + + if (fontDef["charset"].is_array()) + { + for (auto& ch : fontDef["charset"].array_items()) + charset.push_back(static_cast(ch.int_value())); + + // order matters + std::sort(charset.begin(), charset.end()); + + for (uint16_t i = 32; i < 128; i++) + { + if (std::find(charset.begin(), charset.end(), i) == charset.end()) + { + Components::Logger::Error("Font %s missing codepoint %d.", name.data(), i); + } + } + } + else + { + for (uint16_t i = 32; i < 128; i++) + charset.push_back(i); + } + + auto* font = builder->getAllocator()->allocate(); + + font->fontName = fontName; + font->pixelHeight = size; + font->material = material; + font->glowMaterial = glowMaterial; + font->glyphCount = charset.size(); + font->glyphs = builder->getAllocator()->allocateArray(charset.size()); + + // Generate glyph data + int result = PackFonts(reinterpret_cast(fontFile.getBuffer().data()), charset, font->glyphs, static_cast(size), pixels, w, h, yOffset); + + if (result == -1) + { + Components::Logger::Error("Truetype font %s is broken.", name.data()); + } + else if (result < 0) + { + Components::Logger::Error("Texture size of font %s is not enough.", name.data()); + } + else if(h - result > size) + { + Components::Logger::Print("Warn: Texture of font %s have too much left over space: %d\n", name.data(), h - result); + } + + header->font = font; + + // Save generated materials + Game::XAssetHeader tmpHeader; + + tmpHeader.image = image; + Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_IMAGE, tmpHeader); + + tmpHeader.material = material; + Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader); + + tmpHeader.material = glowMaterial; + Components::AssetHandler::StoreTemporaryAsset(Game::ASSET_TYPE_MATERIAL, tmpHeader); + + // Save generated image + Utils::IO::CreateDir("userraw\\images"); + + int fileSize = w * h * 4; + int iwiHeaderSize = static_cast(sizeof(Game::GfxImageFileHeader)); + + Game::GfxImageFileHeader iwiHeader = + { + { 'I', 'W', 'i' }, + /* version */ + 8, + /* flags */ + 2, + /* format */ + Game::IMG_FORMAT_BITMAP_RGBA, + 0, + /* dimensions(x, y, z) */ + { static_cast(w), static_cast(h), 1 }, + /* fileSizeForPicmip (mipSize in bytes + sizeof(GfxImageFileHeader)) */ + { fileSize + iwiHeaderSize, fileSize, fileSize, fileSize } + }; + + std::string outIwi; + outIwi.resize(fileSize + sizeof(Game::GfxImageFileHeader)); + + std::memcpy(outIwi.data(), &iwiHeader, sizeof(Game::GfxImageFileHeader)); + + // Generate RGBA data + auto* rgbaPixels = outIwi.data() + sizeof(Game::GfxImageFileHeader); + + for (int i = 0; i < w * h * 4; i += 4) + { + rgbaPixels[i + 0] = static_cast(255); + rgbaPixels[i + 1] = static_cast(255); + rgbaPixels[i + 2] = static_cast(255); + rgbaPixels[i + 3] = static_cast(pixels[i / 4]); + } + + Utils::IO::WriteFile(Utils::String::VA("userraw\\images\\%s.iwi", texName), outIwi); + } + } + void IFont_s::save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) { AssertSize(Game::Font_s, 24); diff --git a/src/Components/Modules/AssetInterfaces/IFont_s.hpp b/src/Components/Modules/AssetInterfaces/IFont_s.hpp index 3d9edda2..1a6161cb 100644 --- a/src/Components/Modules/AssetInterfaces/IFont_s.hpp +++ b/src/Components/Modules/AssetInterfaces/IFont_s.hpp @@ -9,6 +9,6 @@ namespace Assets virtual void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; virtual void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; - // virtual void load(Game::XAssetHeader* header, std::string name, Components::ZoneBuilder::Zone* builder) override; + virtual void load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) override; }; } diff --git a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp index 7cd2c87d..0cdd6b3a 100644 --- a/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxEffectDef.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_FX_VERSION 1 @@ -7,12 +7,13 @@ namespace Assets void IFxEffectDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { if (!header->data) this->loadEfx(header, name, builder); // Check if we have an editor fx - if (!header->data /*&& !builder->isPrimaryAsset()*/) this->loadNative(header, name, builder); // Check if there is a native one if (!header->data) this->loadBinary(header, name, builder); // Check if we need to import a new one into the game + if (!header->data /*&& !builder->isPrimaryAsset()*/) this->loadNative(header, name, builder); // Check if there is a native one } void IFxEffectDef::loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader) { + switch (elemType) { case Game::FX_ELEM_TYPE_MODEL: @@ -34,10 +35,7 @@ namespace Assets if (visuals->soundName) { visuals->soundName = reader->readCString(); - visuals->soundName = "null"; - Components::Logger::Print("Unable to load sounds yet!\n"); } - break; } diff --git a/src/Components/Modules/AssetInterfaces/IFxWorld.cpp b/src/Components/Modules/AssetInterfaces/IFxWorld.cpp index 93bbc709..9473dfd0 100644 --- a/src/Components/Modules/AssetInterfaces/IFxWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IFxWorld.cpp @@ -1,4 +1,4 @@ -#include "StdInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp b/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp index 685ebaa7..17c7702e 100644 --- a/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp +++ b/src/Components/Modules/AssetInterfaces/IGameWorldMp.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IGameWorldSp.cpp b/src/Components/Modules/AssetInterfaces/IGameWorldSp.cpp index 07a79b9c..04fba9c2 100644 --- a/src/Components/Modules/AssetInterfaces/IGameWorldSp.cpp +++ b/src/Components/Modules/AssetInterfaces/IGameWorldSp.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp index f39a7ea4..f0981c9e 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxImage.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxImage.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_IMG_VERSION "0" diff --git a/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp b/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp index dec55171..b842d9d9 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxLightDef.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_LIGHT_VERSION "0" diff --git a/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp b/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp index 7850c41e..b581c8c1 100644 --- a/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp +++ b/src/Components/Modules/AssetInterfaces/IGfxWorld.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_GFXMAP_VERSION 1 diff --git a/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp b/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp index f2ddb8b0..303212f0 100644 --- a/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp +++ b/src/Components/Modules/AssetInterfaces/ILoadedSound.cpp @@ -1,10 +1,10 @@ -#include "STDInclude.hpp" +#include namespace Assets { void ILoadedSound::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File soundFile(Utils::String::VA("sounds/%s", name.data())); + Components::FileSystem::File soundFile(Utils::String::VA("loaded_sound/%s", name.data())); if (!soundFile.exists()) { header->loadSnd = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).loadSnd; @@ -60,8 +60,8 @@ namespace Assets } sound->sound.info.channels = reader.read(); - sound->sound.info.samples = reader.read(); sound->sound.info.rate = reader.read(); + sound->sound.info.samples = reader.read(); sound->sound.info.block_size = reader.read(); sound->sound.info.bits = reader.read(); diff --git a/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp b/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp index f43e8766..9a906b47 100644 --- a/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp +++ b/src/Components/Modules/AssetInterfaces/ILocalizeEntry.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IMapEnts.cpp b/src/Components/Modules/AssetInterfaces/IMapEnts.cpp index ee7f525b..0b639a58 100644 --- a/src/Components/Modules/AssetInterfaces/IMapEnts.cpp +++ b/src/Components/Modules/AssetInterfaces/IMapEnts.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IMaterial.cpp b/src/Components/Modules/AssetInterfaces/IMaterial.cpp index f401eccb..9ccddf71 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterial.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterial.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_MAT_VERSION "1" @@ -29,6 +29,33 @@ namespace Assets "_add_lin_nofog", }; + std::map techSetCorrespondance = { + {"effect", "effect_blend"}, + {"effect", "effect_blend"}, + {"effect_nofog", "effect_blend_nofog"}, + {"effect_zfeather", "effect_zfeather_blend"}, + + {"wc_unlit_add", "wc_unlit_add_lin"}, + {"wc_unlit_distfalloff", "wc_unlit_distfalloff_replace"}, + {"wc_unlit_multiply", "wc_unlit_multiply_lin"}, + {"wc_unlit_falloff_add", "wc_unlit_falloff_add_lin_ua"}, + {"wc_unlit", "wc_unlit_replace_lin"}, + {"wc_unlit_alphatest", "wc_unlit_blend_lin"}, + {"wc_unlit_blend", "wc_unlit_blend_lin_ua"}, + {"wc_unlit_replace", "wc_unlit_replace_lin"}, + + {"mc_unlit_replace", "mc_unlit_replace_lin"}, + {"mc_unlit_nofog", "mc_unlit_blend_nofog_ua"}, + {"mc_unlit", "mc_unlit_replace_lin_nocast"}, + {"mc_unlit_alphatest", "mc_unlit_blend_lin"} + /*, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""}, + {"", ""},*/ + }; + Components::FileSystem::File materialFile(Utils::String::VA("materials/%s.iw4xMaterial", name.data())); if (!materialFile.exists()) return; @@ -56,38 +83,41 @@ namespace Assets if (asset->techniqueSet) { - std::string techset = reader.readString(); - if (!techset.empty() && techset.front() == ',') techset.erase(techset.begin()); - asset->techniqueSet = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techset.data(), builder).techniqueSet; + std::string techsetName = reader.readString(); + if (!techsetName.empty() && techsetName.front() == ',') techsetName.erase(techsetName.begin()); + asset->techniqueSet = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, techsetName.data(), builder).techniqueSet; if (!asset->techniqueSet) { // Workaround for effect techsets having _nofog suffix std::string suffix; - if (Utils::String::StartsWith(techset, "effect_") && Utils::String::EndsWith(techset, "_nofog")) + if (Utils::String::StartsWith(techsetName, "effect_") && Utils::String::EndsWith(techsetName, "_nofog")) { suffix = "_nofog"; - Utils::String::Replace(techset, suffix, ""); + Utils::String::Replace(techsetName, suffix, ""); } for (int i = 0; i < ARRAYSIZE(techsetSuffix); ++i) { - Game::MaterialTechniqueSet* techsetPtr = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, (techset + techsetSuffix[i] + suffix).data(), builder).techniqueSet; + Game::MaterialTechniqueSet* techsetPtr = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, (techsetName + techsetSuffix[i] + suffix).data(), builder).techniqueSet; if (techsetPtr) { asset->techniqueSet = techsetPtr; if (asset->techniqueSet->name[0] == ',') continue; // Try to find a better one - Components::Logger::Print("Techset '%s' has been mapped to '%s'\n", techset.data(), asset->techniqueSet->name); + Components::Logger::Print("Techset '%s' has been mapped to '%s'\n", techsetName.data(), asset->techniqueSet->name); break; } } } + else { + Components::Logger::Print("Techset %s exists with the same name in iw4, and was mapped 1:1 with %s\n", techsetName.data(), asset->techniqueSet->name); + } if (!asset->techniqueSet) { - Components::Logger::Error("Missing techset: '%s' not found", techset.data()); + Components::Logger::Error("Missing techset: '%s' not found", techsetName.data()); } } @@ -165,11 +195,11 @@ namespace Assets std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48); asset->constantCount = header.material->constantCount; asset->constantTable = header.material->constantTable; - + Components::Logger::Print("For %s, copied constants & statebits from %s\n", asset->info.name, header.material->info.name); replacementFound = true; } } - }, false, false); + }, false); if (!replacementFound) { @@ -185,12 +215,14 @@ namespace Assets if (!t1->techniques[i] && !t2->techniques[i]) continue;; if (!t1->techniques[i] || !t2->techniques[i]) return false; - if (t1->techniques[i]->flags != t1->techniques[i]->flags) return false; + // Apparently, this is really not that important + //if (t1->techniques[i]->flags != t2->techniques[i]->flags) return false; } return true; }; + Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, techsetMatches](Game::XAssetEntry* entry) { if (!replacementFound) @@ -204,13 +236,60 @@ namespace Assets replacementFound = true; } } - }, false, false); + }, false); } if (!replacementFound && asset->techniqueSet) { - Components::Logger::Print("No replacement found for material %s with techset %s\n", asset->info.name, asset->techniqueSet->name); + std::string techName = asset->techniqueSet->name; + if (techSetCorrespondance.find(techName) != techSetCorrespondance.end()) { + auto iw4TechSetName = techSetCorrespondance[techName]; + Game::XAssetEntry* iw4TechSet = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_TECHNIQUE_SET, iw4TechSetName.data()); + + if (iw4TechSet) + { + Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [asset, iw4TechSet](Game::XAssetEntry* entry) + { + if (!replacementFound) + { + Game::XAssetHeader header = entry->asset.header; + + if (header.material->techniqueSet == iw4TechSet->asset.header.techniqueSet) + { + Components::Logger::Print("Material %s with techset %s has been mapped to %s (last chance!), taking the sort key of material %s\n", + asset->info.name, asset->techniqueSet->name, + header.material->techniqueSet->name, header.material->info.name); + + asset->info.sortKey = header.material->info.sortKey; + asset->techniqueSet = iw4TechSet->asset.header.techniqueSet; + + // this is terrible! + asset->stateBitsCount = header.material->stateBitsCount; + asset->stateBitsTable = header.material->stateBitsTable; + std::memcpy(asset->stateBitsEntry, header.material->stateBitsEntry, 48); + asset->constantCount = header.material->constantCount; + asset->constantTable = header.material->constantTable; + + replacementFound = true; + } + } + }, false); + + if (!replacementFound) + { + Components::Logger::Print("Could not find any loaded material with techset %s (in replacement of %s), so I cannot set the sortkey for material %s\n", iw4TechSetName.data(), asset->techniqueSet->name, asset->info.name); + } + } + else + { + Components::Logger::Print("Could not find any loaded techset with iw4 name %s for iw3 techset %s\n", iw4TechSetName.data(), asset->techniqueSet->name); + } + } + else + { + Components::Logger::Print("Could not match iw3 techset %s with any of the techsets I know! This is a critical error, there's a good chance the map will not be playable.\n", techName.data()); + } } if (!reader.end()) diff --git a/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp b/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp index ecf6c330..09023c78 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialPixelShader.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_TECHSET_VERSION "0" diff --git a/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp b/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp index e390cfa3..0e7e481e 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialTechniqueSet.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_TECHSET_VERSION "0" diff --git a/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp b/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp index fa51a556..d4441653 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialVertexDeclaration.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_TECHSET_VERSION "0" diff --git a/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp b/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp index c6d5ee04..ab9bb2c5 100644 --- a/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp +++ b/src/Components/Modules/AssetInterfaces/IMaterialVertexShader.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_TECHSET_VERSION "0" diff --git a/src/Components/Modules/AssetInterfaces/IMenuList.cpp b/src/Components/Modules/AssetInterfaces/IMenuList.cpp index ecf0aad4..44c7e5d9 100644 --- a/src/Components/Modules/AssetInterfaces/IMenuList.cpp +++ b/src/Components/Modules/AssetInterfaces/IMenuList.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IPhysCollmap.cpp b/src/Components/Modules/AssetInterfaces/IPhysCollmap.cpp index 6c84f43f..ed5c9c0c 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysCollmap.cpp +++ b/src/Components/Modules/AssetInterfaces/IPhysCollmap.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp index cf0df51d..998ba165 100644 --- a/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp +++ b/src/Components/Modules/AssetInterfaces/IPhysPreset.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IRawFile.cpp b/src/Components/Modules/AssetInterfaces/IRawFile.cpp index 07904e36..19638490 100644 --- a/src/Components/Modules/AssetInterfaces/IRawFile.cpp +++ b/src/Components/Modules/AssetInterfaces/IRawFile.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/ISndCurve.cpp b/src/Components/Modules/AssetInterfaces/ISndCurve.cpp index 1ecca8f0..205fe210 100644 --- a/src/Components/Modules/AssetInterfaces/ISndCurve.cpp +++ b/src/Components/Modules/AssetInterfaces/ISndCurve.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IStringTable.cpp b/src/Components/Modules/AssetInterfaces/IStringTable.cpp index d58904f8..c6c1d4cc 100644 --- a/src/Components/Modules/AssetInterfaces/IStringTable.cpp +++ b/src/Components/Modules/AssetInterfaces/IStringTable.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IStructuredDataDefSet.cpp b/src/Components/Modules/AssetInterfaces/IStructuredDataDefSet.cpp index b0d159a0..d96757a6 100644 --- a/src/Components/Modules/AssetInterfaces/IStructuredDataDefSet.cpp +++ b/src/Components/Modules/AssetInterfaces/IStructuredDataDefSet.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/ITracerDef.cpp b/src/Components/Modules/AssetInterfaces/ITracerDef.cpp index 92bcf361..e94fb86a 100644 --- a/src/Components/Modules/AssetInterfaces/ITracerDef.cpp +++ b/src/Components/Modules/AssetInterfaces/ITracerDef.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index 92135700..abc6f5d3 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { @@ -15,9 +15,9 @@ namespace Assets } } - void IWeapon::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) - { - Game::WeaponCompleteDef* asset = header.weapon; + void IWeapon::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) + { + Game::WeaponCompleteDef* asset = header.weapon; // convert all script strings if (asset->hideTags) @@ -64,7 +64,7 @@ namespace Assets builder->addScriptString(asset->weapDef->notetrackRumbleMapValues[i]); } } - + // now load all sub-assets properly if (asset->killIcon) builder->loadAsset(Game::XAssetType::ASSET_TYPE_MATERIAL, asset->killIcon); @@ -119,7 +119,80 @@ namespace Assets if (asset->weapDef->projBeaconEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projBeaconEffect); if (asset->weapDef->projIgnitionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projIgnitionEffect); if (asset->weapDef->turretOverheatEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->turretOverheatEffect); - } + +#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##) + + LoadWeapSound(pickupSound); + LoadWeapSound(pickupSoundPlayer); + LoadWeapSound(ammoPickupSound); + LoadWeapSound(ammoPickupSoundPlayer); + LoadWeapSound(projectileSound); + LoadWeapSound(pullbackSound); + LoadWeapSound(pullbackSoundPlayer); + LoadWeapSound(fireSound); + LoadWeapSound(fireSoundPlayer); + LoadWeapSound(fireSoundPlayerAkimbo); + LoadWeapSound(fireLoopSound); + LoadWeapSound(fireLoopSoundPlayer); + LoadWeapSound(fireStopSound); + LoadWeapSound(fireStopSoundPlayer); + LoadWeapSound(fireLastSound); + LoadWeapSound(fireLastSoundPlayer); + LoadWeapSound(emptyFireSound); + LoadWeapSound(emptyFireSoundPlayer); + LoadWeapSound(meleeSwipeSound); + LoadWeapSound(meleeSwipeSoundPlayer); + LoadWeapSound(meleeHitSound); + LoadWeapSound(meleeMissSound); + LoadWeapSound(rechamberSound); + LoadWeapSound(rechamberSoundPlayer); + LoadWeapSound(reloadSound); + LoadWeapSound(reloadSoundPlayer); + LoadWeapSound(reloadEmptySound); + LoadWeapSound(reloadEmptySoundPlayer); + LoadWeapSound(reloadStartSound); + LoadWeapSound(reloadStartSoundPlayer); + LoadWeapSound(reloadEndSound); + LoadWeapSound(reloadEndSoundPlayer); + LoadWeapSound(detonateSound); + LoadWeapSound(detonateSoundPlayer); + LoadWeapSound(nightVisionWearSound); + LoadWeapSound(nightVisionWearSoundPlayer); + LoadWeapSound(nightVisionRemoveSound); + LoadWeapSound(nightVisionRemoveSoundPlayer); + LoadWeapSound(altSwitchSound); + LoadWeapSound(altSwitchSoundPlayer); + LoadWeapSound(raiseSound); + LoadWeapSound(raiseSoundPlayer); + LoadWeapSound(firstRaiseSound); + LoadWeapSound(firstRaiseSoundPlayer); + LoadWeapSound(putawaySound); + LoadWeapSound(putawaySoundPlayer); + LoadWeapSound(scanSound); + + if (asset->weapDef->bounceSound) + { + for (size_t i = 0; i < 31; i++) + { + LoadWeapSound(bounceSound[i]); + } + } + + LoadWeapSound(projExplosionSound); + LoadWeapSound(projDudSound); + LoadWeapSound(projIgnitionSound); + LoadWeapSound(turretOverheatSound); + LoadWeapSound(turretBarrelSpinMaxSnd); + + for (size_t i = 0; i < 4; i++) + { + LoadWeapSound(turretBarrelSpinUpSnd[i]); + LoadWeapSound(turretBarrelSpinDownSnd[i]); + } + + LoadWeapSound(missileConeSoundAlias); + LoadWeapSound(missileConeSoundAliasAtBase); + } void IWeapon::writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer) { @@ -274,9 +347,9 @@ namespace Assets { buffer->align(Utils::Stream::ALIGN_4); int* ptrs = buffer->dest(); - buffer->saveMax(37 * sizeof(Game::snd_alias_list_t*)); + buffer->saveMax(31 * sizeof(Game::snd_alias_list_t*)); - for (int i = 0; i < 37; i++) + for (int i = 0; i < 31; i++) { if (!def->bounceSound[i]) { @@ -433,14 +506,16 @@ namespace Assets if (def->projExplosionSound) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->projExplosionSound->aliasName); Utils::Stream::ClearPointer(&dest->projExplosionSound); } if (def->projDudSound) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->projDudSound->aliasName); Utils::Stream::ClearPointer(&dest->projDudSound); } @@ -476,7 +551,8 @@ namespace Assets if (def->projIgnitionSound) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->projIgnitionSound->aliasName); Utils::Stream::ClearPointer(&dest->projIgnitionSound); } @@ -551,7 +627,8 @@ namespace Assets if (def->turretOverheatSound) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->turretOverheatSound->aliasName); Utils::Stream::ClearPointer(&dest->turretOverheatSound); } @@ -569,7 +646,8 @@ namespace Assets if (def->turretBarrelSpinMaxSnd) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->turretBarrelSpinMaxSnd->aliasName); Utils::Stream::ClearPointer(&dest->turretBarrelSpinMaxSnd); } @@ -577,7 +655,8 @@ namespace Assets for (int i = 0; i < 4; i++) { if (!def->turretBarrelSpinUpSnd[i]) continue; - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->turretBarrelSpinUpSnd[i]->aliasName); Utils::Stream::ClearPointer(&dest->turretBarrelSpinUpSnd[i]); } @@ -585,21 +664,24 @@ namespace Assets for (int i = 0; i < 4; i++) { if (!def->turretBarrelSpinDownSnd[i]) continue; - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->turretBarrelSpinDownSnd[i]->aliasName); Utils::Stream::ClearPointer(&dest->turretBarrelSpinDownSnd[i]); } if (def->missileConeSoundAlias) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->missileConeSoundAlias->aliasName); Utils::Stream::ClearPointer(&dest->missileConeSoundAlias); } if (def->missileConeSoundAliasAtBase) { - buffer->saveMax(4); + buffer->align(Utils::Stream::ALIGN_4); + buffer->saveMax(sizeof(Game::snd_alias_list_t*)); buffer->saveString(def->missileConeSoundAliasAtBase->aliasName); Utils::Stream::ClearPointer(&dest->missileConeSoundAliasAtBase); } diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index 4071fb6e..940b5858 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_ANIM_VERSION 1 diff --git a/src/Components/Modules/AssetInterfaces/IXModel.cpp b/src/Components/Modules/AssetInterfaces/IXModel.cpp index cdcee206..69be49ce 100644 --- a/src/Components/Modules/AssetInterfaces/IXModel.cpp +++ b/src/Components/Modules/AssetInterfaces/IXModel.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define IW4X_MODEL_VERSION 5 @@ -84,13 +84,14 @@ namespace Assets void IXModel::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - if (!builder->isPrimaryAsset()) + Components::FileSystem::File modelFile(Utils::String::VA("xmodel/%s.iw4xModel", name.data())); + + if (!builder->isPrimaryAsset() && (!Components::ZoneBuilder::PreferDiskAssetsDvar.get() || !modelFile.exists())) { header->model = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).model; if (header->model) return; } - Components::FileSystem::File modelFile(Utils::String::VA("xmodel/%s.iw4xModel", name.data())); if (modelFile.exists()) { diff --git a/src/Components/Modules/AssetInterfaces/IXModelSurfs.cpp b/src/Components/Modules/AssetInterfaces/IXModelSurfs.cpp index 6c67932b..686eb3b0 100644 --- a/src/Components/Modules/AssetInterfaces/IXModelSurfs.cpp +++ b/src/Components/Modules/AssetInterfaces/IXModelSurfs.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp index 3904ea74..df933bbc 100644 --- a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp +++ b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp @@ -1,6 +1,6 @@ -#include "StdInclude.hpp" +#include -#define IW4X_CLIPMAP_VERSION 1 +#define IW4X_CLIPMAP_VERSION 2 namespace Assets { @@ -587,12 +587,12 @@ namespace Assets Game::clipMap_t* orgClipMap = nullptr; Game::DB_EnumXAssets(Game::XAssetType::ASSET_TYPE_CLIPMAP_MP, [](Game::XAssetHeader header, void* clipMap) - { - if (!*reinterpret_cast(clipMap)) { - *reinterpret_cast(clipMap) = header.clipMap; - } - }, &orgClipMap, false); + if (!*reinterpret_cast(clipMap)) + { + *reinterpret_cast(clipMap) = header.clipMap; + } + }, &orgClipMap, false); if (orgClipMap) std::memcpy(clipMap, orgClipMap, sizeof Game::clipMap_t); @@ -605,7 +605,7 @@ namespace Assets } int version = reader.read(); - if (version != IW4X_CLIPMAP_VERSION) + if (version > IW4X_CLIPMAP_VERSION) { Components::Logger::Error(0, "Reading clipmap '%s' failed, expected version is %d, but it was %d!", name.data(), IW4X_CLIPMAP_VERSION, version); } @@ -881,33 +881,38 @@ namespace Assets clipMap->smodelNodeCount = reader.read(); clipMap->smodelNodes = reader.readArray(clipMap->smodelNodeCount); - clipMap->checksum = reader.read(); - clipMap->mapEnts = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MAP_ENTS, Utils::String::VA("maps/mp/%s.d3dbsp", name.data()), builder).mapEnts; // add triggers to mapEnts - /* - std::list slabs; - std::list hulls; - std::list models; + if (version >= 2) { + if (clipMap->numSubModels > 0) { + clipMap->mapEnts->trigger.count = clipMap->numSubModels; + clipMap->mapEnts->trigger.hullCount = clipMap->numSubModels; - for (int i = 0; i < clipMap->numCModels; ++i) - { - Game::cLeafBrushNode_t* node = &clipMap->cLeafBrushNodes[clipMap->cModels[i].leaf.leafBrushNode]; - if (!node->leafBrushCount) continue; // skip empty brushes + Game::TriggerHull* hulls = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.hullCount); + Game::TriggerModel* models = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.count); - int baseHull = hulls.size(); - for (int j = 0; j < node->leafBrushCount; ++j) - { - Game::cbrush_t* brush = &clipMap->cBrushes[node->data.leaf.brushes[j]]; - int baseSlab = slabs.size(); - for (int k = 0; k < brush->numsides; ++k) + for (unsigned int i = 0; i < clipMap->numSubModels; ++i) { - Game::TriggerSlab curSlab; + models[i] = reader.read(); + hulls[i] = reader.read(); } + + size_t slabCount = reader.read(); + clipMap->mapEnts->trigger.slabCount = slabCount; + Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray(clipMap->mapEnts->trigger.slabCount); + for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) { + slabs[i] = reader.read(); + } + + + clipMap->mapEnts->trigger.models = &models[0]; + clipMap->mapEnts->trigger.hulls = &hulls[0]; + clipMap->mapEnts->trigger.slabs = &slabs[0]; } } - */ + + clipMap->checksum = reader.read(); // This mustn't be null and has to have at least 1 'valid' entry. if (!clipMap->smodelNodeCount || !clipMap->smodelNodes) diff --git a/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp b/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp index 1fa5dfcc..09e4d101 100644 --- a/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp +++ b/src/Components/Modules/AssetInterfaces/ImenuDef_t.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Assets { diff --git a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp index 2bbc6a8e..55cdf5d2 100644 --- a/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp +++ b/src/Components/Modules/AssetInterfaces/Isnd_alias_list_t.cpp @@ -1,13 +1,13 @@ -#include "STDInclude.hpp" +#include namespace Assets { void Isnd_alias_list_t::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - Components::FileSystem::File aliasFile(Utils::String::VA("sounds/%s", name.data())); + Components::FileSystem::File aliasFile(Utils::String::VA("sounds/%s", name.c_str())); if (!aliasFile.exists()) { - header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.data()).sound; + header->sound = Components::AssetHandler::FindOriginalAsset(this->getType(), name.c_str()).sound; return; } @@ -18,134 +18,330 @@ namespace Assets return; } - aliasList->head = builder->getAllocator()->allocate(); + std::string errors; + json11::Json infoData = json11::Json::parse(aliasFile.getBuffer(), errors); + json11::Json aliasesContainer = infoData["head"]; + + auto aliases = aliasesContainer.array_items(); + + aliasList->count = aliases.size(); + + // Allocate + aliasList->head = builder->getAllocator()->allocateArray(aliasList->count); if (!aliasList->head) { Components::Logger::Print("Error allocating memory for sound alias structure!\n"); return; } - aliasList->head->soundFile = builder->getAllocator()->allocate(); - if (!aliasList->head->soundFile) + + aliasList->aliasName = builder->getAllocator()->duplicateString(name.c_str()); + + for (size_t i = 0; i < aliasList->count; i++) { - Components::Logger::Print("Error allocating memory for sound alias structure!\n"); - return; - } + json11::Json head = aliasesContainer[i]; - aliasList->count = 1; + if (!infoData.is_object()) + { + Components::Logger::Error("Failed to load sound %s!", name.c_str()); + return; + } - std::string errors; - json11::Json infoData = json11::Json::parse(aliasFile.getBuffer(), errors); + aliasList->head->soundFile = builder->getAllocator()->allocate(); + if (!aliasList->head->soundFile) + { + Components::Logger::Print("Error allocating memory for sound alias structure!\n"); + return; + } - if (!infoData.is_object()) - { - Components::Logger::Error("Failed to load sound %s!", name.data()); - return; - } + Game::snd_alias_t* alias = aliasList->head; - Game::snd_alias_t* alias = aliasList->head; + // try and parse everything and if it fails then fail for the whole file + auto type = head["type"]; + auto subtitle = head["subtitle"]; + auto secondaryAliasName = head["secondaryAliasName"]; + auto chainAliasName = head["chainAliasName"]; + auto soundFile = head["soundFile"]; + auto sequence = head["sequence"]; + auto volMin = head["volMin"]; + auto volMax = head["volMax"]; + auto pitchMin = head["pitchMin"]; + auto pitchMax = head["pitchMax"]; + auto distMin = head["distMin"]; + auto distMax = head["distMax"]; + auto flags = head["flags"]; + auto slavePercentage = head["slavePercentage"]; + auto probability = head["probability"]; + auto lfePercentage = head["lfePercentage"]; + auto centerPercentage = head["centerPercentage"]; + auto startDelay = head["startDelay"]; + auto volumeFalloffCurve = head["volumeFalloffCurve"]; + auto envelopMin = head["envelopMin"]; + auto envelopMax = head["envelopMax"]; + auto envelopPercentage = head["envelopPercentage"]; + auto speakerMap = head["speakerMap"]; + auto aliasName = head["aliasName"]; - // try and parse everything and if it fails then fail for the whole file - auto type = infoData["type"]; - auto subtitle = infoData["subtitle"]; - auto secondaryAliasName = infoData["secondaryAliasName"]; - auto chainAliasName = infoData["chainAliasName"]; - auto soundFile = infoData["soundFile"]; - auto sequence = infoData["sequence"]; - auto volMin = infoData["volMin"]; - auto volMax = infoData["volMax"]; - auto pitchMin = infoData["pitchMin"]; - auto pitchMax = infoData["pitchMax"]; - auto distMin = infoData["distMin"]; - auto distMax = infoData["distMax"]; - auto flags = infoData["flags"]; - auto slavePercentage = infoData["slavePercentage"]; - auto probability = infoData["probability"]; - auto lfePercentage = infoData["lfePercentage"]; - auto centerPercentage = infoData["centerPercentage"]; - auto startDelay = infoData["startDelay"]; - auto volumeFalloffCurve = infoData["volumeFalloffCurve"]; - auto envelopMin = infoData["envelopMin"]; - auto envelopMax = infoData["envelopMax"]; - auto envelopPercentage = infoData["envelopPercentage"]; - auto speakerMap = infoData["speakerMap"]; + // Fix casing + if (soundFile.is_null()) + { + soundFile = head["soundfile"]; - if (type.is_null() || soundFile.is_null()) - { - Components::Logger::Error("Failed to parse sound %s! Each alias must have at least a type and a soundFile", name.data()); - return; - } + Components::Logger::Print("Fixed casing on %s\n", name.c_str()); + } + + if (type.is_null() || soundFile.is_null()) + { + Components::Logger::Print("Type is %s\n", type.dump().c_str()); + Components::Logger::Print("SoundFile is %s\n", soundFile.dump().c_str()); + Components::Logger::Error("Failed to parse sound %s! Each alias must have at least a type and a soundFile\n", name.c_str()); + return; + } #define CHECK(x, type) (x.is_##type##() || x.is_null()) - // TODO: actually support all of those properties - if (CHECK(type, string) && CHECK(subtitle, string) && CHECK(secondaryAliasName, string) && CHECK(chainAliasName, string) && - CHECK(soundFile, string) && CHECK(sequence, number) && CHECK(volMin, number) && CHECK(volMax, number) && CHECK(pitchMin, number) && - CHECK(pitchMax, number) && CHECK(distMin, number) && CHECK(distMax, number) && CHECK(flags, number) && CHECK(slavePercentage, number) && - CHECK(probability, number) && CHECK(lfePercentage, number) && CHECK(centerPercentage, number) && CHECK(startDelay, number) && - CHECK(volumeFalloffCurve, string) && CHECK(envelopMin, number) && CHECK(envelopMax, number) && CHECK(envelopPercentage, number) && - CHECK(speakerMap, string)) - { - - alias->soundFile->exists = true; - - if (subtitle.is_string()) + // TODO: actually support all of those properties + if (!CHECK(type, number)) { - alias->subtitle = subtitle.string_value().data(); - } - if (secondaryAliasName.is_string()) - { - alias->secondaryAliasName = secondaryAliasName.string_value().data(); - } - if (chainAliasName.is_string()) - { - alias->chainAliasName = chainAliasName.string_value().data(); + Components::Logger::Print("%s is not number but %d (%s)\n", "type", type.type(), type.dump().c_str()); } - alias->sequence = sequence.int_value(); - alias->volMin = float(volMin.number_value()); - alias->volMax = float(volMax.number_value()); - alias->pitchMin = float(pitchMin.number_value()); - alias->pitchMax = float(pitchMax.number_value()); - alias->distMin = float(distMin.number_value()); - alias->distMax = float(distMax.number_value()); - alias->flags = flags.int_value(); - alias->___u15.slavePercentage = float(slavePercentage.number_value()); - alias->probability = float(probability.number_value()); - alias->lfePercentage = float(lfePercentage.number_value()); - alias->centerPercentage = float(centerPercentage.number_value()); - alias->startDelay = startDelay.int_value(); - alias->envelopMin = float(envelopMin.number_value()); - alias->envelopMax = float(envelopMax.number_value()); - alias->envelopPercentage = float(envelopPercentage.number_value()); - - if (volumeFalloffCurve.is_string()) + if (!CHECK(subtitle, string)) { - alias->volumeFalloffCurve = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, volumeFalloffCurve.string_value(), builder).sndCurve; + Components::Logger::Print("%s is not string but %d (%s)\n", "subtitle", subtitle.type(), subtitle.dump().c_str()); } - if (type.string_value() == "loaded"s) + if (!CHECK(aliasName, string)) { - alias->soundFile->type = Game::SAT_LOADED; - alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.string_value(), builder).loadSnd; + Components::Logger::Print("%s is not string but %d (%s)\n", "aliasName", aliasName.type(), aliasName.dump().c_str()); } - else if (type.string_value() == "streamed"s) + + if (!CHECK(secondaryAliasName, string)) { - alias->soundFile->type = Game::SAT_STREAMED; - std::string streamedFile = soundFile.string_value(); - int split = streamedFile.find_last_of('/'); - alias->soundFile->u.streamSnd.filename.info.raw.dir = builder->getAllocator()->duplicateString(streamedFile.substr(0, split).c_str()); - alias->soundFile->u.streamSnd.filename.info.raw.name = builder->getAllocator()->duplicateString(streamedFile.substr(split).c_str()); + Components::Logger::Print("%s is not string but %d (%s)\n", "secondaryAliasName", secondaryAliasName.type(), secondaryAliasName.dump().c_str()); + } + + if (!CHECK(chainAliasName, string)) + { + Components::Logger::Print("%s is not string but %d (%s)\n", "chainAliasName", chainAliasName.type(), chainAliasName.dump().c_str()); + } + + if (!CHECK(soundFile, string)) + { + Components::Logger::Print("%s is not string but %d (%s)\n", "soundFile", soundFile.type(), soundFile.dump().c_str()); + } + + if (!CHECK(sequence, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "sequence", sequence.type(), sequence.dump().c_str()); + } + + if (!CHECK(volMin, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "volMin", volMin.type(), volMin.dump().c_str()); + } + + if (!CHECK(volMax, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "volMax", volMax.type(), volMax.dump().c_str()); + } + + if (!CHECK(pitchMin, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "pitchMin", pitchMin.type(), pitchMin.dump().c_str()); + } + + if (!CHECK(pitchMax, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "pitchMax", pitchMax.type(), pitchMax.dump().c_str()); + } + + if (!CHECK(probability, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "probability", probability.type(), probability.dump().c_str()); + } + + if (!CHECK(lfePercentage, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "lfePercentage", lfePercentage.type(), lfePercentage.dump().c_str()); + } + + if (!CHECK(centerPercentage, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "centerPercentage", centerPercentage.type(), centerPercentage.dump().c_str()); + } + + if (!CHECK(startDelay, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "startDelay", startDelay.type(), startDelay.dump().c_str()); + } + + if (!CHECK(volumeFalloffCurve, string)) + { + Components::Logger::Print("%s is not string but %d (%s)\n", "volumeFalloffCurve", volumeFalloffCurve.type(), volumeFalloffCurve.dump().c_str()); + } + + if (!CHECK(envelopMin, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "envelopMin", envelopMin.type(), envelopMin.dump().c_str()); + } + + if (!CHECK(envelopMax, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "envelopMax", envelopMax.type(), envelopMax.dump().c_str()); + } + + if (!CHECK(envelopPercentage, number)) + { + Components::Logger::Print("%s is not number but %d (%s)\n", "envelopPercentage", envelopPercentage.type(), envelopPercentage.dump().c_str()); + } + + if (!CHECK(speakerMap, object)) + { + Components::Logger::Print("%s is not object but %d (%s)\n", "speakerMap", speakerMap.type(), speakerMap.dump().c_str()); + } + + + if (CHECK(type, number) && CHECK(aliasName, string) && CHECK(subtitle, string) && CHECK(secondaryAliasName, string) && CHECK(chainAliasName, string) && + CHECK(soundFile, string) && CHECK(sequence, number) && CHECK(volMin, number) && CHECK(volMax, number) && CHECK(pitchMin, number) && + CHECK(pitchMax, number) && CHECK(distMin, number) && CHECK(distMax, number) && CHECK(flags, number) && CHECK(slavePercentage, number) && + CHECK(probability, number) && CHECK(lfePercentage, number) && CHECK(centerPercentage, number) && CHECK(startDelay, number) && + CHECK(volumeFalloffCurve, string) && CHECK(envelopMin, number) && CHECK(envelopMax, number) && CHECK(envelopPercentage, number) && + CHECK(speakerMap, object)) + { + + alias->soundFile->exists = true; + alias->aliasName = builder->getAllocator()->duplicateString(aliasName.string_value().c_str()); + + if (subtitle.is_string()) + { + alias->subtitle = builder->getAllocator()->duplicateString(subtitle.string_value().c_str()); + } + if (secondaryAliasName.is_string()) + { + alias->secondaryAliasName = builder->getAllocator()->duplicateString(secondaryAliasName.string_value().c_str()); + } + if (chainAliasName.is_string()) + { + alias->chainAliasName = builder->getAllocator()->duplicateString(chainAliasName.string_value().c_str()); + } + + alias->sequence = sequence.int_value(); + alias->volMin = float(volMin.number_value()); + alias->volMax = float(volMax.number_value()); + alias->pitchMin = float(pitchMin.number_value()); + alias->pitchMax = float(pitchMax.number_value()); + alias->distMin = float(distMin.number_value()); + alias->distMax = float(distMax.number_value()); + alias->flags = flags.int_value(); + alias->___u15.slavePercentage = float(slavePercentage.number_value()); + alias->probability = float(probability.number_value()); + alias->lfePercentage = float(lfePercentage.number_value()); + alias->centerPercentage = float(centerPercentage.number_value()); + alias->startDelay = startDelay.int_value(); + alias->envelopMin = float(envelopMin.number_value()); + alias->envelopMax = float(envelopMax.number_value()); + alias->envelopPercentage = float(envelopPercentage.number_value()); + + // Speaker map object + if (!speakerMap.is_null()) + { + alias->speakerMap = builder->getAllocator()->allocate(); + if (!alias->speakerMap) + { + Components::Logger::Print("Error allocating memory for speakermap in sound alias%s!\n", alias->aliasName); + return; + } + + alias->speakerMap->name = builder->getAllocator()->duplicateString(speakerMap["name"].string_value().c_str()); + alias->speakerMap->isDefault = speakerMap["isDefault"].bool_value(); + + if (speakerMap["channelMaps"].is_array()) + { + json11::Json::array channelMaps = speakerMap["channelMaps"].array_items(); + + assert(channelMaps.size() <= 4); + + // channelMapIndex should never exceed 1 + for (size_t channelMapIndex = 0; channelMapIndex < 2; channelMapIndex++) + { + // subChannelIndex should never exceed 1 + for (size_t subChannelIndex = 0; subChannelIndex < 2; subChannelIndex++) + { + json11::Json channelMap = channelMaps[channelMapIndex * 2 + subChannelIndex]; // 0-3 + + auto speakers = channelMap["speakers"].array_items(); + + alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount = speakers.size(); + + for (size_t speakerIndex = 0; speakerIndex < alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakerCount; speakerIndex++) + { + auto speaker = speakers[speakerIndex]; + alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[0] = static_cast(speaker["levels0"].number_value()); + alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = static_cast(speaker["levels1"].number_value()); + alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = static_cast(speaker["numLevels"].number_value()); + alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = static_cast(speaker["speaker"].number_value()); + } + } + } + } + } + + if (volumeFalloffCurve.is_string()) + { + std::string fallOffCurve = volumeFalloffCurve.string_value(); + + if (fallOffCurve.size() == 0) + { + fallOffCurve = "$default"; + } + + auto curve = Components::AssetHandler::FindAssetForZone( + Game::XAssetType::ASSET_TYPE_SOUND_CURVE, + fallOffCurve.c_str(), + builder + ).sndCurve; + + alias->volumeFalloffCurve = curve; + } + + if (static_cast(type.number_value()) == Game::snd_alias_type_t::SAT_LOADED) // Loaded + { + alias->soundFile->type = Game::SAT_LOADED; + alias->soundFile->u.loadSnd = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, soundFile.string_value().c_str(), builder).loadSnd; + } + else if (static_cast(type.number_value()) == Game::snd_alias_type_t::SAT_STREAMED) // Streamed + { + alias->soundFile->type = Game::SAT_STREAMED; + + std::string streamedFile = soundFile.string_value(); + std::string directory = ""s; + int split = streamedFile.find_last_of('/'); + + if (split >= 0) + { + directory = streamedFile.substr(0, split); + streamedFile = streamedFile.substr(split+1); + } + + alias->soundFile->u.streamSnd.filename.info.raw.dir = builder->getAllocator()->duplicateString(directory.c_str()); + alias->soundFile->u.streamSnd.filename.info.raw.name = builder->getAllocator()->duplicateString(streamedFile.c_str()); + } + else + { + Components::Logger::Error("Failed to parse sound %s! Invalid sound type %s\n", name.c_str(), type.string_value().c_str()); + return; + } + + aliasList->head[i] = *alias; } else { - Components::Logger::Error("Failed to parse sound %s! Invalid sound type %s", name.data(), type.string_value().c_str()); + Components::Logger::Error("Failed to parse sound %s!\n", name.c_str()); + return; } } - else - { - Components::Logger::Error("Failed to parse sound %s!", name.data()); - return; - } + + header->sound = aliasList; #undef CHECK @@ -155,7 +351,7 @@ namespace Assets { Game::snd_alias_list_t* asset = header.sound; - for (int i = 0; i < asset->count; ++i) + for (unsigned int i = 0; i < asset->count; ++i) { Game::snd_alias_t* alias = &asset->head[i]; @@ -166,7 +362,12 @@ namespace Assets if (alias->volumeFalloffCurve) { - builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve); + if (!builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve)) + { + // (Should never happen, but just in case) + alias->volumeFalloffCurve->filename = "$default"; + builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND_CURVE, alias->volumeFalloffCurve); + } } } } @@ -204,7 +405,7 @@ namespace Assets Game::snd_alias_t* destHead = buffer->dest(); buffer->saveArray(asset->head, asset->count); - for (int i = 0; i < asset->count; ++i) + for (unsigned int i = 0; i < asset->count; ++i) { Game::snd_alias_t* destAlias = &destHead[i]; Game::snd_alias_t* alias = &asset->head[i]; diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index e090e1bb..51e0bd29 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -8,7 +8,8 @@ namespace Components Utils::Cryptography::Token Auth::ComputeToken; Utils::Cryptography::ECC::Key Auth::GuidKey; - std::vector Auth::BannedUids = { + std::vector Auth::BannedUids = + { 0xf4d2c30b712ac6e3, 0xf7e33c4081337fa3, 0x6f5597f103cc50e9 @@ -82,7 +83,7 @@ namespace Components Command::ServerParams params; - if (params.length() < 3) + if (params.size() < 3) { Game::SV_Cmd_EndTokenizedString(); Logger::SoftError("Connecting failed: Command parsing error!"); @@ -169,7 +170,7 @@ namespace Components Command::ServerParams params; // Ensure there are enough params - if (params.length() < 3) + if (params.size() < 3) { Network::Send(address, "error\nInvalid connect string!"); return; @@ -179,8 +180,8 @@ namespace Components Utils::InfoString infostr(params[2]); // Read the required data - std::string steamId = infostr.get("xuid"); - std::string challenge = infostr.get("challenge"); + const auto& steamId = infostr.get("xuid"); + const auto& challenge = infostr.get("challenge"); if (steamId.empty() || challenge.empty()) { @@ -189,12 +190,12 @@ namespace Components } // Parse the id - unsigned __int64 xuid = strtoull(steamId.data(), nullptr, 16); + const auto xuid = std::strtoull(steamId.data(), nullptr, 16); SteamID guid; guid.bits = xuid; - if (Bans::IsBanned({ guid, address.getIP() })) + if (Bans::IsBanned({guid, address.getIP()})) { Network::Send(address, "error\nEXE_ERR_BANNED_PERM"); return; @@ -223,8 +224,8 @@ namespace Components } // Verify the security level - uint32_t ourLevel = static_cast(Dvar::Var("sv_securityLevel").get()); - uint32_t userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey()); + auto ourLevel = Dvar::Var("sv_securityLevel").get(); + auto userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey()); if (userLevel < ourLevel) { @@ -431,7 +432,7 @@ namespace Components Scheduler::OnFrame(Auth::Frame); // Register dvar - Dvar::Register("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_FLAG_SERVERINFO, "Security level for GUID certificates (POW)"); + Dvar::Register("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_SERVERINFO, "Security level for GUID certificates (POW)"); // Install registration hook Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick(); @@ -454,7 +455,7 @@ namespace Components { Command::Add("securityLevel", [](Command::Params* params) { - if (params->length() < 2) + if (params->size() < 2) { uint32_t level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); Logger::Print("Your current security level is %d\n", level); diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index 7fdef6cb..d179cc7b 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -6,6 +6,8 @@ namespace Components bool Bans::IsBanned(Bans::Entry entry) { + std::lock_guard _(Bans::AccessMutex); + Bans::BanList list; Bans::LoadBans(&list); @@ -181,9 +183,9 @@ namespace Components Game::client_t* client = &Game::svs_clients[num]; SteamID guid; - guid.bits = client->steamid; + guid.bits = client->steamID; - Bans::InsertBan({ guid, client->addr.ip }); + Bans::InsertBan({ guid, client->netchan.remoteAddress.ip }); Game::SV_KickClientError(client, reason); } @@ -232,17 +234,17 @@ namespace Components { Command::Add("banclient", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; std::string reason = "EXE_ERR_BANNED_PERM"; - if (params->length() >= 3) reason = params->join(2); + if (params->size() >= 3) reason = params->join(2); Bans::BanClientNum(atoi(params->get(1)), reason); }); Command::Add("unbanclient", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; std::string type = params->get(1); @@ -272,9 +274,4 @@ namespace Components Bans::LoadBans(&list); }); } - - Bans::~Bans() - { - - } } diff --git a/src/Components/Modules/Bans.hpp b/src/Components/Modules/Bans.hpp index 1ba76a66..fe5c6410 100644 --- a/src/Components/Modules/Bans.hpp +++ b/src/Components/Modules/Bans.hpp @@ -8,7 +8,6 @@ namespace Components typedef std::pair Entry; Bans(); - ~Bans(); static void BanClientNum(int num, const std::string& reason); static void UnbanClient(SteamID id); diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 4cbbde53..d177c573 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define KEY_MASK_FIRE 1 #define KEY_MASK_SPRINT 2 @@ -96,7 +96,7 @@ namespace Components if (bots.exists()) { - std::vector names = Utils::String::Explode(bots.getBuffer(), '\n'); + std::vector names = Utils::String::Split(bots.getBuffer(), '\n'); for (auto name : names) { @@ -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; @@ -437,7 +437,7 @@ namespace Components ucmd.rightmove = g_botai[i].right; ucmd.weapon = g_botai[i].weapon; - client->deltaMessage = client->outgoingSequence - 1; + client->deltaMessage = client->netchan.outgoingSequence - 1; Game::SV_ClientThink(client, &ucmd); } @@ -447,7 +447,7 @@ namespace Components { unsigned int count = 1; - if (params->length() > 1) + if (params->size() > 1) { if (params->get(1) == "all"s) count = static_cast(-1); else count = atoi(params->get(1)); diff --git a/src/Components/Modules/CardTitles.cpp b/src/Components/Modules/CardTitles.cpp index 00120a8f..396eebf5 100644 --- a/src/Components/Modules/CardTitles.cpp +++ b/src/Components/Modules/CardTitles.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -192,14 +192,14 @@ namespace Components { Dvar::OnInit([]() { - CardTitles::CustomTitleDvar = Dvar::Register("customtitle", "", Game::dvar_flag::DVAR_FLAG_USERINFO | Game::dvar_flag::DVAR_FLAG_SAVED, "Custom card title"); + CardTitles::CustomTitleDvar = Dvar::Register("customtitle", "", Game::dvar_flag::DVAR_USERINFO | Game::dvar_flag::DVAR_ARCHIVE, "Custom card title"); }); ServerCommands::OnCommand(21, [](Command::Params* params) { if (params->get(1) == "customTitles"s && !Dedicated::IsEnabled()) { - if (params->length() == 3) + if (params->size() == 3) { CardTitles::ParseCustomTitles(params->get(2)); return true; @@ -210,11 +210,6 @@ namespace Components }); - for (int i = 0; i < ARRAYSIZE(CardTitles::CustomTitles); ++i) - { - CardTitles::CustomTitles[i].clear(); - } - Utils::Hook(0x62EB26, CardTitles::GetPlayerCardClientInfoStub).install()->quick(); // Table lookup stuff @@ -227,12 +222,4 @@ namespace Components AntiCheat::CheckStartupTime(); #endif } - - CardTitles::~CardTitles() - { - for (int i = 0; i < ARRAYSIZE(CardTitles::CustomTitles); ++i) - { - CardTitles::CustomTitles[i].clear(); - } - } } diff --git a/src/Components/Modules/CardTitles.hpp b/src/Components/Modules/CardTitles.hpp index 7f838ef8..0b43bdab 100644 --- a/src/Components/Modules/CardTitles.hpp +++ b/src/Components/Modules/CardTitles.hpp @@ -60,7 +60,6 @@ namespace Components static void ParseCustomTitles(const char * msg); CardTitles(); - ~CardTitles(); private: static CClient * GetClientByIndex(std::uint32_t index); diff --git a/src/Components/Modules/Changelog.cpp b/src/Components/Modules/Changelog.cpp index 059ef0bb..a6da0d83 100644 --- a/src/Components/Modules/Changelog.cpp +++ b/src/Components/Modules/Changelog.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -19,7 +19,7 @@ namespace Components data = "^1Unable to get changelog."; } - Changelog::Lines = Utils::String::Explode(data, '\n'); + Changelog::Lines = Utils::String::Split(data, '\n'); for (auto& line : Changelog::Lines) { @@ -60,12 +60,4 @@ namespace Components Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner1); #endif } - - Changelog::~Changelog() - { - { - std::lock_guard _(Changelog::Mutex); - Changelog::Lines.clear(); - } - } } diff --git a/src/Components/Modules/Changelog.hpp b/src/Components/Modules/Changelog.hpp index dd74a8be..b58eb1ca 100644 --- a/src/Components/Modules/Changelog.hpp +++ b/src/Components/Modules/Changelog.hpp @@ -6,7 +6,6 @@ namespace Components { public: Changelog(); - ~Changelog(); static void LoadChangelog(); diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp new file mode 100644 index 00000000..baa00267 --- /dev/null +++ b/src/Components/Modules/Chat.cpp @@ -0,0 +1,331 @@ +#include + +namespace Components +{ + Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast(0x7ED398); + Dvar::Var Chat::cg_chatWidth; + Game::dvar_t** Chat::cg_chatTime = reinterpret_cast(0x9F5DE8); + + bool Chat::SendChat; + + std::mutex Chat::AccessMutex; + std::unordered_set Chat::MuteList; + + const char* Chat::EvaluateSay(char* text, Game::gentity_t* player) + { + Chat::SendChat = true; + + if (text[1] == '/') + { + Chat::SendChat = false; + text[1] = text[0]; + ++text; + } + + std::unique_lock lock(Chat::AccessMutex); + if (Chat::MuteList.find(Game::svs_clients[player->s.number].steamID) != Chat::MuteList.end()) + { + lock.unlock(); + Chat::SendChat = false; + Game::SV_GameSendServerCommand(player->s.number, 0, + Utils::String::VA("%c \"You are muted\"", 0x65)); + } + + // Test whether the lock is still locked + if (lock.owns_lock()) + { + lock.unlock(); + } + + TextRenderer::StripMaterialTextIcons(text, text, strlen(text) + 1); + + Game::Scr_AddEntity(player); + Game::Scr_AddString(text + 1); + Game::Scr_NotifyLevel(Game::SL_GetString("say", 0), 2); + + return text; + } + + __declspec(naked) void Chat::PreSayStub() + { + __asm + { + mov eax, [esp + 100h + 10h] + + push eax + pushad + + push[esp + 100h + 28h] + push eax + call Chat::EvaluateSay + add esp, 8h + + mov[esp + 20h], eax + popad + pop eax + + mov[esp + 100h + 10h], eax + + jmp PlayerName::CleanStrStub + } + } + + __declspec(naked) void Chat::PostSayStub() + { + __asm + { + // eax is used by the callee + push eax + + xor eax, eax + mov al, Chat::SendChat + + test al, al + jnz return + + // Don't send the chat + pop eax + retn + + return: + pop eax + + // Jump to the target + push 5DF620h + retn + } + } + + void Chat::CheckChatLineEnd(const char*& inputBuffer, char*& lineBuffer, float& len, const int chatHeight, const float chatWidth, char*& lastSpacePos, char*& lastFontIconPos, const int lastColor) + { + if (len > chatWidth) + { + if (lastSpacePos && lastSpacePos > lastFontIconPos) + { + inputBuffer += lastSpacePos - lineBuffer + 1; + lineBuffer = lastSpacePos; + } + else if (lastFontIconPos) + { + inputBuffer += lastFontIconPos - lineBuffer; + lineBuffer = lastFontIconPos; + } + *lineBuffer = 0; + len = 0.0f; + Game::cgsArray[0].teamChatMsgTimes[Game::cgsArray[0].teamChatPos % chatHeight] = Game::cgArray[0].time; + + Game::cgsArray[0].teamChatPos++; + lineBuffer = Game::cgsArray[0].teamChatMsgs[Game::cgsArray[0].teamChatPos % chatHeight]; + lineBuffer[0] = '^'; + lineBuffer[1] = CharForColorIndex(lastColor); + lineBuffer += 2; + lastSpacePos = nullptr; + lastFontIconPos = nullptr; + } + } + + void Chat::CG_AddToTeamChat(const char* text) + { + // Text can only be 150 characters maximum. This is bigger than the teamChatMsgs buffers with 160 characters + // Therefore it is not needed to check for buffer lengths + + const auto chatHeight = (*cg_chatHeight)->current.integer; + const auto chatWidth = static_cast(cg_chatWidth.get()); + const auto chatTime = (*cg_chatTime)->current.integer; + if (chatHeight < 0 || static_cast(chatHeight) > std::extent_v || chatWidth <= 0 || chatTime <= 0) + { + Game::cgsArray[0].teamLastChatPos = 0; + Game::cgsArray[0].teamChatPos = 0; + return; + } + + TextRenderer::FontIconInfo fontIconInfo{}; + auto len = 0.0f; + auto lastColor = static_cast(TEXT_COLOR_DEFAULT); + char* lastSpace = nullptr; + char* lastFontIcon = nullptr; + char* p = Game::cgsArray[0].teamChatMsgs[Game::cgsArray[0].teamChatPos % chatHeight]; + p[0] = '\0'; + + while (*text) + { + CheckChatLineEnd(text, p, len, chatHeight, chatWidth, lastSpace, lastFontIcon, lastColor); + + const char* fontIconEndPos = &text[1]; + if(text[0] == TextRenderer::FONT_ICON_SEPARATOR_CHARACTER && TextRenderer::IsFontIcon(fontIconEndPos, fontIconInfo)) + { + // The game calculates width on a per character base. Since the width of a font icon is calculated based on the height of the font + // which is roughly double as much as the average width of a character without an additional multiplier the calculated len of the font icon + // would be less than it most likely would be rendered. Therefore apply a guessed 2.0f multiplier at this location which makes + // the calculated width of a font icon roughly comparable to the width of an average character of the font. + const auto normalizedFontIconWidth = TextRenderer::GetNormalizedFontIconWidth(fontIconInfo); + const auto fontIconWidth = normalizedFontIconWidth * FONT_ICON_CHAT_WIDTH_CALCULATION_MULTIPLIER; + len += fontIconWidth; + + lastFontIcon = p; + for(; text < fontIconEndPos; text++) + { + p[0] = text[0]; + p++; + } + + CheckChatLineEnd(text, p, len, chatHeight, chatWidth, lastSpace, lastFontIcon, lastColor); + } + else if (text[0] == '^' && text[1] != 0 && text[1] >= TextRenderer::COLOR_FIRST_CHAR && text[1] <= TextRenderer::COLOR_LAST_CHAR) + { + p[0] = '^'; + p[1] = text[1]; + lastColor = ColorIndexForChar(text[1]); + p += 2; + text += 2; + } + else + { + if (text[0] == ' ') + lastSpace = p; + *p++ = *text++; + len += 1.0f; + } + } + + *p = 0; + + Game::cgsArray[0].teamChatMsgTimes[Game::cgsArray[0].teamChatPos % chatHeight] = Game::cgArray[0].time; + + Game::cgsArray[0].teamChatPos++; + if (Game::cgsArray[0].teamChatPos - Game::cgsArray[0].teamLastChatPos > chatHeight) + Game::cgsArray[0].teamLastChatPos = Game::cgsArray[0].teamChatPos + 1 - chatHeight; + } + + __declspec(naked) void Chat::CG_AddToTeamChat_Stub() + { + __asm + { + pushad + + push ecx + call CG_AddToTeamChat + add esp, 4h + + popad + ret + } + } + + void Chat::MuteClient(const Game::client_t* client) + { + std::unique_lock lock(Chat::AccessMutex); + + if (Chat::MuteList.find(client->steamID) == Chat::MuteList.end()) + { + Chat::MuteList.insert(client->steamID); + lock.unlock(); + + Logger::Print("%s was muted\n", client->name); + Game::SV_GameSendServerCommand(client->gentity->s.number, 0, + Utils::String::VA("%c \"You were muted\"", 0x65)); + return; + } + + lock.unlock(); + Logger::Print("%s is already muted\n", client->name); + Game::SV_GameSendServerCommand(-1, 0, + Utils::String::VA("%c \"%s is already muted\"", 0x65, client->name)); + } + + void Chat::UnmuteClient(const Game::client_t* client) + { + Chat::UnmuteInternal(client->steamID); + + Logger::Print("%s was unmuted\n", client->name); + Game::SV_GameSendServerCommand(client->gentity->s.number, 0, + Utils::String::VA("%c \"You were unmuted\"", 0x65)); + } + + void Chat::UnmuteInternal(const std::uint64_t id, bool everyone) + { + std::unique_lock lock(Chat::AccessMutex); + + if (everyone) + Chat::MuteList.clear(); + else + Chat::MuteList.erase(id); + } + + void Chat::AddChatCommands() + { + Command::AddSV("muteClient", [](Command::Params* params) + { + if (!Dvar::Var("sv_running").get()) + { + Logger::Print("Server is not running.\n"); + return; + } + + const auto* cmd = params->get(0); + if (params->size() < 2) + { + Logger::Print("Usage: %s : prevent the player from using the chat\n", cmd); + return; + } + + const auto* client = Game::SV_GetPlayerByNum(); + if (client != nullptr) + { + Chat::MuteClient(client); + } + }); + + Command::AddSV("unmute", [](Command::Params* params) + { + if (!Dvar::Var("sv_running").get()) + { + Logger::Print("Server is not running.\n"); + return; + } + + const auto* cmd = params->get(0); + if (params->size() < 2) + { + Logger::Print("Usage: %s \n%s all = unmute everyone\n", cmd, cmd); + return; + } + + const auto* client = Game::SV_GetPlayerByNum(); + + if (client != nullptr) + { + Chat::UnmuteClient(client); + return; + } + + if (std::strcmp(params->get(1), "all") == 0) + { + Logger::Print("All players were unmuted\n"); + Chat::UnmuteInternal(0, true); + } + else + { + const auto steamId = std::strtoull(params->get(1), nullptr, 16); + Chat::UnmuteInternal(steamId); + } + }); + } + + Chat::Chat() + { + Dvar::OnInit([] + { + cg_chatWidth = Dvar::Register("cg_chatWidth", 52, 1, std::numeric_limits::max(), Game::DVAR_ARCHIVE, "The normalized maximum width of a chat message"); + Chat::AddChatCommands(); + }); + + // Intercept chat sending + Utils::Hook(0x4D000B, PreSayStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4D0110, PostSayStub, HOOK_CALL).install()->quick(); + + // Change logic that does word splitting with new lines for chat messages to support fonticons + Utils::Hook(0x592E10, CG_AddToTeamChat_Stub, HOOK_JUMP).install()->quick(); + } +} diff --git a/src/Components/Modules/Chat.hpp b/src/Components/Modules/Chat.hpp new file mode 100644 index 00000000..134efb37 --- /dev/null +++ b/src/Components/Modules/Chat.hpp @@ -0,0 +1,35 @@ +#pragma once + +namespace Components +{ + class Chat : public Component + { + static constexpr auto FONT_ICON_CHAT_WIDTH_CALCULATION_MULTIPLIER = 2.0f; + public: + Chat(); + + private: + static Game::dvar_t** cg_chatHeight; + static Dvar::Var cg_chatWidth; + static Game::dvar_t** cg_chatTime; + + static bool SendChat; + + static std::mutex AccessMutex; + static std::unordered_set MuteList; + + static const char* EvaluateSay(char* text, Game::gentity_t* player); + + static void PreSayStub(); + static void PostSayStub(); + + static void CheckChatLineEnd(const char*& inputBuffer, char*& lineBuffer, float& len, int chatHeight, float chatWidth, char*& lastSpacePos, char*& lastFontIconPos, int lastColor); + static void CG_AddToTeamChat(const char* text); + static void CG_AddToTeamChat_Stub(); + + static void MuteClient(const Game::client_t* client); + static void UnmuteClient(const Game::client_t* client); + static void UnmuteInternal(const std::uint64_t id, bool everyone = false); + static void AddChatCommands(); + }; +} diff --git a/src/Components/Modules/Clantags.cpp b/src/Components/Modules/Clantags.cpp index 8c6be95a..c23a3b62 100644 --- a/src/Components/Modules/Clantags.cpp +++ b/src/Components/Modules/Clantags.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -75,7 +75,7 @@ namespace Components // Create clantag dvar Dvar::OnInit([]() { - Dvar::Register("clantag", "", Game::dvar_flag::DVAR_FLAG_USERINFO | Game::dvar_flag::DVAR_FLAG_SAVED, "If set, your clantag will be shown on the scoreboard."); + Dvar::Register("clantag", "", Game::dvar_flag::DVAR_USERINFO | Game::dvar_flag::DVAR_ARCHIVE, "If set, your clantag will be shown on the scoreboard."); }); // Servercommand hook @@ -83,7 +83,7 @@ namespace Components { if (params->get(1) == "clantags"s && !Dedicated::IsEnabled()) { - if (params->length() == 3) + if (params->size() == 3) { ClanTags::ParseClantags(params->get(2)); return true; @@ -93,20 +93,7 @@ namespace Components return false; }); - for (int i = 0; i < ARRAYSIZE(ClanTags::Tags); ++i) - { - ClanTags::Tags[i].clear(); - } - // Draw clantag before playername Utils::Hook(0x591242, ClanTags::DrawPlayerNameOnScoreboard).install()->quick(); } - - ClanTags::~ClanTags() - { - for (int i = 0; i < ARRAYSIZE(ClanTags::Tags); ++i) - { - ClanTags::Tags[i].clear(); - } - } } diff --git a/src/Components/Modules/Clantags.hpp b/src/Components/Modules/Clantags.hpp index caadbe69..ea7bca39 100644 --- a/src/Components/Modules/Clantags.hpp +++ b/src/Components/Modules/Clantags.hpp @@ -10,7 +10,6 @@ namespace Components static const char* GetUserClantag(std::uint32_t clientnum, const char * playername); ClanTags(); - ~ClanTags(); private: static std::string Tags[18]; diff --git a/src/Components/Modules/Client.cpp b/src/Components/Modules/Client.cpp index 0f3e991f..fc881a36 100644 --- a/src/Components/Modules/Client.cpp +++ b/src/Components/Modules/Client.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -134,7 +134,7 @@ namespace Components if (client->state >= 3) { - std::string ip = Game::NET_AdrToString(client->addr); + std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); if (ip.find_first_of(":") != std::string::npos) ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // erase port Game::Scr_AddString(ip.data()); diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp new file mode 100644 index 00000000..5f7ef964 --- /dev/null +++ b/src/Components/Modules/ClientCommand.cpp @@ -0,0 +1,312 @@ +#include + +namespace Components +{ + std::unordered_map> ClientCommand::FunctionMap; + + bool ClientCommand::CheatsOk(const Game::gentity_s* ent) + { + const auto entNum = ent->s.number; + + if (!Dvar::Var("sv_cheats").get()) + { + Logger::Print("CheatsOk: cheats are disabled!\n"); + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65)); + return false; + } + + if (ent->health < 1) + { + Logger::Print("CheatsOk: entity %u must be alive to use this command!\n", entNum); + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65)); + return false; + } + + return true; + } + + bool ClientCommand::CallbackHandler(Game::gentity_s* ent, const char* cmd) + { + const auto command = Utils::String::ToLower(cmd); + + if (ClientCommand::FunctionMap.find(command) != ClientCommand::FunctionMap.end()) + { + ClientCommand::FunctionMap[command](ent); + return true; + } + + return false; + } + + void ClientCommand::Add(const char* name, Utils::Slot callback) + { + const auto command = Utils::String::ToLower(name); + + ClientCommand::FunctionMap[command] = callback; + } + + void ClientCommand::ClientCommandStub(const int clientNum) + { + char cmd[1024]{}; + const auto entity = &Game::g_entities[clientNum]; + + if (entity->client == nullptr) + { + Logger::Print("ClientCommand: client %d is not fully in game yet\n", clientNum); + return; + } + + Game::SV_Cmd_ArgvBuffer(0, cmd, sizeof(cmd)); + + if (!ClientCommand::CallbackHandler(entity, cmd)) + { + // If no callback was found call original game function + Utils::Hook::Call(0x416790)(clientNum); + } + } + + void ClientCommand::AddCheatCommands() + { + ClientCommand::Add("noclip", [](Game::gentity_s* ent) + { + if (!ClientCommand::CheatsOk(ent)) + return; + + ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; + + const auto entNum = ent->s.number; + Logger::Print("Noclip toggled for entity %u\n", entNum); + + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); + }); + + ClientCommand::Add("ufo", [](Game::gentity_s* ent) + { + if (!ClientCommand::CheatsOk(ent)) + return; + + ent->client->flags ^= Game::PLAYER_FLAG_UFO; + + const auto entNum = ent->s.number; + Logger::Print("UFO toggled for entity %u\n", entNum); + + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); + }); + + ClientCommand::Add("god", [](Game::gentity_s* ent) + { + if (!ClientCommand::CheatsOk(ent)) + return; + + ent->flags ^= Game::FL_GODMODE; + + const auto entNum = ent->s.number; + Logger::Print("God toggled for entity %u\n", entNum); + + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); + }); + + ClientCommand::Add("demigod", [](Game::gentity_s* ent) + { + if (!ClientCommand::CheatsOk(ent)) + return; + + ent->flags ^= Game::FL_DEMI_GODMODE; + + const auto entNum = ent->s.number; + Logger::Print("Demigod toggled for entity %u\n", entNum); + + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + (ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF")); + }); + + ClientCommand::Add("notarget", [](Game::gentity_s* ent) + { + if (!ClientCommand::CheatsOk(ent)) + return; + + ent->flags ^= Game::FL_NOTARGET; + + const auto entNum = ent->s.number; + Logger::Print("Notarget toggled for entity %u\n", entNum); + + Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); + }); + + ClientCommand::Add("setviewpos", [](Game::gentity_s* ent) + { + assert(ent != nullptr); + + if (!ClientCommand::CheatsOk(ent)) + return; + + Command::ServerParams params = {}; + Game::vec3_t origin, angles{0.f, 0.f, 0.f}; + + if (params.size() < 4 || params.size() > 6) + { + Game::SV_GameSendServerCommand(ent->s.number, 0, + Utils::String::VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65)); + return; + } + + for (auto i = 0; i < 3; i++) + { + origin[i] = std::strtof(params.get(i + 1), nullptr); + } + + if (params.size() >= 5) + { + angles[1] = std::strtof(params.get(4), nullptr); // Yaw + } + + if (params.size() == 6) + { + angles[0] = std::strtof(params.get(5), nullptr); // Pitch + } + + Game::TeleportPlayer(ent, origin, angles); + }); + } + + void ClientCommand::AddScriptFunctions() + { + Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); + { + 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() == 1u && 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) // gsc: Ufo(); + { + 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() == 1u && 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::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); + { + if (entref >= Game::MAX_GENTITIES) + { + Game::Scr_Error(Utils::String::VA("^1God: entity %u is out of bounds\n", entref)); + return; + } + + if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + { + if (Game::Scr_GetInt(0)) + { + Game::g_entities[entref].flags |= Game::FL_GODMODE; + } + else + { + Game::g_entities[entref].flags &= ~Game::FL_GODMODE; + } + } + else + { + Game::g_entities[entref].flags ^= Game::FL_GODMODE; + } + }); + + Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); + { + if (entref >= Game::MAX_GENTITIES) + { + Game::Scr_Error(Utils::String::VA("^1Demigod: entity %u is out of bounds\n", entref)); + return; + } + + if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + { + if (Game::Scr_GetInt(0)) + { + Game::g_entities[entref].flags |= Game::FL_DEMI_GODMODE; + } + else + { + Game::g_entities[entref].flags &= ~Game::FL_DEMI_GODMODE; + } + } + else + { + Game::g_entities[entref].flags ^= Game::FL_DEMI_GODMODE; + } + }); + + Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); + { + if (entref >= Game::MAX_GENTITIES) + { + Game::Scr_Error(Utils::String::VA("^1Notarget: entity %u is out of bounds\n", entref)); + return; + } + + if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + { + if (Game::Scr_GetInt(0)) + { + Game::g_entities[entref].flags |= Game::FL_NOTARGET; + } + else + { + Game::g_entities[entref].flags &= ~Game::FL_NOTARGET; + } + } + else + { + Game::g_entities[entref].flags ^= Game::FL_NOTARGET; + } + }); + } + + ClientCommand::ClientCommand() + { + // Hook call to ClientCommand in SV_ExecuteClientCommand so we may add custom commands + Utils::Hook(0x6259FA, ClientCommand::ClientCommandStub, HOOK_CALL).install()->quick(); + + ClientCommand::AddCheatCommands(); + ClientCommand::AddScriptFunctions(); + } +} diff --git a/src/Components/Modules/ClientCommand.hpp b/src/Components/Modules/ClientCommand.hpp new file mode 100644 index 00000000..ca9bc2a3 --- /dev/null +++ b/src/Components/Modules/ClientCommand.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace Components +{ + class ClientCommand : public Component + { + public: + typedef void(Callback)(Game::gentity_s* entity); + + ClientCommand(); + + static void Add(const char* name, Utils::Slot callback); + static bool CheatsOk(const Game::gentity_s* ent); + + private: + static std::unordered_map> FunctionMap; + + static bool CallbackHandler(Game::gentity_s* ent, const char* cmd); + static void ClientCommandStub(const int clientNum); + static void AddCheatCommands(); + static void AddScriptFunctions(); + }; +} diff --git a/src/Components/Modules/Colors.cpp b/src/Components/Modules/Colors.cpp deleted file mode 100644 index 863cd821..00000000 --- a/src/Components/Modules/Colors.cpp +++ /dev/null @@ -1,269 +0,0 @@ -#include "STDInclude.hpp" - -namespace Components -{ - Dvar::Var Colors::NewColors; - std::vector Colors::ColorTable; - - DWORD Colors::HsvToRgb(Colors::HsvColor hsv) - { - DWORD rgb; - unsigned char region, p, q, t; - unsigned int h, s, v, remainder; - - if (hsv.s == 0) - { - rgb = RGB(hsv.v, hsv.v, hsv.v); - return rgb; - } - - // converting to 16 bit to prevent overflow - h = hsv.h; - s = hsv.s; - v = hsv.v; - - region = static_cast(h / 43); - remainder = (h - (region * 43)) * 6; - - p = static_cast((v * (255 - s)) >> 8); - q = static_cast((v * (255 - ((s * remainder) >> 8))) >> 8); - t = static_cast((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8); - - switch (region) - { - case 0: - rgb = RGB(v, t, p); - break; - case 1: - rgb = RGB(q, v, p); - break; - case 2: - rgb = RGB(p, v, t); - break; - case 3: - rgb = RGB(p, q, v); - break; - case 4: - rgb = RGB(t, p, v); - break; - default: - rgb = RGB(v, p, q); - break; - } - - return rgb; - } - - void Colors::Strip(const char* in, char* out, int max) - { - if (!in || !out) return; - - max--; - int current = 0; - while (*in != 0 && current < max) - { - char index = *(in + 1); - if (*in == '^' && (Colors::ColorIndex(index) != 7 || index == '7')) - { - ++in; - } - else - { - *out = *in; - ++out; - ++current; - } - - ++in; - } - *out = '\0'; - } - - std::string Colors::Strip(const std::string& in) - { - char buffer[1000] = { 0 }; // Should be more than enough - Colors::Strip(in.data(), buffer, sizeof(buffer)); - return std::string(buffer); - } - - void Colors::UserInfoCopy(char* buffer, const char* name, size_t size) - { - Utils::Memory::Allocator allocator; - - if (!Dvar::Var("sv_allowColoredNames").get()) - { - Colors::Strip(name, buffer, size); - } - else - { - strncpy_s(buffer, size, name, _TRUNCATE); - } - } - - __declspec(naked) void Colors::ClientUserinfoChanged() - { - __asm - { - mov eax, [esp + 4h] // length - //sub eax, 1 - push eax - - push ecx // name - push edx // buffer - - call Colors::UserInfoCopy - - add esp, 0Ch - retn - } - } - - char* Colors::GetClientName(int localClientNum, int index, char *buf, size_t size) - { - Game::CL_GetClientName(localClientNum, index, buf, size); - - // Append clantag to username & remove the colors - strncpy_s(buf, size, Colors::Strip(ClanTags::GetUserClantag(index, buf)).data(), size); - - return buf; - } - - void Colors::PatchColorLimit(char limit) - { - Utils::Hook::Set(0x535629, limit); // DrawText2d - Utils::Hook::Set(0x4C1BE4, limit); // No idea :P - Utils::Hook::Set(0x4863DD, limit); // No idea :P - Utils::Hook::Set(0x486429, limit); // No idea :P - Utils::Hook::Set(0x49A5A8, limit); // No idea :P - Utils::Hook::Set(0x505721, limit); // R_TextWidth - Utils::Hook::Set(0x505801, limit); // No idea :P - Utils::Hook::Set(0x50597F, limit); // No idea :P - Utils::Hook::Set(0x5815DB, limit); // No idea :P - Utils::Hook::Set(0x592ED0, limit); // No idea :P - Utils::Hook::Set(0x5A2E2E, limit); // No idea :P - - Utils::Hook::Set(0x5A2733, limit - '0'); // No idea :P - } - - char Colors::Add(uint8_t r, uint8_t g, uint8_t b) - { - char index = '0' + static_cast(Colors::ColorTable.size()); - Colors::ColorTable.push_back(RGB(r, g, b)); - Colors::PatchColorLimit(index); - return index; - } - - unsigned int Colors::ColorIndex(char index) - { - char result = index - '0'; - if (static_cast(result) >= Colors::ColorTable.size() || result < 0) result = 7; - return result; - } - - void Colors::LookupColor(DWORD* color, char index) - { - *color = RGB(255, 255, 255); - - if (index == '8') // Color 8 - { - *color = *reinterpret_cast(0x66E5F70); - } - else if (index == '9') // Color 9 - { - *color = *reinterpret_cast(0x66E5F74); - } - else if (index == ':') - { - *color = Colors::HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); - } - else if (index == ';') - { - float fltColor[4]; - Game::Dvar_GetUnpackedColorByName("sv_customTextColor", fltColor); - *color = RGB(fltColor[0] * 255, fltColor[1] * 255, fltColor[2] * 255); - } - else - { - int clrIndex = Colors::ColorIndex(index); - - // Use native colors - if (clrIndex <= 7 && !Colors::NewColors.get()) - { - *color = reinterpret_cast(0x78DC70)[index - 48]; - } - else - { - *color = Colors::ColorTable[clrIndex]; - } - } - } - - char* Colors::CleanStrStub(char* string) - { - Colors::Strip(string, string, strlen(string) + 1); - return string; - } - - __declspec(naked) void Colors::LookupColorStub() - { - __asm - { - pushad - push [esp + 24h] // Index - push esi // Color ref - call Colors::LookupColor - add esp, 8h - popad - retn - } - } - - Colors::Colors() - { - // Disable SV_UpdateUserinfo_f, to block changing the name ingame - Utils::Hook::Set(0x6258D0, 0xC3); - - // Allow colored names ingame - Utils::Hook(0x5D8B40, Colors::ClientUserinfoChanged, HOOK_JUMP).install()->quick(); - - // Though, don't apply that to overhead names. - Utils::Hook(0x581932, Colors::GetClientName, HOOK_CALL).install()->quick(); - - // Patch RB_LookupColor - Utils::Hook(0x534CD0, Colors::LookupColorStub, HOOK_JUMP).install()->quick(); - - // Patch ColorIndex - Utils::Hook(0x417770, Colors::ColorIndex, HOOK_JUMP).install()->quick(); - - // Patch I_CleanStr - Utils::Hook(0x4AD470, Colors::CleanStrStub, HOOK_JUMP).install()->quick(); - - // Register dvar - Colors::NewColors = Dvar::Register("cg_newColors", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Use Warfare² color code style."); - Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::dvar_flag::DVAR_FLAG_REPLICATED, "Color for the extended color code."); - Dvar::Register("sv_allowColoredNames", true, Game::dvar_flag::DVAR_FLAG_NONE, "Allow colored names on the server"); - - // Add our colors - Colors::Add(0, 0, 0); // 0 - Black - Colors::Add(255, 49, 49); // 1 - Red - Colors::Add(134, 192, 0); // 2 - Green - Colors::Add(255, 173, 34); // 3 - Yellow - Colors::Add(0, 135, 193); // 4 - Blue - Colors::Add(32, 197, 255); // 5 - Light Blue - Colors::Add(151, 80, 221); // 6 - Pink - - Colors::Add(255, 255, 255); // 7 - White - - Colors::Add(0, 0, 0); // 8 - Team color (axis?) - Colors::Add(0, 0, 0); // 9 - Team color (allies?) - - // Custom colors - Colors::Add(0, 0, 0); // 10 - Rainbow (:) - Colors::Add(0, 0, 0); // 11 - Server color (;) - using that color in infostrings (e.g. your name) fails, ';' is an illegal character! - } - - Colors::~Colors() - { - Colors::ColorTable.clear(); - } -} diff --git a/src/Components/Modules/Colors.hpp b/src/Components/Modules/Colors.hpp deleted file mode 100644 index 84dd1bc8..00000000 --- a/src/Components/Modules/Colors.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -namespace Components -{ - class Colors : public Component - { - public: - Colors(); - ~Colors(); - - static void Strip(const char* in, char* out, int max); - static std::string Strip(const std::string& in); - - static char Add(uint8_t r, uint8_t g, uint8_t b); - - private: - struct HsvColor - { - unsigned char h; - unsigned char s; - unsigned char v; - }; - - static Dvar::Var NewColors; - - static DWORD HsvToRgb(HsvColor hsv); - - static void UserInfoCopy(char* buffer, const char* name, size_t size); - - static void ClientUserinfoChanged(); - static char* GetClientName(int localClientNum, int index, char *buf, size_t size); - static void PatchColorLimit(char limit); - - static unsigned int ColorIndex(char index); - static void LookupColor(DWORD* color, char index); - static void LookupColorStub(); - static char* CleanStrStub(char* string); - static std::vector ColorTable; - }; -} diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index 1037144e..cbe13615 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -1,60 +1,75 @@ -#include "STDInclude.hpp" +#include namespace Components { std::unordered_map> Command::FunctionMap; std::unordered_map> Command::FunctionMapSV; - std::string Command::Params::join(size_t startIndex) + std::string Command::Params::join(const int index) { std::string result; - for (size_t i = startIndex; i < this->length(); ++i) + for (auto i = index; i < this->size(); i++) { - if (i > startIndex) result.append(" "); - result.append(this->operator[](i)); + if (i > index) result.append(" "); + result.append(this->get(i)); } return result; } - char* Command::Params::operator[](size_t index) + Command::ClientParams::ClientParams() + : nesting_(Game::cmd_args->nesting) { - return this->get(index); + assert(Game::cmd_args->nesting < Game::CMD_MAX_NESTING); } - char* Command::ClientParams::get(size_t index) + int Command::ClientParams::size() { - if (index >= this->length()) return const_cast(""); - return Game::cmd_argv[this->commandId][index]; + return Game::cmd_args->argc[this->nesting_]; } - size_t Command::ClientParams::length() + const char* Command::ClientParams::get(const int index) { - return Game::cmd_argc[this->commandId]; + if (index >= this->size()) + { + return ""; + } + + return Game::cmd_args->argv[this->nesting_][index]; } - char* Command::ServerParams::get(size_t index) + Command::ServerParams::ServerParams() + : nesting_(Game::sv_cmd_args->nesting) { - if (index >= this->length()) return const_cast(""); - return Game::cmd_argv_sv[this->commandId][index]; + assert(Game::sv_cmd_args->nesting < Game::CMD_MAX_NESTING); } - size_t Command::ServerParams::length() + int Command::ServerParams::size() { - return Game::cmd_argc_sv[this->commandId]; + return Game::sv_cmd_args->argc[this->nesting_]; + } + + const char* Command::ServerParams::get(const int index) + { + if (index >= this->size()) + { + return ""; + } + + return Game::sv_cmd_args->argv[this->nesting_][index]; } void Command::Add(const char* name, Utils::Slot callback) { - std::string command = Utils::String::ToLower(name); + const auto command = Utils::String::ToLower(name); if (Command::FunctionMap.find(command) == Command::FunctionMap.end()) { Command::AddRaw(name, Command::MainCallback); } - Command::FunctionMap[command] = callback; + Command::FunctionMap[command] = std::move(callback); } void Command::AddSV(const char* name, Utils::Slot callback) @@ -70,7 +85,7 @@ namespace Components return; } - std::string command = Utils::String::ToLower(name); + const auto command = Utils::String::ToLower(name); if (Command::FunctionMapSV.find(command) == Command::FunctionMapSV.end()) { @@ -80,7 +95,7 @@ namespace Components Command::AddRaw(name, Game::Cbuf_AddServerText); } - Command::FunctionMapSV[command] = callback; + FunctionMapSV[command] = std::move(callback); } void Command::AddRaw(const char* name, void(*callback)(), bool key) @@ -134,25 +149,27 @@ namespace Components void Command::MainCallback() { - Command::ClientParams params(*Game::cmd_id); + Command::ClientParams params; - std::string command = Utils::String::ToLower(params[0]); + const auto command = Utils::String::ToLower(params[0]); + const auto got = Command::FunctionMap.find(command); - if (Command::FunctionMap.find(command) != Command::FunctionMap.end()) + if (got != Command::FunctionMap.end()) { - Command::FunctionMap[command](¶ms); + got->second(¶ms); } } void Command::MainCallbackSV() { - Command::ServerParams params(*Game::cmd_id_sv); + Command::ServerParams params; - std::string command = Utils::String::ToLower(params[0]); + const auto command = Utils::String::ToLower(params[0]); + const auto got = Command::FunctionMapSV.find(command); - if (Command::FunctionMapSV.find(command) != Command::FunctionMapSV.end()) + if (got != Command::FunctionMapSV.end()) { - Command::FunctionMapSV[command](¶ms); + got->second(¶ms); } } @@ -160,115 +177,12 @@ namespace Components { AssertSize(Game::cmd_function_t, 24); - static int toastDurationShort = 1000; - static int toastDurationMedium = 2500; - static int toastDurationLong = 5000; - - // Disable native noclip command - Utils::Hook::Nop(0x474846, 5); - - Command::Add("noclip", [](Command::Params*) - { - int clientNum = Game::CG_GetClientNum(); - if (!Game::CL_IsCgameInitialized() || clientNum >= 18 || clientNum < 0 || !Game::g_entities[clientNum].client) - { - Logger::Print("You are not hosting a match!\n"); - Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", toastDurationMedium); - return; - } - - if (!Dvar::Var("sv_cheats").get()) - { - Logger::Print("Cheats disabled!\n"); - Toast::Show("cardicon_stop", "Error", "Cheats disabled!", toastDurationMedium); - return; - } - - Game::g_entities[clientNum].client->flags ^= Game::PLAYER_FLAG_NOCLIP; - - Logger::Print("Noclip toggled\n"); - Toast::Show("cardicon_abduction", "Success", "Noclip toggled", toastDurationShort); - }); - - Command::Add("ufo", [](Command::Params*) - { - int clientNum = Game::CG_GetClientNum(); - if (!Game::CL_IsCgameInitialized() || clientNum >= 18 || clientNum < 0 || !Game::g_entities[clientNum].client) - { - Logger::Print("You are not hosting a match!\n"); - Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", toastDurationMedium); - return; - } - - if (!Dvar::Var("sv_cheats").get()) - { - Logger::Print("Cheats disabled!\n"); - Toast::Show("cardicon_stop", "Error", "Cheats disabled!", toastDurationMedium); - return; - } - - Game::g_entities[clientNum].client->flags ^= Game::PLAYER_FLAG_UFO; - - Logger::Print("UFO toggled\n"); - Toast::Show("cardicon_abduction", "Success", "UFO toggled", toastDurationShort); - }); - - Command::Add("setviewpos", [](Command::Params* params) - { - int clientNum = Game::CG_GetClientNum(); - if (!Game::CL_IsCgameInitialized() || clientNum >= 18 || clientNum < 0 || !Game::g_entities[clientNum].client) - { - Logger::Print("You are not hosting a match!\n"); - Toast::Show("cardicon_stop", "Error", "You are not hosting a match!", toastDurationMedium); - return; - } - - if (!Dvar::Var("sv_cheats").get()) - { - Logger::Print("Cheats disabled!\n"); - Toast::Show("cardicon_stop", "Error", "Cheats disabled!", toastDurationMedium); - return; - } - - if (params->length() != 4 && params->length() != 6) - { - Logger::Print("Invalid coordinate specified!\n"); - Toast::Show("cardicon_stop", "Error", "Invalid coordinate specified!", toastDurationMedium); - return; - } - - float pos[3] = { 0.0f, 0.0f, 0.0f }; - float orientation[3] = { 0.0f, 0.0f, 0.0f }; - - pos[0] = strtof(params->get(1), nullptr); - pos[1] = strtof(params->get(2), nullptr); - pos[2] = strtof(params->get(3), nullptr); - - if (params->length() == 6) - { - orientation[0] = strtof(params->get(4), nullptr); - orientation[1] = strtof(params->get(5), nullptr); - } - - Game::TeleportPlayer(&Game::g_entities[clientNum], pos, orientation); - - // Logging that will spam the console and screen if people use cinematics - //Logger::Print("Successfully teleported player!\n"); - //Toast::Show("cardicon_abduction", "Success", "You have been teleported!", toastDurationShort); - }); - Command::Add("openLink", [](Command::Params* params) { - if (params->length() > 1) + if (params->size() > 1) { Utils::OpenUrl(params->get(1)); } }); } - - Command::~Command() - { - Command::FunctionMap.clear(); - Command::FunctionMapSV.clear(); - } } diff --git a/src/Components/Modules/Command.hpp b/src/Components/Modules/Command.hpp index 17f90d7c..d6379a94 100644 --- a/src/Components/Modules/Command.hpp +++ b/src/Components/Modules/Command.hpp @@ -9,46 +9,44 @@ namespace Components { public: Params() {}; - virtual ~Params() {}; - virtual char* get(size_t index) = 0; - virtual size_t length() = 0; - virtual std::string join(size_t startIndex); - virtual char* operator[](size_t index); + virtual int size() = 0; + virtual const char* get(int index) = 0; + virtual std::string join(int index); + + virtual const char* operator[](const int index) + { + return this->get(index); + } }; class ClientParams : public Params { public: - ClientParams(unsigned int id) : commandId(id) {}; - ClientParams(const ClientParams &obj) : commandId(obj.commandId) {}; - ClientParams() : ClientParams(*Game::cmd_id) {}; + ClientParams(); - char* get(size_t index) override; - size_t length() override; + int size() override; + const char* get(int index) override; private: - unsigned int commandId; + int nesting_; }; class ServerParams : public Params { public: - ServerParams(unsigned int id) : commandId(id) {}; - ServerParams(const ServerParams &obj) : commandId(obj.commandId) {}; - ServerParams() : ServerParams(*Game::cmd_id_sv) {}; + ServerParams(); - char* get(size_t index) override; - size_t length() override; + int size() override; + const char* get(int index) override; private: - unsigned int commandId; + int nesting_; }; typedef void(Callback)(Command::Params* params); Command(); - ~Command(); static Game::cmd_function_t* Allocate(); diff --git a/src/Components/Modules/ConnectProtocol.cpp b/src/Components/Modules/ConnectProtocol.cpp index 3b716c53..c07d3df8 100644 --- a/src/Components/Modules/ConnectProtocol.cpp +++ b/src/Components/Modules/ConnectProtocol.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index 01869043..c50f723c 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -45,7 +45,7 @@ namespace Components void Console::RefreshStatus() { std::string mapname = Dvar::Var("mapname").get(); - std::string hostname = Colors::Strip(Dvar::Var("sv_hostname").get()); + std::string hostname = TextRenderer::StripColors(Dvar::Var("sv_hostname").get()); if (Console::HasConsole) { @@ -504,7 +504,8 @@ namespace Components Console::ConsoleThread = std::thread(Console::ConsoleRunner); } - Game::dvar_t* Console::RegisterConColor(const char* name, float r, float g, float b, float a, float min, float max, int flags, const char* description) + Game::dvar_t* Console::RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, + float max, unsigned __int16 flags, const char* description) { static struct { @@ -521,7 +522,7 @@ namespace Components for (int i = 0; i < ARRAYSIZE(patchedColors); ++i) { - if (std::string(name) == patchedColors[i].name) + if (std::strcmp(dvarName, patchedColors[i].name) == 0) { r = patchedColors[i].color[0]; g = patchedColors[i].color[1]; @@ -531,7 +532,7 @@ namespace Components } } - return reinterpret_cast(0x471500)(name, r, g, b, a, min, max, flags, description); + return reinterpret_cast(0x471500)(dvarName, r, g, b, a, min, max, flags, description); } Console::Console() diff --git a/src/Components/Modules/Console.hpp b/src/Components/Modules/Console.hpp index 0ef30bb5..2ca6eb60 100644 --- a/src/Components/Modules/Console.hpp +++ b/src/Components/Modules/Console.hpp @@ -66,6 +66,6 @@ namespace Components static void ToggleConsole(); static char** GetAutoCompleteFileList(const char *path, const char *extension, Game::FsListBehavior_e behavior, int *numfiles, int allocTrackType); - static Game::dvar_t* RegisterConColor(const char* name, float r, float g, float b, float a, float min, float max, int flags, const char* description); + static Game::dvar_t* RegisterConColor(const char* dvarName, float r, float g, float b, float a, float min, float max, unsigned __int16 flags, const char* description); }; } diff --git a/src/Components/Modules/D3D9Ex.cpp b/src/Components/Modules/D3D9Ex.cpp index d15f66b9..d9222836 100644 --- a/src/Components/Modules/D3D9Ex.cpp +++ b/src/Components/Modules/D3D9Ex.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -748,7 +748,7 @@ namespace Components { if (Dedicated::IsEnabled()) return; - Dvar::Register("r_useD3D9Ex", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Use extended d3d9 interface!"); + Dvar::Register("r_useD3D9Ex", false, Game::dvar_flag::DVAR_ARCHIVE, "Use extended d3d9 interface!"); // Hook Interface creation Utils::Hook::Set(0x6D74D0, D3D9Ex::Direct3DCreate9Stub); diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 98a77fbc..b56e9ccb 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -1,10 +1,9 @@ -#include "STDInclude.hpp" +#include namespace Components { SteamID Dedicated::PlayerGuids[18][2]; - - bool Dedicated::SendChat; + Dvar::Var Dedicated::SVRandomMapRotation; bool Dedicated::IsEnabled() { @@ -76,74 +75,6 @@ namespace Components } } - const char* Dedicated::EvaluateSay(char* text, Game::gentity_t* player) - { - Dedicated::SendChat = true; - - if (text[1] == '/') - { - Dedicated::SendChat = false; - text[1] = text[0]; - ++text; - } - - Game::Scr_AddEntity(player); - Game::Scr_AddString(text + 1); - Game::Scr_NotifyLevel(Game::SL_GetString("say", 0), 2); - - return text; - } - - __declspec(naked) void Dedicated::PreSayStub() - { - __asm - { - mov eax, [esp + 100h + 10h] - - push eax - pushad - - push[esp + 100h + 28h] - push eax - call Dedicated::EvaluateSay - add esp, 8h - - mov [esp + 20h], eax - popad - pop eax - - mov [esp + 100h + 10h], eax - - jmp Colors::CleanStrStub - } - } - - __declspec(naked) void Dedicated::PostSayStub() - { - __asm - { - // eax is used by the callee - push eax - - xor eax, eax - mov al, Dedicated::SendChat - - test al, al - jnz return - - // Don't send the chat - pop eax - retn - - return: - pop eax - - // Jump to the target - push 5DF620h - retn - } - } - void Dedicated::TransmitGuids() { std::string list = Utils::String::VA("%c", 20); @@ -152,7 +83,7 @@ namespace Components { if (Game::svs_clients[i].state >= 3) { - list.append(Utils::String::VA(" %llX", Game::svs_clients[i].steamid)); + list.append(Utils::String::VA(" %llX", Game::svs_clients[i].steamID)); Utils::InfoString info(Game::svs_clients[i].connectInfoString); list.append(Utils::String::VA(" %llX", strtoull(info.get("realsteamId").data(), nullptr, 16))); @@ -166,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; @@ -189,11 +120,46 @@ namespace Components Game::Com_Error(code, message); } + void Dedicated::RandomizeMapRotation() + { + auto rotation = Dvar::Var("sv_mapRotation").get(); + + const auto tokens = Utils::String::Split(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()) { - Dvar::Var("sv_dontrotate").setRaw(0); + Dvar::Var("sv_dontrotate").set(false); return; } @@ -204,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"); @@ -222,16 +189,25 @@ 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, ' '); + auto tokens = Utils::String::Split(rotation, ' '); for (unsigned int i = 0; i < (tokens.size() - 1); i += 2) { @@ -297,24 +273,24 @@ namespace Components } } + Game::dvar_t* Dedicated::Dvar_RegisterSVNetworkFps(const char* dvarName, int, int min, int, int, const char* description) + { + return Game::Dvar_RegisterInt(dvarName, 1000, min, 1000, Game::dvar_flag::DVAR_NONE, description); + } + Dedicated::Dedicated() { // Map rotation Utils::Hook::Set(0x4152E8, Dedicated::MapRotate); - Dvar::Register("sv_dontrotate", false, Game::dvar_flag::DVAR_FLAG_CHEAT, ""); - Dvar::Register("com_logFilter", true, Game::dvar_flag::DVAR_FLAG_LATCHED, "Removes ~95% of unneeded lines from the log"); - - // Intercept chat sending - Utils::Hook(0x4D000B, Dedicated::PreSayStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x4D00D4, Dedicated::PostSayStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x4D0110, Dedicated::PostSayStub, HOOK_CALL).install()->quick(); + Dvar::Register("sv_dontrotate", false, Game::dvar_flag::DVAR_CHEAT, ""); + Dvar::Register("com_logFilter", true, Game::dvar_flag::DVAR_LATCH, "Removes ~95% of unneeded lines from the log"); if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) { // Make sure all callbacks are handled Scheduler::OnFrame(Steam::SteamAPI_RunCallbacks); - Dvar::Register("sv_lanOnly", false, Game::dvar_flag::DVAR_FLAG_NONE, "Don't act as node"); + Dvar::Register("sv_lanOnly", false, Game::dvar_flag::DVAR_NONE, "Don't act as node"); Utils::Hook(0x60BE98, Dedicated::InitDedicatedServer, HOOK_CALL).install()->quick(); @@ -340,7 +316,7 @@ namespace Components Utils::Hook::Nop(0x4DCEC9, 2); // some check preventing proper game functioning Utils::Hook::Nop(0x507C79, 6); // another similar bsp check - Utils::Hook::Nop(0x414E4D, 6); // unknown check in SV_ExecuteClientMessage (0x20F0890 == 0, related to client->f_40) + Utils::Hook::Nop(0x414E4D, 6); // cl->messageAcknowledge > cl->gamestateMessageNum check in SV_ExecuteClientMessage Utils::Hook::Nop(0x4DCEE9, 5); // some deinit renderer function Utils::Hook::Nop(0x59A896, 5); // warning message on a removed subsystem Utils::Hook::Nop(0x4B4EEF, 5); // same as above @@ -355,14 +331,8 @@ namespace Components // isHost script call return 0 Utils::Hook::Set(0x5DEC04, 0); - // sv_network_fps max 1000, and uncheat - Utils::Hook::Set(0x4D3C67, 0); // ? - Utils::Hook::Set(0x4D3C69, 1000); - // Manually register sv_network_fps - Utils::Hook::Nop(0x4D3C7B, 5); - Utils::Hook::Nop(0x4D3C8E, 5); - *reinterpret_cast(0x62C7C00) = Dvar::Register("sv_network_fps", 1000, 20, 1000, Game::dvar_flag::DVAR_FLAG_NONE, "Number of times per second the server checks for net messages").get(); + Utils::Hook(0x4D3C7B, Dedicated::Dvar_RegisterSVNetworkFps, HOOK_CALL).install()->quick(); // r_loadForRenderer default to 0 Utils::Hook::Set(0x519DDF, 0); @@ -421,13 +391,14 @@ namespace Components Dvar::OnInit([]() { - 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"); + Dedicated::SVRandomMapRotation = Dvar::Register("sv_randomMapRotation", false, Game::dvar_flag::DVAR_ARCHIVE, "Randomize map rotation when true"); + Dvar::Register("sv_sayName", "^7Console", Game::dvar_flag::DVAR_NONE, "The name to pose as for 'say' commands"); + Dvar::Register("sv_motd", "", Game::dvar_flag::DVAR_NONE, "A custom message of the day for servers"); // Say command Command::AddSV("say", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; std::string message = params->join(1); std::string name = Dvar::Var("sv_sayName").get(); @@ -447,7 +418,7 @@ namespace Components // Tell command Command::AddSV("tell", [](Command::Params* params) { - if (params->length() < 3) return; + if (params->size() < 3) return; int client = atoi(params->get(1)); std::string message = params->join(2); @@ -468,7 +439,7 @@ namespace Components // Sayraw command Command::AddSV("sayraw", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; std::string message = params->join(1); Game::SV_GameSendServerCommand(-1, 0, Utils::String::VA("%c \"%s\"", 104, message.data())); @@ -478,33 +449,13 @@ namespace Components // Tellraw command Command::AddSV("tellraw", [](Command::Params* params) { - if (params->length() < 3) return; + if (params->size() < 3) return; int client = atoi(params->get(1)); std::string message = params->join(2); Game::SV_GameSendServerCommand(client, 0, Utils::String::VA("%c \"%s\"", 104, message.data())); Game::Com_Printf(15, "Raw -> %i: %s\n", client, message.data()); }); - - // ! command - Command::AddSV("!", [](Command::Params* params) - { - if (params->length() != 2) return; - - int client = -1; - if (params->get(1) != "all"s) - { - client = atoi(params->get(1)); - - if (client >= *reinterpret_cast(0x31D938C)) - { - Game::Com_Printf(0, "Invalid player.\n"); - return; - } - } - - Game::SV_GameSendServerCommand(client, 0, Utils::String::VA("%c \"\"", 106)); - }); }); } } @@ -548,9 +499,4 @@ namespace Components } }); } - - Dedicated::~Dedicated() - { - - } } diff --git a/src/Components/Modules/Dedicated.hpp b/src/Components/Modules/Dedicated.hpp index 002d28bb..cfcbc539 100644 --- a/src/Components/Modules/Dedicated.hpp +++ b/src/Components/Modules/Dedicated.hpp @@ -6,7 +6,6 @@ namespace Components { public: Dedicated(); - ~Dedicated(); static SteamID PlayerGuids[18][2]; @@ -15,23 +14,21 @@ namespace Components static void Heartbeat(); private: - static bool SendChat; + static Dvar::Var SVRandomMapRotation; + static void RandomizeMapRotation(); static void MapRotate(); static void InitDedicatedServer(); static void PostInitialization(); static void PostInitializationStub(); - static const char* EvaluateSay(char* text, Game::gentity_t* player); - - static void PreSayStub(); - static void PostSayStub(); - static void FrameStub(); static void TransmitGuids(); - static void TimeWrapStub(int code, const char* message); + static void TimeWrapStub(Game::errorParm_t code, const char* message); + + static Game::dvar_t* Dvar_RegisterSVNetworkFps(const char* dvarName, int value, int min, int max, int flags, const char* description); }; } diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index 833b7103..93dbf96f 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -14,8 +14,8 @@ namespace Components Discovery::Discovery() { - Dvar::Register("net_discoveryPortRangeMin", 25000, 0, 65535, Game::dvar_flag::DVAR_FLAG_SAVED, "Minimum scan range port for local server discovery"); - Dvar::Register("net_discoveryPortRangeMax", 35000, 1, 65536, Game::dvar_flag::DVAR_FLAG_SAVED, "Maximum scan range port for local server discovery"); + Dvar::Register("net_discoveryPortRangeMin", 25000, 0, 65535, Game::dvar_flag::DVAR_ARCHIVE, "Minimum scan range port for local server discovery"); + Dvar::Register("net_discoveryPortRangeMax", 35000, 1, 65536, Game::dvar_flag::DVAR_ARCHIVE, "Maximum scan range port for local server discovery"); // An additional thread prevents lags // Not sure if that's the best way though @@ -95,11 +95,6 @@ namespace Components #endif } - Discovery::~Discovery() - { - - } - void Discovery::preDestroy() { Discovery::IsPerforming = false; diff --git a/src/Components/Modules/Discovery.hpp b/src/Components/Modules/Discovery.hpp index 655122bf..63f69b1b 100644 --- a/src/Components/Modules/Discovery.hpp +++ b/src/Components/Modules/Discovery.hpp @@ -6,7 +6,6 @@ namespace Components { public: Discovery(); - ~Discovery(); void preDestroy() override; diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index f4c25566..02b1de2a 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -387,7 +387,7 @@ namespace Components if (client->state >= 3) { - if (address.getIP().full == Network::Address(client->addr).getIP().full) + if (address.getIP().full == Network::Address(client->netchan.remoteAddress).getIP().full) { return client; } @@ -902,9 +902,9 @@ namespace Components { Dvar::OnInit([]() { - Dvar::Register("ui_dl_timeLeft", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); - Dvar::Register("ui_dl_progress", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); - Dvar::Register("ui_dl_transRate", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); + Dvar::Register("ui_dl_timeLeft", "", Game::dvar_flag::DVAR_NONE, ""); + Dvar::Register("ui_dl_progress", "", Game::dvar_flag::DVAR_NONE, ""); + Dvar::Register("ui_dl_transRate", "", Game::dvar_flag::DVAR_NONE, ""); }); UIScript::Add("mod_download_cancel", [](UIScript::Token) @@ -915,13 +915,13 @@ namespace Components Dvar::OnInit([]() { - Dvar::Register("sv_wwwDownload", false, Game::dvar_flag::DVAR_FLAG_DEDISAVED, "Set to true to enable downloading maps/mods from an external server."); - Dvar::Register("sv_wwwBaseUrl", "", Game::dvar_flag::DVAR_FLAG_DEDISAVED, "Set to the base url for the external map download."); + Dvar::Register("sv_wwwDownload", false, Game::dvar_flag::DVAR_ARCHIVE, "Set to true to enable downloading maps/mods from an external server."); + Dvar::Register("sv_wwwBaseUrl", "", Game::dvar_flag::DVAR_ARCHIVE, "Set to the base url for the external map download."); - // Force users to enable this because we don't want to accidentally turn everyone's pc into a http server into all their files again - // not saying we are but ya know... accidents happen - // by having it saved we force the user to enable it in config_mp because it only checks the dvar on startup to see if we should init download or not - Dvar::Register("mod_force_download_server", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Set to true to force the client to run the download server for mods (for mods in private matches)."); + // Force users to enable this because we don't want to accidentally turn everyone's pc into a http server into all their files again + // not saying we are but ya know... accidents happen + // by having it saved we force the user to enable it in config_mp because it only checks the dvar on startup to see if we should init download or not + Dvar::Register("mod_force_download_server", false, Game::dvar_flag::DVAR_ARCHIVE, "Set to true to force the client to run the download server for mods (for mods in private matches)."); }); Scheduler::OnFrame([]() @@ -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/Download.hpp b/src/Components/Modules/Download.hpp index 1894c079..90472069 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -1,5 +1,5 @@ #pragma once -#include "Game/Functions.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index 13a473b5..99acf132 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -1,18 +1,19 @@ -#include "STDInclude.hpp" +#include namespace Components { Utils::Signal Dvar::RegistrationSignal; + const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg"; Dvar::Var::Var(const std::string& dvarName) : Var() { this->dvar = Game::Dvar_FindVar(dvarName.data()); - if (!this->dvar) + // If the dvar can't be found it will be registered as an empty string dvar + if (this->dvar == nullptr) { - // Quick-register the dvar - Game::Dvar_SetStringByName(dvarName.data(), ""); - this->dvar = Game::Dvar_FindVar(dvarName.data()); + this->dvar = const_cast(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", + Game::DvarSetSource::DVAR_SOURCE_INTERNAL)); } } @@ -20,127 +21,184 @@ namespace Components { return this->dvar; } - template <> char* Dvar::Var::get() - { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING && this->dvar->current.string) - { - return const_cast(this->dvar->current.string); - } - return const_cast(""); - } template <> const char* Dvar::Var::get() { - return this->get(); + if (this->dvar == nullptr) + return ""; + + if (this->dvar->type == Game::dvar_type::DVAR_TYPE_STRING + || this->dvar->type == Game::dvar_type::DVAR_TYPE_ENUM) + { + if (this->dvar->current.string != nullptr) + return this->dvar->current.string; + } + + return ""; } + template <> int Dvar::Var::get() { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_INT) + if (this->dvar == nullptr) + return 0; + + if (this->dvar->type == Game::dvar_type::DVAR_TYPE_INT || this->dvar->type == Game::dvar_type::DVAR_TYPE_ENUM) { return this->dvar->current.integer; } return 0; } + template <> unsigned int Dvar::Var::get() { - return static_cast(this->get()); + if (this->dvar == nullptr) + return 0u; + + if (this->dvar->type == Game::dvar_type::DVAR_TYPE_INT) + { + return this->dvar->current.unsignedInt; + } + + return 0u; } + template <> float Dvar::Var::get() { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT) + if (this->dvar == nullptr) + return 0.f; + + if (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT) { return this->dvar->current.value; } - return 0; + return 0.f; } + template <> float* Dvar::Var::get() { - static float val[4] = { 0 }; + static Game::vec4_t vector{ 0.f, 0.f, 0.f, 0.f }; - if (this->dvar && (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4)) + if (this->dvar == nullptr) + return vector; + + if (this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_2 || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_3 + || this->dvar->type == Game::dvar_type::DVAR_TYPE_FLOAT_4) { return this->dvar->current.vector; } - return val; + return vector; } + template <> bool Dvar::Var::get() { - if (this->dvar && this->dvar->type == Game::dvar_type::DVAR_TYPE_BOOL) + if (this->dvar == nullptr) + return false; + + if (this->dvar->type == Game::dvar_type::DVAR_TYPE_BOOL) { return this->dvar->current.enabled; } return false; } + template <> std::string Dvar::Var::get() { return this->get(); } - void Dvar::Var::set(char* string) - { - this->set(const_cast(string)); - } void Dvar::Var::set(const char* string) { - if (this->dvar && this->dvar->name) + assert(this->dvar->type == Game::DVAR_TYPE_STRING); + if (this->dvar) { - Game::Dvar_SetCommand(this->dvar->name, string); + Game::Dvar_SetString(this->dvar, string); } } + void Dvar::Var::set(const std::string& string) { this->set(string.data()); } + void Dvar::Var::set(int integer) { - if (this->dvar && this->dvar->name) + assert(this->dvar->type == Game::DVAR_TYPE_INT); + if (this->dvar) { - Game::Dvar_SetCommand(this->dvar->name, Utils::String::VA("%i", integer)); + Game::Dvar_SetInt(this->dvar, integer); } } + void Dvar::Var::set(float value) { - if (this->dvar && this->dvar->name) + assert(this->dvar->type == Game::DVAR_TYPE_FLOAT); + if (this->dvar) { - Game::Dvar_SetCommand(this->dvar->name, Utils::String::VA("%f", value)); + Game::Dvar_SetFloat(this->dvar, value); + } + } + + void Dvar::Var::set(bool enabled) + { + assert(this->dvar->type == Game::DVAR_TYPE_BOOL); + if (this->dvar) + { + Game::Dvar_SetBool(this->dvar, enabled); } } void Dvar::Var::setRaw(int integer) { + assert(this->dvar->type == Game::DVAR_TYPE_INT); if (this->dvar) { this->dvar->current.integer = integer; + this->dvar->latched.integer = integer; } } void Dvar::Var::setRaw(float value) { + assert(this->dvar->type == Game::DVAR_TYPE_FLOAT); if (this->dvar) { this->dvar->current.value = value; + this->dvar->latched.value = value; } } - template<> static Dvar::Var Dvar::Register(const char* name, bool value, Dvar::Flag flag, const char* description) + void Dvar::Var::setRaw(bool enabled) { - return Game::Dvar_RegisterBool(name, value, flag.val, description); + assert(this->dvar->type == Game::DVAR_TYPE_BOOL); + if (this->dvar) + { + this->dvar->current.enabled = enabled; + this->dvar->latched.enabled = enabled; + } } - template<> static Dvar::Var Dvar::Register(const char* name, const char* value, Dvar::Flag flag, const char* description) + + template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Dvar::Flag flag, const char* description) { - return Game::Dvar_RegisterString(name, value, flag.val, description); + return Game::Dvar_RegisterBool(dvarName, value, flag.val, description); } - template<> static Dvar::Var Dvar::Register(const char* name, int value, int min, int max, Dvar::Flag flag, const char* description) + + template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Dvar::Flag flag, const char* description) { - return Game::Dvar_RegisterInt(name, value, min, max, flag.val, description); + return Game::Dvar_RegisterString(dvarName, value, flag.val, description); } - template<> static Dvar::Var Dvar::Register(const char* name, float value, float min, float max, Dvar::Flag flag, const char* description) + + template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Dvar::Flag flag, const char* description) { - return Game::Dvar_RegisterFloat(name, value, min, max, flag.val, description); + return Game::Dvar_RegisterInt(dvarName, value, min, max, flag.val, description); + } + + template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Dvar::Flag flag, const char* description) + { + return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description); } void Dvar::OnInit(Utils::Slot callback) @@ -148,6 +206,16 @@ namespace Components Dvar::RegistrationSignal.connect(callback); } + void Dvar::ResetDvarsValue() + { + if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath)) + return; + + Command::Execute("exec archivedvars.cfg", true); + // Clean up + Utils::IO::RemoveFile(Dvar::ArchiveDvarPath); + } + Game::dvar_t* Dvar::RegisterName(const char* name, const char* /*default*/, Game::dvar_flag flag, const char* description) { // Run callbacks @@ -157,12 +225,12 @@ namespace Components Scheduler::OnFrame([]() { static std::string lastValidName = "Unknown Soldier"; - std::string name = Dvar::Var("name").get(); + std::string name = Dvar::Var("name").get(); // Don't perform any checks if name didn't change if (name == lastValidName) return; - std::string saneName = Colors::Strip(Utils::String::Trim(name)); + std::string saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(Utils::String::Trim(name))); if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{')) { Logger::Print("Username '%s' is invalid. It must at least be 3 characters long and not appear empty!\n", name.data()); @@ -187,10 +255,10 @@ namespace Components } } - return Dvar::Register(name, username.data(), Dvar::Flag(flag | Game::dvar_flag::DVAR_FLAG_SAVED).val, description).get(); + return Dvar::Register(name, username.data(), Dvar::Flag(flag | Game::dvar_flag::DVAR_ARCHIVE).val, description).get(); } - Game::dvar_t* Dvar::SetFromStringByNameSafeExternal(const char* dvar, const char* value) + void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string) { static const char* exceptions[] = { @@ -206,63 +274,91 @@ namespace Components for (int i = 0; i < ARRAYSIZE(exceptions); ++i) { - if (Utils::String::ToLower(dvar) == Utils::String::ToLower(exceptions[i])) + if (Utils::String::ToLower(dvarName) == Utils::String::ToLower(exceptions[i])) { - return Game::Dvar_SetFromStringByName(dvar, value); + Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DvarSetSource::DVAR_SOURCE_INTERNAL); + return; } } - return Dvar::SetFromStringByNameExternal(dvar, value); + Dvar::SetFromStringByNameExternal(dvarName, string); } - Game::dvar_t* Dvar::SetFromStringByNameExternal(const char* dvar, const char* value) + void Dvar::SetFromStringByNameExternal(const char* dvarName, const char* string) { - return Game::Dvar_SetFromStringByNameFromSource(dvar, value, Game::DvarSetSource::DVAR_SOURCE_EXTERNAL); + Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DvarSetSource::DVAR_SOURCE_EXTERNAL); + } + + void Dvar::SaveArchiveDvar(const Game::dvar_t* var) + { + if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath)) + { + Utils::IO::WriteFile(Dvar::ArchiveDvarPath, + "// generated by IW4x, do not modify\n"); + } + + Utils::IO::WriteFile(Dvar::ArchiveDvarPath, + Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true); + } + + void Dvar::DvarSetFromStringByNameStub(const char* dvarName, const char* value) + { + // Save the dvar original value if it has the archive flag + const auto* dvar = Game::Dvar_FindVar(dvarName); + if (dvar != nullptr && dvar->flags & Game::dvar_flag::DVAR_ARCHIVE) + { + Dvar::SaveArchiveDvar(dvar); + } + + Utils::Hook::Call(0x4F52E0)(dvarName, value); } Dvar::Dvar() { // set flags of cg_drawFPS to archive - Utils::Hook::Or(0x4F8F69, Game::dvar_flag::DVAR_FLAG_SAVED); + Utils::Hook::Or(0x4F8F69, Game::dvar_flag::DVAR_ARCHIVE); + // un-cheat camera_thirdPersonCrosshairOffset and add archive flags + Utils::Hook::Xor(0x447B41, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); + // un-cheat cg_fov and add archive flags - Utils::Hook::Xor(0x4F8E35, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); + Utils::Hook::Xor(0x4F8E35, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); // un-cheat cg_fovscale and add archive flags - Utils::Hook::Xor(0x4F8E68, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); + Utils::Hook::Xor(0x4F8E68, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); // un-cheat cg_debugInfoCornerOffset and add archive flags - Utils::Hook::Xor(0x4F8FC2, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); + Utils::Hook::Xor(0x4F8FC2, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); // remove archive flags for cg_hudchatposition - Utils::Hook::Xor(0x4F9992, Game::dvar_flag::DVAR_FLAG_SAVED); + Utils::Hook::Xor(0x4F9992, Game::dvar_flag::DVAR_ARCHIVE); // remove write protection from fs_game - Utils::Hook::Xor(0x6431EA, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED); + Utils::Hook::Xor(0x6431EA, Game::dvar_flag::DVAR_WRITEPROTECTED); - // set cg_fov max to 90.0 - // ...120 because of V2 - static float cgFov90 = 120.0f; - Utils::Hook::Set(0x4F8E28, &cgFov90); + // set cg_fov max to 160.0 + // because that's the max on SP + static float cg_Fov = 160.0f; + Utils::Hook::Set(0x4F8E28, &cg_Fov); // set max volume to 1 static float volume = 1.0f; Utils::Hook::Set(0x408078, &volume); // Uncheat ui_showList - Utils::Hook::Xor(0x6310DC, Game::dvar_flag::DVAR_FLAG_CHEAT); + Utils::Hook::Xor(0x6310DC, Game::dvar_flag::DVAR_CHEAT); // Uncheat ui_debugMode - Utils::Hook::Xor(0x6312DE, Game::dvar_flag::DVAR_FLAG_CHEAT); + Utils::Hook::Xor(0x6312DE, Game::dvar_flag::DVAR_CHEAT); // Hook dvar 'name' registration Utils::Hook(0x40531C, Dvar::RegisterName, HOOK_CALL).install()->quick(); // un-cheat safeArea_* and add archive flags - Utils::Hook::Xor(0x42E3F5, Game::dvar_flag::DVAR_FLAG_READONLY | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_adjusted_horizontal - Utils::Hook::Xor(0x42E423, Game::dvar_flag::DVAR_FLAG_READONLY | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_adjusted_vertical - Utils::Hook::Xor(0x42E398, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_horizontal - Utils::Hook::Xor(0x42E3C4, Game::dvar_flag::DVAR_FLAG_CHEAT | Game::dvar_flag::DVAR_FLAG_SAVED); //safeArea_vertical + Utils::Hook::Xor(0x42E3F5, Game::dvar_flag::DVAR_READONLY | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_adjusted_horizontal + Utils::Hook::Xor(0x42E423, Game::dvar_flag::DVAR_READONLY | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_adjusted_vertical + Utils::Hook::Xor(0x42E398, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_horizontal + Utils::Hook::Xor(0x42E3C4, Game::dvar_flag::DVAR_CHEAT | Game::dvar_flag::DVAR_ARCHIVE); //safeArea_vertical // Don't allow setting cheat protected dvars via menus Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); @@ -270,7 +366,7 @@ namespace Components Utils::Hook(0x63CDB5, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); Utils::Hook(0x635E47, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - // SetDvar + // Script_SetDvar Utils::Hook(0x63444C, Dvar::SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick(); // Slider @@ -284,10 +380,20 @@ namespace Components // Entirely block setting cheat dvars internally without sv_cheats //Utils::Hook(0x4F52EC, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + + // Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server + Utils::Hook(0x59386A, Dvar::DvarSetFromStringByNameStub, HOOK_CALL).install()->quick(); + + // If the game closed abruptly, the dvars would not have been restored + Dvar::OnInit([] + { + Dvar::ResetDvarsValue(); + }); } Dvar::~Dvar() { Dvar::RegistrationSignal.clear(); + Utils::IO::RemoveFile(Dvar::ArchiveDvarPath); } } diff --git a/src/Components/Modules/Dvar.hpp b/src/Components/Modules/Dvar.hpp index 4775b575..c140ac73 100644 --- a/src/Components/Modules/Dvar.hpp +++ b/src/Components/Modules/Dvar.hpp @@ -9,7 +9,7 @@ namespace Components { public: Flag(Game::dvar_flag flag) : val(flag) {}; - Flag(int flag) : Flag(static_cast(flag)) {}; + Flag(unsigned __int16 flag) : Flag(static_cast(flag)) {}; Game::dvar_flag val; }; @@ -18,23 +18,23 @@ namespace Components { public: Var() : dvar(nullptr) {}; - Var(const Var &obj) { this->dvar = obj.dvar; }; + Var(const Var& obj) { this->dvar = obj.dvar; }; Var(Game::dvar_t* _dvar) : dvar(_dvar) {}; Var(DWORD ppdvar) : Var(*reinterpret_cast(ppdvar)) {}; Var(const std::string& dvarName); template T get(); - void set(char* string); void set(const char* string); void set(const std::string& string); void set(int integer); void set(float value); + void set(bool enabled); - // TODO: Add others void setRaw(int integer); void setRaw(float value); + void setRaw(bool enabled); private: Game::dvar_t* dvar; @@ -46,15 +46,21 @@ namespace Components static void OnInit(Utils::Slot callback); // Only strings and bools use this type of declaration - template static Var Register(const char* name, T value, Flag flag, const char* description); - template static Var Register(const char* name, T value, T min, T max, Flag flag, const char* description); + template static Var Register(const char* dvarName, T value, Flag flag, const char* description); + template static Var Register(const char* dvarName, T value, T min, T max, Flag flag, const char* description); + + static void ResetDvarsValue(); private: static Utils::Signal RegistrationSignal; + static const char* ArchiveDvarPath; static Game::dvar_t* RegisterName(const char* name, const char* defaultVal, Game::dvar_flag flag, const char* description); - static Game::dvar_t* SetFromStringByNameExternal(const char* dvar, const char* value); - static Game::dvar_t* SetFromStringByNameSafeExternal(const char* dvar, const char* value); + static void SetFromStringByNameExternal(const char* dvar, const char* value); + static void SetFromStringByNameSafeExternal(const char* dvar, const char* value); + + static void SaveArchiveDvar(const Game::dvar_t* var); + static void DvarSetFromStringByNameStub(const char* dvarName, const char* value); }; } diff --git a/src/Components/Modules/Elevators.cpp b/src/Components/Modules/Elevators.cpp new file mode 100644 index 00000000..08dffeb7 --- /dev/null +++ b/src/Components/Modules/Elevators.cpp @@ -0,0 +1,123 @@ +#include + +namespace Components +{ + Dvar::Var Elevators::BG_Elevators; + + int Elevators::PM_CorrectAllSolid(Game::pmove_s* pm, Game::pml_t* pml, Game::trace_t* trace) + { + assert(pm != nullptr); + assert(pm->ps != nullptr); + + Game::vec3_t point; + auto* ps = pm->ps; + + auto i = 0; + const auto elevatorSetting = Elevators::BG_Elevators.get(); + while (true) + { + point[0] = ps->origin[0] + Game::CorrectSolidDeltas[i][0]; + point[1] = ps->origin[1] + Game::CorrectSolidDeltas[i][1]; + point[2] = ps->origin[2] + Game::CorrectSolidDeltas[i][2]; + + Game::PM_playerTrace(pm, trace, point, point, &pm->bounds, ps->clientNum, pm->tracemask); + + // If the player wishes to glitch without effort they can do so + if (!trace->startsolid || elevatorSetting == Elevators::EASY) + { + ps->origin[0] = point[0]; + ps->origin[1] = point[1]; + ps->origin[2] = point[2]; + point[2] = (ps->origin[2] - 1.0f) - 0.25f; + + Game::PM_playerTrace(pm, trace, ps->origin, point, &pm->bounds, ps->clientNum, pm->tracemask); + + // If elevators are disabled we need to check that startsolid is false before proceeding + // like later versions of the game do + if (!trace->startsolid || elevatorSetting >= Elevators::ENABLED) + { + break; + } + } + + i += 1; + if (i >= 26) + { + ps->groundEntityNum = Game::ENTITYNUM_NONE; + pml->groundPlane = 0; + pml->almostGroundPlane = 0; + pml->walking = 0; + Game::Jump_ClearState(ps); + return 0; + } + } + + pml->groundTrace = *trace; + + const auto fraction = trace->fraction; + ps->origin[0] += fraction * (point[0] - ps->origin[0]); + ps->origin[1] += fraction * (point[1] - ps->origin[1]); + ps->origin[2] += fraction * (point[2] - ps->origin[2]); + + return 1; + } + + void Elevators::PM_Trace_Hk(Game::pmove_s* pm, Game::trace_t* trace, const float* f3, + const float* f4, const Game::Bounds* bounds, int a6, int a7) + { + Game::PM_Trace(pm, trace, f3, f4, bounds, a6, a7); + + // Allow the player to stand even when there is no headroom + if (Elevators::BG_Elevators.get() == Elevators::EASY) + { + trace->allsolid = false; + } + } + + __declspec(naked) void Elevators::PM_CorrectAllSolidStub() + { + __asm + { + push eax + pushad + + push [esp + 0x8 + 0x24] + push [esp + 0x8 + 0x24] + push eax + call Elevators::PM_CorrectAllSolid + add esp, 0xC + + mov [esp + 0x20], eax + popad + pop eax + + ret + } + } + + Elevators::Elevators() + { + Dvar::OnInit([] + { + static const char* values[] = + { + "off", + "normal", + "easy", + nullptr + }; + + Elevators::BG_Elevators = Game::Dvar_RegisterEnum("bg_elevators", values, + Elevators::ENABLED, Game::DVAR_CODINFO, "Elevators glitch settings"); + }); + + //Replace PM_CorrectAllSolid + Utils::Hook(0x57369E, Elevators::PM_CorrectAllSolidStub, HOOK_CALL).install()->quick(); + + // Place hooks in PM_CheckDuck. If the elevators dvar is set to easy the + // flags for duck/prone will always be removed from the player state + Utils::Hook(0x570EC5, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x570E0B, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x570D70, Elevators::PM_Trace_Hk, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/Elevators.hpp b/src/Components/Modules/Elevators.hpp new file mode 100644 index 00000000..1a096161 --- /dev/null +++ b/src/Components/Modules/Elevators.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace Components +{ + class Elevators : public Component + { + public: + Elevators(); + + private: + enum ElevatorSettings { DISABLED, ENABLED, EASY }; + static Dvar::Var BG_Elevators; + + static int PM_CorrectAllSolid(Game::pmove_s* move, Game::pml_t* pml, Game::trace_t* trace); + static void PM_CorrectAllSolidStub(); + static void PM_Trace_Hk(Game::pmove_s*, Game::trace_t*, const float*, const float*, const Game::Bounds*, int, int); + }; +} diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index d94fd884..3700587b 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -43,10 +43,10 @@ namespace Components Utils::Time::Interval interval; while (IsWindow(Window::GetWindow()) != FALSE && !interval.elapsed(2s)) { - if (PeekMessage(&msg, nullptr, NULL, NULL, PM_REMOVE)) + if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE)) { TranslateMessage(&msg); - DispatchMessage(&msg); + DispatchMessageA(&msg); } std::this_thread::sleep_for(10ms); @@ -57,6 +57,35 @@ namespace Components Game::Sys_SuspendOtherThreads(); } + void Exception::CopyMessageToClipboard(const std::string& error) + { + const auto hWndNewOwner = GetDesktopWindow(); + const auto result = OpenClipboard(hWndNewOwner); + + if (result == FALSE) + return; + + EmptyClipboard(); + auto* hMem = GlobalAlloc(GMEM_MOVEABLE, error.size() + 1); + + if (hMem == nullptr) + { + CloseClipboard(); + return; + } + + auto lock = GlobalLock(hMem); + if (lock != nullptr) + { + std::memcpy(lock, error.data(), error.size() + 1); + GlobalUnlock(hMem); + SetClipboardData(1, hMem); + } + + CloseClipboard(); + GlobalFree(hMem); + } + LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo) { // Pass on harmless errors @@ -69,30 +98,22 @@ namespace Components std::string errorStr; if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { - errorStr = "Termination because of a stack overflow."; + errorStr = "Termination because of a stack overflow.\nCopy exception address to clipboard?"; } else { - errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); + errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); } //Exception::SuspendProcess(); - bool doFullDump = Flags::HasFlag("bigdumps") || Flags::HasFlag("reallybigdumps"); - /*if (!doFullDump) + // Message should be copied to the keyboard if no button is pressed + if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES) { - if (MessageBoxA(nullptr, - Utils::String::VA("%s\n\n" // errorStr - "Would you like to create a full crash dump for the developers (this can be 100mb or more)?\nNo will create small dumps that are automatically uploaded.", errorStr), - "IW4x Error!", MB_YESNO | MB_ICONERROR) == IDYES) - { - doFullDump = true; - } - }*/ + Exception::CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress)); + } - MessageBoxA(nullptr, errorStr.data(), "ERROR", MB_ICONERROR); - - if (doFullDump) + if (Flags::HasFlag("bigminidumps")) { Exception::SetMiniDumpType(true, false); } @@ -184,8 +205,8 @@ namespace Components // Display DEBUG branding, so we know we're on a debug build Scheduler::OnFrame([]() { - Game::Font_s* font = Game::R_RegisterFont("fonts/normalFont", 0); - float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + auto* font = Game::R_RegisterFont("fonts/normalFont", 0); + Game::vec4_t color = { 1.0f, 1.0f, 1.0f, 1.0f }; // Change the color when attaching a debugger if (IsDebuggerPresent()) @@ -213,12 +234,12 @@ namespace Components Game::UI_UpdateArenas(); std::string command; - for (int i = 0; i < (params->length() >= 2 ? atoi(params->get(1)) : *Game::arenaCount); ++i) + for (auto i = 0; i < (params->size() >= 2 ? atoi(params->get(1)) : *Game::arenaCount); ++i) { - char* mapname = ArenaLength::NewArenas[i % *Game::arenaCount].mapName; + const auto* mapname = ArenaLength::NewArenas[i % *Game::arenaCount].mapName; - if (!(i % 2)) command.append(Utils::String::VA("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect - else command.append(Utils::String::VA("wait 500;", mapname)); // Test direct map switch + if (!(i % 2)) command.append("wait 250;disconnect;wait 750;"); // Test a disconnect + else command.append("wait 500;"); // Test direct map switch command.append(Utils::String::VA("map %s;", mapname)); } diff --git a/src/Components/Modules/Exception.hpp b/src/Components/Modules/Exception.hpp index 0b887af8..55a04c1f 100644 --- a/src/Components/Modules/Exception.hpp +++ b/src/Components/Modules/Exception.hpp @@ -18,7 +18,8 @@ namespace Components static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); static __declspec(noreturn) void ErrorLongJmp(jmp_buf _Buf, int _Value); static __declspec(noreturn) void LongJmp(jmp_buf _Buf, int _Value); - static void DebugMinidumpCommand(Command::Params*); + + static void CopyMessageToClipboard(const std::string& error); static int MiniDumpType; static Utils::Hook SetFilterHook; diff --git a/src/Components/Modules/FastFiles.cpp b/src/Components/Modules/FastFiles.cpp index fb7c8939..963b1777 100644 --- a/src/Components/Modules/FastFiles.cpp +++ b/src/Components/Modules/FastFiles.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -495,7 +495,7 @@ namespace Components FastFiles::FastFiles() { - Dvar::Register("ui_zoneDebug", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Display current loaded zone."); + Dvar::Register("ui_zoneDebug", false, Game::dvar_flag::DVAR_ARCHIVE, "Display current loaded zone."); // Fix XSurface assets Utils::Hook(0x0048E8A5, FastFiles::Load_XSurfaceArray, HOOK_CALL).install()->quick(); @@ -594,7 +594,7 @@ namespace Components Command::Add("loadzone", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; Game::XZoneInfo info; info.name = params->get(1); @@ -622,9 +622,4 @@ namespace Components }, HOOK_CALL).install()/*->quick()*/; #endif } - - FastFiles::~FastFiles() - { - FastFiles::ZonePaths.clear(); - } } diff --git a/src/Components/Modules/FastFiles.hpp b/src/Components/Modules/FastFiles.hpp index 38c3e62c..a1b4c235 100644 --- a/src/Components/Modules/FastFiles.hpp +++ b/src/Components/Modules/FastFiles.hpp @@ -6,7 +6,6 @@ namespace Components { public: FastFiles(); - ~FastFiles(); static void AddZonePath(const std::string& path); static std::string Current(); diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index 68de4044..fface938 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Flags.cpp b/src/Components/Modules/Flags.cpp index d40e5802..87400340 100644 --- a/src/Components/Modules/Flags.cpp +++ b/src/Components/Modules/Flags.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -8,7 +8,7 @@ namespace Components { Flags::ParseFlags(); - for (auto entry : Flags::EnabledFlags) + for (const auto& entry : Flags::EnabledFlags) { if (Utils::String::ToLower(entry) == Utils::String::ToLower(flag)) { @@ -21,21 +21,26 @@ namespace Components void Flags::ParseFlags() { - static bool flagsParsed = false; - if (flagsParsed) return; - flagsParsed = true; + static auto flagsParsed = false; + if (flagsParsed) + { + return; + } + // Only parse flags once + flagsParsed = true; int numArgs; - LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &numArgs); + auto* const argv = CommandLineToArgvW(GetCommandLineW(), &numArgs); if (argv) { - for (int i = 0; i < numArgs; ++i) + for (auto i = 0; i < numArgs; ++i) { std::wstring wFlag(argv[i]); if (wFlag[0] == L'-') { - Flags::EnabledFlags.push_back(std::string(++wFlag.begin(), wFlag.end())); + wFlag.erase(wFlag.begin()); + Flags::EnabledFlags.emplace_back(Utils::String::Convert(wFlag)); } } @@ -45,17 +50,7 @@ namespace Components // Workaround for wine if (Utils::IsWineEnvironment() && Dedicated::IsEnabled() && !Flags::HasFlag("console") && !Flags::HasFlag("stdout")) { - Flags::EnabledFlags.push_back("stdout"); + Flags::EnabledFlags.emplace_back("stdout"); } } - - Flags::Flags() - { - Flags::ParseFlags(); - } - - Flags::~Flags() - { - Flags::EnabledFlags.clear(); - } } diff --git a/src/Components/Modules/Flags.hpp b/src/Components/Modules/Flags.hpp index 823a5ee9..b8ee410d 100644 --- a/src/Components/Modules/Flags.hpp +++ b/src/Components/Modules/Flags.hpp @@ -5,8 +5,7 @@ namespace Components class Flags : public Component { public: - Flags(); - ~Flags(); + Flags() = default; static bool HasFlag(const std::string& flag); diff --git a/src/Components/Modules/FrameTime.cpp b/src/Components/Modules/FrameTime.cpp index 69bb3d98..661ea80b 100644 --- a/src/Components/Modules/FrameTime.cpp +++ b/src/Components/Modules/FrameTime.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index 2350877b..b77a983a 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -71,7 +71,7 @@ namespace Components entry->name = Steam::Proxy::SteamFriends->GetFriendPersonaName(user); entry->online = Steam::Proxy::SteamFriends->GetFriendPersonaState(user) != 0; - entry->cleanName = Utils::String::ToLower(Colors::Strip(entry->name)); + entry->cleanName = Utils::String::ToLower(TextRenderer::StripColors(entry->name)); std::string guid = Friends::GetPresence(user, "iw4x_guid"); std::string name = Friends::GetPresence(user, "iw4x_name"); @@ -111,8 +111,8 @@ namespace Components Friends::SortList(); - int notify = Dvar::Var("cl_notifyFriendState").get(); - if (gotOnline && (notify == -1 || (notify == 1 && !Game::CL_IsCgameInitialized())) && !Dvar::Var("ui_streamFriendly").get()) + const auto notify = Dvar::Var("cl_notifyFriendState").get(); + if (gotOnline && (!notify || (notify && !Game::CL_IsCgameInitialized())) && !Dvar::Var("ui_streamFriendly").get()) { Game::Material* material = Friends::CreateAvatar(user); Toast::Show(material, entry->name, "is playing IW4x", 3000, [material]() @@ -186,7 +186,7 @@ namespace Components { std::lock_guard _(Friends::Mutex); - const unsigned int modId = *reinterpret_cast(const_cast("IW4x")) | 0x80000000; + const unsigned int modId = *reinterpret_cast("IW4x") | 0x80000000; // Split up the list for (auto entry : Friends::FriendsList) @@ -458,7 +458,10 @@ namespace Components __asm { pushad + call Friends::ClearServer + call Dvar::ResetDvarsValue + popad push 467CC0h @@ -575,14 +578,19 @@ namespace Components if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) return; - Dvar::Register("cl_anonymous", false, Game::DVAR_FLAG_SAVED, ""); - Dvar::Register("cl_notifyFriendState", 1, -1, 1, Game::DVAR_FLAG_SAVED, ""); + Dvar::Register("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam"); + Dvar::Register("cl_notifyFriendState", true, Game::DVAR_ARCHIVE, "Update friends about current game status"); Command::Add("addFriend", [](Command::Params* params) { - if (params->length() <= 1) return; + if (params->size() < 2u) + { + Logger::Print("Usage: %s \n", params->get(0)); + return; + } + SteamID id; - id.bits = atoll(params->get(1)); + id.bits = std::strtoull(params->get(1), nullptr, 16); Friends::AddFriend(id); }); diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp new file mode 100644 index 00000000..f6fc1aef --- /dev/null +++ b/src/Components/Modules/Gamepad.cpp @@ -0,0 +1,1965 @@ +#include + +namespace Components +{ + Game::ButtonToCodeMap_t Gamepad::buttonList[] + { + {Game::GPAD_X, Game::K_BUTTON_X}, + {Game::GPAD_A, Game::K_BUTTON_A}, + {Game::GPAD_B, Game::K_BUTTON_B}, + {Game::GPAD_Y, Game::K_BUTTON_Y}, + {Game::GPAD_L_TRIG, Game::K_BUTTON_LTRIG}, + {Game::GPAD_R_TRIG, Game::K_BUTTON_RTRIG}, + {Game::GPAD_L_SHLDR, Game::K_BUTTON_LSHLDR}, + {Game::GPAD_R_SHLDR, Game::K_BUTTON_RSHLDR}, + {Game::GPAD_START, Game::K_BUTTON_START}, + {Game::GPAD_BACK, Game::K_BUTTON_BACK}, + {Game::GPAD_L3, Game::K_BUTTON_LSTICK}, + {Game::GPAD_R3, Game::K_BUTTON_RSTICK}, + {Game::GPAD_UP, Game::K_DPAD_UP}, + {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, + {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, + {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} + }; + + Game::StickToCodeMap_t Gamepad::analogStickList[4] + { + {Game::GPAD_LX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_LY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + {Game::GPAD_RX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_RY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + }; + + Game::GamePadStick Gamepad::stickForAxis[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_RX, + Game::GPAD_RY, + Game::GPAD_LX, + Game::GPAD_LY, + Game::GPAD_INVALID, + Game::GPAD_INVALID + }; + + Game::GamepadPhysicalAxis Gamepad::axisSameStick[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_PHYSAXIS_RSTICK_Y, + Game::GPAD_PHYSAXIS_RSTICK_X, + Game::GPAD_PHYSAXIS_LSTICK_Y, + Game::GPAD_PHYSAXIS_LSTICK_X, + Game::GPAD_PHYSAXIS_NONE, + Game::GPAD_PHYSAXIS_NONE + }; + + const char* Gamepad::physicalAxisNames[Game::GPAD_PHYSAXIS_COUNT] + { + "A_RSTICK_X", + "A_RSTICK_Y", + "A_LSTICK_X", + "A_LSTICK_Y", + "A_RTRIGGER", + "A_LTRIGGER" + }; + + const char* Gamepad::virtualAxisNames[Game::GPAD_VIRTAXIS_COUNT] + { + "VA_SIDE", + "VA_FORWARD", + "VA_UP", + "VA_YAW", + "VA_PITCH", + "VA_ATTACK" + }; + + const char* Gamepad::gamePadMappingTypeNames[Game::GPAD_MAP_COUNT] + { + "MAP_LINEAR", + "MAP_SQUARED" + }; + + Game::keyNum_t Gamepad::menuScrollButtonList[] + { + Game::K_APAD_UP, + Game::K_APAD_DOWN, + Game::K_APAD_LEFT, + Game::K_APAD_RIGHT, + Game::K_DPAD_UP, + Game::K_DPAD_DOWN, + Game::K_DPAD_LEFT, + Game::K_DPAD_RIGHT + }; + + Game::keyname_t Gamepad::extendedKeyNames[] + { + {"BUTTON_A", Game::K_BUTTON_A}, + {"BUTTON_B", Game::K_BUTTON_B}, + {"BUTTON_X", Game::K_BUTTON_X}, + {"BUTTON_Y", Game::K_BUTTON_Y}, + {"BUTTON_LSHLDR", Game::K_BUTTON_LSHLDR}, + {"BUTTON_RSHLDR", Game::K_BUTTON_RSHLDR}, + {"BUTTON_START", Game::K_BUTTON_START}, + {"BUTTON_BACK", Game::K_BUTTON_BACK}, + {"BUTTON_LSTICK", Game::K_BUTTON_LSTICK}, + {"BUTTON_RSTICK", Game::K_BUTTON_RSTICK}, + {"BUTTON_LTRIG", Game::K_BUTTON_LTRIG}, + {"BUTTON_RTRIG", Game::K_BUTTON_RTRIG}, + {"DPAD_UP", Game::K_DPAD_UP}, + {"DPAD_DOWN", Game::K_DPAD_DOWN}, + {"DPAD_LEFT", Game::K_DPAD_LEFT}, + {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, + }; + + 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}, + {"^\x01\x32\x32\x08""button_b", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x08""button_x", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x08""button_y", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_lshldr", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_rshldr", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x0C""button_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0B""button_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_lstick", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_rstick", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0C""button_ltrig", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0C""button_rtrig", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x07""dpad_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x09""dpad_down", Game::K_DPAD_DOWN}, + {"^\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::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[] + { + {Game::K_BUTTON_A, Game::K_ENTER}, + {Game::K_BUTTON_START, Game::K_ENTER}, + {Game::K_BUTTON_B, Game::K_ESCAPE}, + {Game::K_BUTTON_BACK, Game::K_ESCAPE}, + {Game::K_DPAD_UP, Game::K_UPARROW}, + {Game::K_APAD_UP, Game::K_UPARROW}, + {Game::K_DPAD_DOWN, Game::K_DOWNARROW}, + {Game::K_APAD_DOWN, Game::K_DOWNARROW}, + {Game::K_DPAD_LEFT, Game::K_LEFTARROW}, + {Game::K_APAD_LEFT, Game::K_LEFTARROW}, + {Game::K_DPAD_RIGHT, Game::K_RIGHTARROW}, + {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, + }; + + Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; + Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; + int Gamepad::gamePadBindingsModifiedFlags = 0; + + Dvar::Var Gamepad::gpad_enabled; + 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; + Dvar::Var Gamepad::gpad_menu_scroll_delay_rest; + Dvar::Var Gamepad::gpad_rumble; + Dvar::Var Gamepad::gpad_stick_pressed_hysteresis; + Dvar::Var Gamepad::gpad_stick_pressed; + Dvar::Var Gamepad::gpad_stick_deadzone_max; + Dvar::Var Gamepad::gpad_stick_deadzone_min; + Dvar::Var Gamepad::gpad_button_deadzone; + Dvar::Var Gamepad::gpad_button_rstick_deflect_max; + Dvar::Var Gamepad::gpad_button_lstick_deflect_max; + Dvar::Var Gamepad::gpad_use_hold_time; + Dvar::Var Gamepad::gpad_lockon_enabled; + Dvar::Var Gamepad::gpad_slowdown_enabled; + Dvar::Var Gamepad::input_viewSensitivity; + Dvar::Var Gamepad::input_invertPitch; + Dvar::Var Gamepad::sv_allowAimAssist; + Dvar::Var Gamepad::aim_turnrate_pitch; + Dvar::Var Gamepad::aim_turnrate_pitch_ads; + Dvar::Var Gamepad::aim_turnrate_yaw; + Dvar::Var Gamepad::aim_turnrate_yaw_ads; + Dvar::Var Gamepad::aim_accel_turnrate_enabled; + Dvar::Var Gamepad::aim_accel_turnrate_lerp; + Dvar::Var Gamepad::aim_input_graph_enabled; + Dvar::Var Gamepad::aim_input_graph_index; + Dvar::Var Gamepad::aim_scale_view_axis; + Dvar::Var Gamepad::cl_bypassMouseInput; + Dvar::Var Gamepad::cg_mapLocationSelectionCursorSpeed; + Dvar::Var Gamepad::aim_aimAssistRangeScale; + Dvar::Var Gamepad::aim_slowdown_enabled; + Dvar::Var Gamepad::aim_slowdown_debug; + Dvar::Var Gamepad::aim_slowdown_pitch_scale; + Dvar::Var Gamepad::aim_slowdown_pitch_scale_ads; + Dvar::Var Gamepad::aim_slowdown_yaw_scale; + Dvar::Var Gamepad::aim_slowdown_yaw_scale_ads; + Dvar::Var Gamepad::aim_lockon_enabled; + Dvar::Var Gamepad::aim_lockon_deflection; + Dvar::Var Gamepad::aim_lockon_pitch_strength; + Dvar::Var Gamepad::aim_lockon_strength; + + Gamepad::GamePadGlobals::GamePadGlobals() + : axes{}, + nextScrollTime(0) + { + for (auto& virtualAxis : axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + __declspec(naked) void Gamepad::MSG_WriteDeltaUsercmdKeyStub() + { + __asm + { + // fix stack pointer + add esp, 0Ch + + // put both forward move and rightmove values in the movement button + mov dl, byte ptr[edi + 1Ah] // to_forwardMove + mov dh, byte ptr[edi + 1Bh] // to_rightMove + + mov[esp + 30h], dx // to_buttons + + mov dl, byte ptr[ebp + 1Ah] // from_forwardMove + mov dh, byte ptr[ebp + 1Bh] // from_rightMove + + mov[esp + 2Ch], dx // from_buttons + + // return back + push 0x60E40E + retn + } + } + + void Gamepad::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to) + { + char forward; + char right; + + if (Game::MSG_ReadBit(msg)) + { + short movementBits = static_cast(key ^ Game::MSG_ReadBits(msg, 16)); + + forward = static_cast(movementBits); + right = static_cast(movementBits >> 8); + } + else + { + forward = from->forwardmove; + right = from->rightmove; + } + + to->forwardmove = forward; + to->rightmove = right; + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 0x4921BF + ret + } + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub2() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 3 + push esi + push 0x492085 + ret + } + } + + bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) + { + gamePad.enabled = true; + gamePad.portIndex = portIndex; + return true; + } + + gamePad.enabled = false; + return false; + } + + void Gamepad::GPad_RefreshAll() + { + auto currentGamePadNum = 0; + + for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++) + { + if (GPad_Check(currentGamePadNum, currentPort)) + currentGamePadNum++; + } + } + + float Gamepad::LinearTrack(const float target, const float current, const float rate, const float deltaTime) + { + const auto err = target - current; + float step; + if (err <= 0.0f) + step = -rate * deltaTime; + else + step = rate * deltaTime; + + if (std::fabs(err) <= 0.001f) + return target; + + if (std::fabs(step) <= std::fabs(err)) + return current + step; + + return target; + } + + bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) + { + return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth + && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; + } + + bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) + { + // Check offhand flag + if ((ps->weapFlags & 2) == 0) + return false; + + // If offhand weapon has no id we are not using one + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + + return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight) + { + const auto rangeSqr = range * range; + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->distSqr <= rangeSqr && AimAssist_DoBoundsIntersectCenterBox(currentTarget->clipMins, currentTarget->clipMaxs, regionWidth, regionHeight)) + { + return currentTarget; + } + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) + { + if (entIndex == Game::AIM_TARGET_INVALID) + return nullptr; + + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->entIndex == entIndex) + return currentTarget; + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, + const int prevTargetEnt) + { + const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); + + if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) + return screenTarget; + + return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); + } + + bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[gamePadIndex]; + + if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) + return false; + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + return false; + + if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + return false; + + return true; + } + + void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + const auto prevTargetEnt = aaGlob.lockOnTargetEnt; + aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; + + if (!AimAssist_IsLockonActive(input->localClientNum)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + if (weaponDef->requireLockonToFire) + return; + + const auto deflection = aim_lockon_deflection.get(); + if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) + return; + + if (!aaGlob.ps.weapIndex) + return; + + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); + + if (screenTarget && screenTarget->distSqr > 0.0f) + { + aaGlob.lockOnTargetEnt = screenTarget->entIndex; + const auto arcLength = std::sqrt(screenTarget->distSqr) * static_cast(M_PI); + + const auto pitchTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[2][0] + screenTarget->velocity[1] * aaGlob.viewAxis[2][1] + screenTarget->velocity[2] * aaGlob.viewAxis[2][2] + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[2][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[2][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[2][2])) + / arcLength * 180.0f * aim_lockon_pitch_strength.get(); + + const auto yawTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[1][0] + screenTarget->velocity[1] * aaGlob.viewAxis[1][1] + screenTarget->velocity[2] * aaGlob.viewAxis[1][2] + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) + / arcLength * 180.0f * aim_lockon_strength.get(); + + output->pitch -= pitchTurnRate * input->deltaTime; + output->yaw += yawTurnRate * input->deltaTime; + } + } + + void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) + { + assert(input); + assert(pitchAxis); + assert(yawAxis); + + const auto graphIndex = aim_input_graph_index.get(); + if (aim_input_graph_enabled.get() && graphIndex >= 0 && static_cast(graphIndex) < Game::AIM_ASSIST_GRAPH_COUNT) + { + const auto deflection = std::sqrt(input->pitchAxis * input->pitchAxis + input->yawAxis * input->yawAxis); + + float fraction; + if (deflection - 1.0f < 0.0f) + fraction = deflection; + else + fraction = 1.0f; + + if (0.0f - deflection >= 0.0f) + fraction = 0.0f; + + const auto graphScale = Game::GraphFloat_GetValue(&Game::aaInputGraph[graphIndex], fraction); + *pitchAxis = input->pitchAxis * graphScale; + *yawAxis = input->yawAxis * graphScale; + } + else + { + *pitchAxis = input->pitchAxis; + *yawAxis = input->yawAxis; + } + + if (aim_scale_view_axis.get()) + { + const auto absPitchAxis = std::fabs(*pitchAxis); + const auto absYawAxis = std::fabs(*yawAxis); + + if (absPitchAxis <= absYawAxis) + *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; + else + *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; + } + } + + bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) + { + if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) + return false; + + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + if (weaponDef->requireLockonToFire) + return false; + + if (ps->linkFlags & Game::PLF_WEAPONVIEW_ONLY) + return false; + + if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) + return false; + + if (ps->eFlags & (Game::EF_VEHICLE_ACTIVE | Game::EF_TURRET_ACTIVE_DUCK | Game::EF_TURRET_ACTIVE_PRONE)) + return false; + + if (!ps->hasAmmo) + return false; + + return true; + } + + void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + assert(pitchScale); + assert(yawScale); + + *pitchScale = 1.0f; + *yawScale = 1.0f; + + if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.slowdownRegionWidth, aaGlob.tweakables.slowdownRegionHeight); + + if (screenTarget) + { + *pitchScale = AimAssist_Lerp(aim_slowdown_pitch_scale.get(), aim_slowdown_pitch_scale_ads.get(), aaGlob.adsLerp); + *yawScale = AimAssist_Lerp(aim_slowdown_yaw_scale.get(), aim_slowdown_yaw_scale_ads.get(), aaGlob.adsLerp); + } + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + *pitchScale = 1.0f; + } + + float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) + { + return (to - from) * fraction + from; + } + + void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + auto slowdownPitchScale = 0.0f; + auto slowdownYawScale = 0.0f; + float adjustedPitchAxis; + float adjustedYawAxis; + + if (aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + { + adjustedPitchAxis = 0.0f; + adjustedYawAxis = 0.0f; + slowdownPitchScale = 1.0f; + slowdownYawScale = 1.0f; + } + else + { + AimAssist_CalcAdjustedAxis(input, &adjustedPitchAxis, &adjustedYawAxis); + AimAssist_CalcSlowdown(input, &slowdownPitchScale, &slowdownYawScale); + } + + const auto sensitivity = input_viewSensitivity.get(); + auto pitchTurnRate = AimAssist_Lerp(aim_turnrate_pitch.get(), aim_turnrate_pitch_ads.get(), aaGlob.adsLerp); + pitchTurnRate = slowdownPitchScale * aaGlob.fovTurnRateScale * sensitivity * pitchTurnRate; + auto yawTurnRate = AimAssist_Lerp(aim_turnrate_yaw.get(), aim_turnrate_yaw_ads.get(), aaGlob.adsLerp); + yawTurnRate = slowdownYawScale * aaGlob.fovTurnRateScale * sensitivity * yawTurnRate; + + if (input->pitchMax > 0 && input->pitchMax < pitchTurnRate) + pitchTurnRate = input->pitchMax; + if (input->yawMax > 0 && input->yawMax < yawTurnRate) + yawTurnRate = input->yawMax; + + const auto pitchSign = adjustedPitchAxis >= 0.0f ? 1.0f : -1.0f; + const auto yawSign = adjustedYawAxis >= 0.0f ? 1.0f : -1.0f; + + const auto pitchDelta = std::fabs(adjustedPitchAxis) * pitchTurnRate; + const auto yawDelta = std::fabs(adjustedYawAxis) * yawTurnRate; + + if (!aim_accel_turnrate_enabled.get()) + { + aaGlob.pitchDelta = pitchDelta; + aaGlob.yawDelta = yawDelta; + } + else + { + const auto accel = aim_accel_turnrate_lerp.get() * sensitivity; + if (pitchDelta <= aaGlob.pitchDelta) + aaGlob.pitchDelta = pitchDelta; + else + aaGlob.pitchDelta = LinearTrack(pitchDelta, aaGlob.pitchDelta, accel, input->deltaTime); + + if (yawDelta <= aaGlob.yawDelta) + aaGlob.yawDelta = yawDelta; + else + aaGlob.yawDelta = LinearTrack(yawDelta, aaGlob.yawDelta, accel, input->deltaTime); + } + + output->pitch += aaGlob.pitchDelta * input->deltaTime * pitchSign; + output->yaw += aaGlob.yawDelta * input->deltaTime * yawSign; + } + + void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + output->pitch = input->pitch; + output->yaw = input->yaw; + + if (aaGlob.initialized) + { + Game::AimAssist_UpdateTweakables(input->localClientNum); + Game::AimAssist_UpdateAdsLerp(input); + AimAssist_ApplyTurnRates(input, output); + + Game::AimAssist_ApplyAutoMelee(input, output); + AimAssist_ApplyLockOn(input, output); + } + + aaGlob.prevButtons = input->buttons; + } + + void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) + { + // Buttons are already handled by keyboard input handler + + const auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + const auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + const auto yaw = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + const auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto sensitivity = input_viewSensitivity.get(); + + constexpr auto scale = static_cast(std::numeric_limits::max()); + cmd->remoteControlAngles[0] = ClampChar(cmd->remoteControlAngles[0] + static_cast(std::floor(-up * scale * sensitivity)) + + static_cast(std::floor(-pitch * scale * sensitivity))); + cmd->remoteControlAngles[1] = ClampChar(cmd->remoteControlAngles[1] + static_cast(std::floor(-right * scale * sensitivity)) + + static_cast(std::floor(-yaw * scale * sensitivity))); + } + + constexpr auto CL_RemoteControlMove = 0x5A6BA0; + __declspec(naked) void Gamepad::CL_RemoteControlMove_Stub() + { + __asm + { + // Prepare args for our function call + push edi // usercmd + push eax // localClientNum + + call CL_RemoteControlMove + + // Call our function, the args were already prepared earlier + call CL_RemoteControlMove_GamePad + add esp, 0x8 + + ret + } + } + + bool Gamepad::CG_HandleLocationSelectionInput_GamePad(const int localClientNum, Game::usercmd_s* /*cmd*/) + { + // Buttons are already handled by keyboard input handler + + const auto frameTime = static_cast(Game::cgArray[0].frametime) * 0.001f; + const auto mapAspectRatio = Game::cgArray[0].compassMapWorldSize[0] / Game::cgArray[0].compassMapWorldSize[1]; + const auto selectionRequiresAngle = (Game::cgArray[0].predictedPlayerState.locationSelectionInfo & 0x80) != 0; + + auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + auto magnitude = up * up + right * right; + + if (magnitude > 1.0f) + { + magnitude = std::sqrt(magnitude); + up /= magnitude; + right /= magnitude; + } + + Game::cgArray[0].selectedLocation[0] += right * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + Game::cgArray[0].selectedLocation[1] -= up * mapAspectRatio * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + + if (selectionRequiresAngle) + { + const auto yawUp = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto yawRight = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + + if (std::fabs(yawUp) > 0.0f || std::fabs(yawRight) > 0.0f) + { + Game::vec2_t vec + { + yawUp, + -yawRight + }; + + Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + } + else + { + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + + return true; + } + + constexpr auto CG_HandleLocationSelectionInput = 0x5A67A0; + __declspec(naked) void Gamepad::CG_HandleLocationSelectionInput_Stub() + { + __asm + { + // Prepare args for our function call + push esi // usercmd + push eax // localClientNum + + call CG_HandleLocationSelectionInput + + test al,al + jz exit_handling + + // Call our function, the args were already prepared earlier + call CG_HandleLocationSelectionInput_GamePad + + exit_handling: + add esp, 0x8 + ret + } + } + + bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) + { + return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; + } + + float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); + const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; + + if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) + return 0.0f; + + auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; + + if (mapType == Game::GPAD_MAP_SQUARED) + { + const auto otherAxisSameStick = axisSameStick[physicalAxis]; + + float otherAxisDeflection; + if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) + otherAxisDeflection = 0.0f; + else + otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; + + axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; + } + + return axisDeflection; + } + + char Gamepad::ClampChar(const int value) + { + return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); + } + + void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + auto& clientActive = Game::clients[gamePadIndex]; + + if (!gpad_enabled.get() || !gamePad.enabled) + return; + + auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); + if (!input_invertPitch.get()) + pitch *= -1; + + auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); + auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); + auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); + + // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. + //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); + + auto moveScale = static_cast(std::numeric_limits::max()); + + if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) + { + const auto length = std::fabs(side) <= std::fabs(forward) + ? side / forward + : forward / side; + moveScale = std::sqrt((length * length) + 1.0f) * moveScale; + } + + const auto forwardMove = static_cast(std::floor(forward * moveScale)); + const auto rightMove = static_cast(std::floor(side * moveScale)); + + cmd->rightmove = ClampChar(cmd->rightmove + rightMove); + cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); + + // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" + if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) + { + auto oldButtons = cmd->buttons; + if (oldButtons & Game::CMD_BUTTON_ATTACK) + cmd->buttons |= Game::CMD_BUTTON_THROW; + else + cmd->buttons &= ~Game::CMD_BUTTON_THROW; + + if (oldButtons & Game::CMD_BUTTON_THROW) + cmd->buttons |= Game::CMD_BUTTON_ATTACK; + else + cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; + } + + // Check for frozen controls. Flag name should start with PMF_ + if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) + { + Game::AimInput aimInput{}; + Game::AimOutput aimOutput{}; + aimInput.deltaTime = frameTimeBase; + aimInput.buttons = cmd->buttons; + aimInput.localClientNum = gamePadIndex; + aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; + aimInput.pitch = clientActive.clViewangles[0]; + aimInput.pitchAxis = pitch; + aimInput.pitchMax = clientActive.cgameMaxPitchSpeed; + aimInput.yaw = clientActive.clViewangles[1]; + aimInput.yawAxis = yaw; + aimInput.yawMax = clientActive.cgameMaxYawSpeed; + aimInput.forwardAxis = forward; + aimInput.rightAxis = side; + AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); + clientActive.clViewangles[0] = aimOutput.pitch; + clientActive.clViewangles[1] = aimOutput.yaw; + cmd->meleeChargeDist = aimOutput.meleeChargeDist; + cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; + } + } + + constexpr auto CL_MouseMove = 0x5A6240; + __declspec(naked) void Gamepad::CL_MouseMove_Stub() + { + __asm + { + // Prepare args for our function call + push [esp+0x4] // frametime_base + push ebx // cmd + push eax // localClientNum + + push [esp+0x8] // restore frametime_base on the stack + call CL_MouseMove + add esp,4 + + // Call our function, the args were already prepared earlier + call CL_GamepadMove + add esp,0xC + + ret + } + } + + bool Gamepad::Gamepad_ShouldUse(const Game::gentity_s* playerEnt, const unsigned useTime) + { + // Only apply hold time to +usereload keybind + return !(playerEnt->client->buttons & Game::CMD_BUTTON_USE_RELOAD) || useTime >= static_cast(gpad_use_hold_time.get()); + } + + __declspec(naked) void Gamepad::Player_UseEntity_Stub() + { + __asm + { + // Execute overwritten instructions + cmp eax, [ecx + 0x10] + jl skipUse + + // Call our custom check + push eax + pushad + push eax + push edi + call Gamepad_ShouldUse + add esp, 8h + mov [esp + 0x20], eax + popad + pop eax + + // Skip use if custom check returns false + test al, al + jz skipUse + + // perform use + push 0x5FE39B + ret + + skipUse: + push 0x5FE3AF + ret + } + } + + bool Gamepad::Key_IsValidGamePadChar(const int key) + { + return key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_1 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_1 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_2 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_2 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; + } + + void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (!down) + return; + + const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + for (const auto scrollButton : menuScrollButtonList) + { + if (key == scrollButton) + { + gamePadGlobal.nextScrollTime = scrollDelayFirst + time; + return; + } + } + } + + void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + + const auto stick = stickForAxis[physicalAxis]; + const auto stickIndex = stick & Game::GPAD_VALUE_MASK; + if (stick != Game::GPAD_INVALID) + { + assert(stickIndex < 4); + const auto& mapping = analogStickList[stickIndex]; + + if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); + } + else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); + } + } + } + + void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + gamePadGlobal.axes.axesValues[physicalAxis] = value; + CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); + + if (std::fabs(value) > 0.0f) + { + gamePad.inUse = true; + gpad_in_use.setRaw(true); + } + } + + void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) + { + for (const auto& mapping : controllerMenuKeyMappings) + { + if (mapping.controllerKey == key) + { + Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); + return; + } + } + + // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway + // Game::UI_KeyEvent(gamePadIndex, key, down); + } + + bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& keyState = Game::playerKeys[gamePadIndex]; + + if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) + { + Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); + return true; + } + + switch (key) + { + case Game::K_DPAD_UP: + Game::CG_ScrollScoreboardUp(Game::cgArray); + return true; + + case Game::K_DPAD_DOWN: + Game::CG_ScrollScoreboardDown(Game::cgArray); + return true; + + default: + return false; + } + } + + bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); + + for (const auto menuScrollButton : menuScrollButtonList) + { + if (key == menuScrollButton) + { + if (repeatCount == 1) + { + gamePadGlobal.nextScrollTime = time + scrollDelayFirst; + return false; + } + + if (time > gamePadGlobal.nextScrollTime) + { + gamePadGlobal.nextScrollTime = time + scrollDelayRest; + return false; + } + break; + } + } + } + + return repeatCount > 1; + } + + void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; + const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; + + auto& keyState = Game::playerKeys[gamePadIndex]; + keyState.keys[key].down = pressedOrUpdated; + + if (pressedOrUpdated) + { + if (++keyState.keys[key].repeats == 1) + keyState.anyKeyDown++; + } + else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) + { + keyState.keys[key].repeats = 0; + if (--keyState.anyKeyDown < 0) + keyState.anyKeyDown = 0; + } + + if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) + return; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) + { + if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; + } + else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+attack") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; + } + return; + } + + const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); + if(activeMenu == Game::UIMENU_SCOREBOARD) + { + if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) + return; + } + + keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; + + const auto* keyBinding = keyState.keys[key].binding; + + char cmd[1024]; + if (pressedOrUpdated) + { + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + return; + } + + if (keyBinding) + { + if (keyBinding[0] == '+') + { + sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + else + { + Game::Cbuf_InsertText(gamePadIndex, keyBinding); + } + } + } + else + { + if (keyBinding && keyBinding[0] == '+') + { + sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + } + } + } + + void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + gamePad.inUse = true; + gpad_in_use.setRaw(true); + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); + + + CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); + } + + void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) + { + if(x == 0 && y == 0) + { + outX = 0.0f; + outY = 0.0f; + return; + } + + Game::vec2_t stickVec; + stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); + stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); + + const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); + auto len = Game::Vec2Normalize(stickVec); + + if (gpad_stick_deadzone_min.get() <= len) + { + if (1.0f - gpad_stick_deadzone_max.get() >= len) + len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); + else + len = 1.0f; + } + else + len = 0.0f; + + outX = stickVec[0] * len; + outY = stickVec[1] * len; + } + + float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + return gamePad.sticks[stick]; + } + + float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + float value = 0.0f; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + value = buttonValue & gamePad.digitals ? 1.0f : 0.0f; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + if (analogIndex < std::extent_v) + { + value = gamePad.analogs[analogIndex]; + } + } + + return value; + } + + bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + down = (buttonValue & gamePad.digitals) != 0; + lastDown = (buttonValue & gamePad.lastDigitals) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return down && !lastDown; + } + + bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) + { + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; + } + + bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + + down = (gamePad.digitals & buttonValue) != 0; + lastDown = (gamePad.lastDigitals & buttonValue) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return !down && lastDown; + } + + void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) + { + for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) + { + gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; + + auto threshold = gpad_stick_pressed.get(); + + if (gamePad.stickDownLast[stickIndex][dir]) + threshold -= gpad_stick_pressed_hysteresis.get(); + else + threshold += gpad_stick_pressed_hysteresis.get(); + + if (dir == Game::GPAD_STICK_POS) + { + gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] > threshold; + } + else + { + assert(dir == Game::GPAD_STICK_NEG); + gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] < -threshold; + } + } + } + } + + void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + Game::vec2_t lVec, rVec; + GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); + GPad_ConvertStickToFloat(state.sThumbRX, state.sThumbRY, rVec[0], rVec[1]); + + gamePad.lastSticks[0] = gamePad.sticks[0]; + gamePad.sticks[0] = lVec[0]; + gamePad.lastSticks[1] = gamePad.sticks[1]; + gamePad.sticks[1] = lVec[1]; + gamePad.lastSticks[2] = gamePad.sticks[2]; + gamePad.sticks[2] = rVec[0]; + gamePad.lastSticks[3] = gamePad.sticks[3]; + gamePad.sticks[3] = rVec[1]; + + GPad_UpdateSticksDown(gamePadIndex); + +#ifdef DEBUG + if (gpad_debug.get()) + { + Logger::Print("Left: X: %f Y: %f\n", lVec[0], lVec[1]); + Logger::Print("Right: X: %f Y: %f\n", rVec[0], rVec[1]); + Logger::Print("Down: %i:%i %i:%i %i:%i %i:%i\n", gamePad.stickDown[0][Game::GPAD_STICK_POS], gamePad.stickDown[0][Game::GPAD_STICK_NEG], + gamePad.stickDown[1][Game::GPAD_STICK_POS], gamePad.stickDown[1][Game::GPAD_STICK_NEG], + gamePad.stickDown[2][Game::GPAD_STICK_POS], gamePad.stickDown[2][Game::GPAD_STICK_NEG], + gamePad.stickDown[3][Game::GPAD_STICK_POS], gamePad.stickDown[3][Game::GPAD_STICK_NEG]); + } +#endif + } + + void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + gamePad.lastDigitals = gamePad.digitals; + gamePad.digitals = state.wButtons; + + const auto leftDeflect = gpad_button_lstick_deflect_max.get(); + if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) + gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); + const auto rightDeflect = gpad_button_rstick_deflect_max.get(); + if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) + gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); + +#ifdef DEBUG + if (gpad_debug.get()) + { + Logger::Print("Buttons: %x\n", gamePad.digitals); + } +#endif + } + + void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + const auto buttonDeadZone = gpad_button_deadzone.get(); + + gamePad.lastAnalogs[0] = gamePad.analogs[0]; + gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); + if (gamePad.analogs[0] < buttonDeadZone) + gamePad.analogs[0] = 0.0f; + + + gamePad.lastAnalogs[1] = gamePad.analogs[1]; + gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); + if (gamePad.analogs[1] < buttonDeadZone) + gamePad.analogs[1] = 0.0f; + +#ifdef DEBUG + if (gpad_debug.get()) + { + Logger::Print("Triggers: %f %f\n", gamePad.analogs[0], gamePad.analogs[1]); + } +#endif + } + + void Gamepad::GPad_UpdateAll() + { + GPad_RefreshAll(); + + for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++) + { + const auto& gamePad = gamePads[currentGamePadIndex]; + if (!gamePad.enabled) + continue; + + XINPUT_STATE inputState; + if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) + continue; + + GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); + } + } + + void Gamepad::IN_GamePadsMove() + { + if (!gpad_enabled.get()) + return; + + GPad_UpdateAll(); + const auto time = Game::Sys_Milliseconds(); + + bool gpadPresent = false; + for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++) + { + const auto& gamePad = gamePads[gamePadIndex]; + + if (gamePad.enabled) + { + gpadPresent = true; + const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); + const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); + const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); + const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); + const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); + const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); + + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); + + for (const auto& buttonMapping : buttonList) + { + if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_PRESSED, + time); + } + else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_UPDATE, + time); + } + else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_RELEASED, + time); + } + } + } + } + + gpad_present.setRaw(gpadPresent); + } + + + void Gamepad::IN_Frame_Hk() + { + // Call original method + Utils::Hook::Call(0x64C490)(); + + IN_GamePadsMove(); + } + + void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + Game::FS_Printf(handle, "unbindallaxis\n"); + + for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) + { + const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; + if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT + || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) + { + continue; + } + + const auto* physicalAxisName = physicalAxisNames[axisMapping.physicalAxis]; + const auto* virtualAxisName = virtualAxisNames[virtualAxisIndex]; + const auto* mappingName = gamePadMappingTypeNames[axisMapping.mapType]; + + Game::FS_Printf(handle, "bindaxis %s %s %s\n", physicalAxisName, virtualAxisName, mappingName); + } + } + + void Gamepad::Key_WriteBindings_Hk(const int localClientNum, const int handle) + { + // Call original function + Utils::Hook::Call(0x4A5A20)(localClientNum, handle); + + Gamepad_WriteBindings(0, handle); + } + + void __declspec(naked) Gamepad::Com_WriteConfiguration_Modified_Stub() + { + __asm + { + mov eax, [ecx + 0x18] + or eax, gamePadBindingsModifiedFlags // Also check for gamePadBindingsModifiedFlags + test al, 1 + jz endMethod + mov gamePadBindingsModifiedFlags, 0 // Reset gamePadBindingsModifiedFlags + mov eax, [ecx + 0x18] // Restore eax to dvar_modified_flags + + push 0x60B26E + retn + + endMethod: + push 0x60B298 + retn + } + } + + void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); + assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); + assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); + + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; + gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; + + gamePadBindingsModifiedFlags |= 1; + } + + Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, physicalAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_PHYSAXIS_NONE; + } + + Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, virtualAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_VIRTAXIS_NONE; + } + + Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, gamePadMappingTypeNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_MAP_NONE; + } + + void Gamepad::Axis_Bind_f(Command::Params* params) + { + if (params->size() < 4) + { + Logger::Print("bindaxis \n"); + return; + } + + const auto* physicalAxisText = params->get(1); + const auto* virtualAxisText = params->get(2); + const auto* mappingText = params->get(3); + + const Game::GamepadPhysicalAxis physicalAxis = StringToPhysicalAxis(physicalAxisText); + if (physicalAxis == Game::GPAD_PHYSAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid physical axis\n", physicalAxisText); + return; + } + + const Game::GamepadVirtualAxis virtualAxis = StringToVirtualAxis(virtualAxisText); + if (virtualAxis == Game::GPAD_VIRTAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid virtual axis\n", virtualAxisText); + return; + } + + const Game::GamepadMapping mapping = StringToGamePadMapping(mappingText); + if (mapping == Game::GPAD_MAP_NONE) + { + Logger::Print("\"%s\" isn't a valid input type\n", mappingText); + return; + } + + Gamepad_BindAxis(0, physicalAxis, virtualAxis, mapping); + } + + void Gamepad::Axis_Unbindall_f(Command::Params*) + { + auto& gamePadGlobal = gamePadGlobals[0]; + + for (auto& virtualAxis : gamePadGlobal.axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + void Gamepad::Bind_GP_SticksConfigs_f(Command::Params*) + { + const auto* stickConfigName = gpad_sticksConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", stickConfigName)); + } + + void Gamepad::Bind_GP_ButtonsConfigs_f(Command::Params*) + { + const auto* buttonConfigName = gpad_buttonConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", buttonConfigName)); + } + + void Gamepad::Scores_Toggle_f(Command::Params*) + { + if(Game::cgArray[0].nextSnap) + { + if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) + Game::CG_ScoresDown_f(); + else + Game::CG_ScoresUp_f(); + } + } + + void Gamepad::InitDvars() + { + gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_ARCHIVE, "Game pad enabled"); + gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); + gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_NONE, "Game pad present"); + gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_NONE, "A game pad is in use"); + gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_ARCHIVE, "Switch between Xbox and PS HUD"); + gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_ARCHIVE, "Game pad stick configuration"); + gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_ARCHIVE, "Game pad button configuration"); + gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_ARCHIVE, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); + gpad_menu_scroll_delay_rest = Dvar::Register("gpad_menu_scroll_delay_rest", 210, 0, 1000, Game::DVAR_ARCHIVE, + "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); + gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_ARCHIVE, "Enable game pad rumble"); + gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_NONE, + "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); + gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_NONE, "Game pad stick pressed threshhold"); + gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum stick deadzone"); + gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad minimum stick deadzone"); + gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad button deadzone threshhold"); + gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); + gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); + gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, std::numeric_limits::max(), Game::DVAR_NONE, "Time to hold the 'use' button on gamepads to activate use"); + gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_ARCHIVE, "Game pad lockon aim assist enabled"); + gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_ARCHIVE, "Game pad slowdown aim assist enabled"); + + input_viewSensitivity = Dvar::Register("input_viewSensitivity", 1.0f, 0.0001f, 5.0f, Game::DVAR_ARCHIVE, "View Sensitivity"); + input_invertPitch = Dvar::Register("input_invertPitch", false, Game::DVAR_ARCHIVE, "Invert gamepad pitch"); + sv_allowAimAssist = Dvar::Register("sv_allowAimAssist", true, Game::DVAR_NONE, "Controls whether aim assist features on clients are enabled"); + aim_turnrate_pitch = Dvar::Var("aim_turnrate_pitch"); + aim_turnrate_pitch_ads = Dvar::Var("aim_turnrate_pitch_ads"); + aim_turnrate_yaw = Dvar::Var("aim_turnrate_yaw"); + aim_turnrate_yaw_ads = Dvar::Var("aim_turnrate_yaw_ads"); + aim_accel_turnrate_enabled = Dvar::Var("aim_accel_turnrate_enabled"); + aim_accel_turnrate_lerp = Dvar::Var("aim_accel_turnrate_lerp"); + aim_input_graph_enabled = Dvar::Var("aim_input_graph_enabled"); + aim_input_graph_index = Dvar::Var("aim_input_graph_index"); + aim_scale_view_axis = Dvar::Var("aim_scale_view_axis"); + cl_bypassMouseInput = Dvar::Var("cl_bypassMouseInput"); + cg_mapLocationSelectionCursorSpeed = Dvar::Var("cg_mapLocationSelectionCursorSpeed"); + aim_aimAssistRangeScale = Dvar::Var("aim_aimAssistRangeScale"); + aim_slowdown_enabled = Dvar::Var("aim_slowdown_enabled"); + aim_slowdown_debug = Dvar::Var("aim_slowdown_debug"); + aim_slowdown_pitch_scale = Dvar::Var("aim_slowdown_pitch_scale"); + aim_slowdown_pitch_scale_ads = Dvar::Var("aim_slowdown_pitch_scale_ads"); + aim_slowdown_yaw_scale = Dvar::Var("aim_slowdown_yaw_scale"); + aim_slowdown_yaw_scale_ads = Dvar::Var("aim_slowdown_yaw_scale_ads"); + aim_lockon_enabled = Dvar::Var("aim_lockon_enabled"); + aim_lockon_deflection = Dvar::Var("aim_lockon_deflection"); + aim_lockon_pitch_strength = Dvar::Var("aim_lockon_pitch_strength"); + aim_lockon_strength = Dvar::Var("aim_lockon_strength"); + } + + void Gamepad::CG_RegisterDvars_Hk() + { + // Call original method + Utils::Hook::Call(0x4F8DC0)(); + + InitDvars(); + } + + const char* Gamepad::GetGamePadCommand(const char* command) + { + if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) + return "+usereload"; + if (strcmp(command, "+melee_breath") == 0) + return "+holdbreath"; + + return command; + } + + int Gamepad::Key_GetCommandAssignmentInternal_Hk(const char* cmd, int (*keys)[2]) + { + auto keyCount = 0; + + if (gamePads[0].inUse) + { + cmd = GetGamePadCommand(cmd); + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (!Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + else + { + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + + return keyCount; + } + + void Gamepad::CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time) + { + // A keyboard key has been pressed. Mark controller as unused. + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + + // Call original function + Utils::Hook::Call(0x4F6480)(localClientNum, key, down, time); + } + + bool Gamepad::IsGamePadInUse() + { + return gamePads[0].inUse; + } + + int Gamepad::CL_MouseEvent_Hk(const int x, const int y, const int dx, const int dy) + { + if (dx != 0 || dy != 0) + { + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + } + + // Call original function + return Utils::Hook::Call(0x4D7C50)(x, y, dx, dy); + } + + bool Gamepad::UI_RefreshViewport_Hk() + { + 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(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(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); + } + + Gamepad::Gamepad() + { + if (ZoneBuilder::IsEnabled()) + return; + + // Initialize gamepad environment + Utils::Hook(0x4059FE, CG_RegisterDvars_Hk, HOOK_CALL).install()->quick(); + + // package the forward and right move components in the move buttons + Utils::Hook(0x60E38D, MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + + // send two bytes for sending movement data + Utils::Hook::Set(0x60E501, 16); + Utils::Hook::Set(0x60E5CD, 16); + + // make sure to parse the movement data properly and apply it + Utils::Hook(0x492127, MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x492009, MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick(); + + // Also rewrite configuration when gamepad config is dirty + 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); + Command::Add("unbindallaxis", Axis_Unbindall_f); + Command::Add("bindgpsticksconfigs", Bind_GP_SticksConfigs_f); + Command::Add("bindgpbuttonsconfigs", Bind_GP_ButtonsConfigs_f); + Command::Add("togglescores", Scores_Toggle_f); + + if (Dedicated::IsEnabled()) + return; + + // Gamepad on frame hook + Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); + + // Mark controller as unused when keyboard key is pressed + Utils::Hook(0x43D179, CL_KeyEvent_Hk, HOOK_CALL).install()->quick(); + + // Mark controller as unused when mouse is moved + Utils::Hook(0x64C507, CL_MouseEvent_Hk, HOOK_CALL).install()->quick(); + + // Hide cursor when controller is active + Utils::Hook(0x48E527, UI_RefreshViewport_Hk, HOOK_CALL).install()->quick(); + + // 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 gamepad inputs to remote control (eg predator) handling + Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); + + // Add gamepad inputs to location selection (eg airstrike location) handling + Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); + + // Add gamepad inputs to usercmds + Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp new file mode 100644 index 00000000..de0c16e0 --- /dev/null +++ b/src/Components/Modules/Gamepad.hpp @@ -0,0 +1,203 @@ +#pragma once + +namespace Components +{ + class Gamepad : public Component + { + struct ControllerMenuKeyMapping + { + Game::keyNum_t controllerKey; + Game::keyNum_t pcKey; + }; + + struct GamePad + { + bool enabled; + bool inUse; + int portIndex; + unsigned short digitals; + unsigned short lastDigitals; + float analogs[2]; + float lastAnalogs[2]; + float sticks[4]; + float lastSticks[4]; + bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; + bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; + float lowRumble; + float highRumble; + + XINPUT_VIBRATION rumble; + XINPUT_CAPABILITIES caps; + }; + + struct GamePadGlobals + { + Game::GpadAxesGlob axes; + unsigned nextScrollTime; + + GamePadGlobals(); + }; + + public: + Gamepad(); + + private: + static Game::ButtonToCodeMap_t buttonList[]; + static Game::StickToCodeMap_t analogStickList[4]; + static Game::GamePadStick stickForAxis[]; + static Game::GamepadPhysicalAxis axisSameStick[]; + static const char* physicalAxisNames[]; + static const char* virtualAxisNames[]; + static const char* gamePadMappingTypeNames[]; + static Game::keyNum_t menuScrollButtonList[]; + static Game::keyname_t extendedKeyNames[]; + static Game::keyname_t extendedLocalizedKeyNamesXenon[]; + static Game::keyname_t extendedLocalizedKeyNamesPs3[]; + static Game::keyname_t combinedKeyNames[]; + static Game::keyname_t combinedLocalizedKeyNamesXenon[]; + static Game::keyname_t combinedLocalizedKeyNamesPs3[]; + static ControllerMenuKeyMapping controllerMenuKeyMappings[]; + + static GamePad gamePads[Game::MAX_GAMEPADS]; + static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; + + static int gamePadBindingsModifiedFlags; + + static Dvar::Var gpad_enabled; + 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; + static Dvar::Var gpad_menu_scroll_delay_rest; + static Dvar::Var gpad_rumble; + static Dvar::Var gpad_stick_pressed_hysteresis; + static Dvar::Var gpad_stick_pressed; + static Dvar::Var gpad_stick_deadzone_max; + static Dvar::Var gpad_stick_deadzone_min; + static Dvar::Var gpad_button_deadzone; + static Dvar::Var gpad_button_rstick_deflect_max; + static Dvar::Var gpad_button_lstick_deflect_max; + static Dvar::Var gpad_use_hold_time; + static Dvar::Var gpad_lockon_enabled; + static Dvar::Var gpad_slowdown_enabled; + static Dvar::Var input_viewSensitivity; + static Dvar::Var input_invertPitch; + static Dvar::Var sv_allowAimAssist; + static Dvar::Var aim_turnrate_pitch; + static Dvar::Var aim_turnrate_pitch_ads; + static Dvar::Var aim_turnrate_yaw; + static Dvar::Var aim_turnrate_yaw_ads; + static Dvar::Var aim_accel_turnrate_enabled; + static Dvar::Var aim_accel_turnrate_lerp; + static Dvar::Var aim_input_graph_enabled; + static Dvar::Var aim_input_graph_index; + static Dvar::Var aim_scale_view_axis; + static Dvar::Var cl_bypassMouseInput; + static Dvar::Var cg_mapLocationSelectionCursorSpeed; + static Dvar::Var aim_aimAssistRangeScale; + static Dvar::Var aim_slowdown_enabled; + static Dvar::Var aim_slowdown_debug; + static Dvar::Var aim_slowdown_pitch_scale; + static Dvar::Var aim_slowdown_pitch_scale_ads; + static Dvar::Var aim_slowdown_yaw_scale; + static Dvar::Var aim_slowdown_yaw_scale_ads; + static Dvar::Var aim_lockon_enabled; + static Dvar::Var aim_lockon_deflection; + static Dvar::Var aim_lockon_pitch_strength; + static Dvar::Var aim_lockon_strength; + + static void MSG_WriteDeltaUsercmdKeyStub(); + + static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); + + static void MSG_ReadDeltaUsercmdKeyStub(); + static void MSG_ReadDeltaUsercmdKeyStub2(); + + static float LinearTrack(float target, float current, float rate, float deltaTime); + static bool AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, float clipHalfWidth, float clipHalfHeight); + static bool AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps); + static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); + static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); + static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); + static bool AimAssist_IsLockonActive(int gamePadIndex); + static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); + static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); + static void AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale); + static float AimAssist_Lerp(float from, float to, float fraction); + static void AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output); + + static void CL_RemoteControlMove_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CL_RemoteControlMove_Stub(); + static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CG_HandleLocationSelectionInput_Stub(); + static bool CG_ShouldUpdateViewAngles(int localClientNum); + static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); + static char ClampChar(int value); + static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); + static void CL_MouseMove_Stub(); + + static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); + static void Player_UseEntity_Stub(); + + static bool Key_IsValidGamePadChar(int key); + static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); + static bool Scoreboard_HandleInput(int gamePadIndex, int key); + static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); + static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); + static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); + static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); + static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); + static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); + + static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); + static float GPad_GetStick(int gamePadIndex, Game::GamePadStick stick); + static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); + static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); + + static void GPad_UpdateSticksDown(int gamePadIndex); + static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); + + static bool GPad_Check(int gamePadIndex, int portIndex); + static void GPad_RefreshAll(); + static void GPad_UpdateAll(); + static void IN_GamePadsMove(); + static void IN_Frame_Hk(); + + static void Gamepad_WriteBindings(int gamePadIndex, int handle); + static void Key_WriteBindings_Hk(int localClientNum, int handle); + static void Com_WriteConfiguration_Modified_Stub(); + + static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); + static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); + static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); + static Game::GamepadMapping StringToGamePadMapping(const char* str); + static void Axis_Bind_f(Command::Params* params); + static void Axis_Unbindall_f(Command::Params* params); + static void Bind_GP_SticksConfigs_f(Command::Params* params); + static void Bind_GP_ButtonsConfigs_f(Command::Params* params); + static void Scores_Toggle_f(Command::Params* params); + + static void InitDvars(); + static void CG_RegisterDvars_Hk(); + + static const char* GetGamePadCommand(const char* command); + static int Key_GetCommandAssignmentInternal_Hk(const char* cmd, int(*keys)[2]); + static bool IsGamePadInUse(); + 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/Gametypes.cpp b/src/Components/Modules/Gametypes.cpp index e9d85f3a..c155cf99 100644 --- a/src/Components/Modules/Gametypes.cpp +++ b/src/Components/Modules/Gametypes.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/IPCPipe.cpp b/src/Components/Modules/IPCPipe.cpp index ee50163a..bcd8185b 100644 --- a/src/Components/Modules/IPCPipe.cpp +++ b/src/Components/Modules/IPCPipe.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/IW4MVM.cpp b/src/Components/Modules/IW4MVM.cpp deleted file mode 100644 index 94fe685e..00000000 --- a/src/Components/Modules/IW4MVM.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "STDInclude.hpp" - -#ifdef COMPILE_IW4MVM -#include -#endif - -namespace Components -{ - IW4MVM::IW4MVM() - { - if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled() || Loader::IsPerformingUnitTests()) return; - - DWORD oldProtect; - std::uint8_t* _module = reinterpret_cast(GetModuleHandle(nullptr)); - VirtualProtect(_module + 0x1000, 0x2D6000, PAGE_EXECUTE_READWRITE, &oldProtect); - -#ifdef COMPILE_IW4MVM - client_main::Init(); - Scheduler::Once(client_main::PostInit); -#endif - Scheduler::OnFrame([]() - { - if (!Game::CL_IsCgameInitialized()) - { - Dvar::Var("com_timescale").setRaw(1.0f); - } - }); - - VirtualProtect(_module + 0x1000, 0x2D6000, PAGE_EXECUTE_READ, &oldProtect); - } - - IW4MVM::~IW4MVM() - { - - } -} diff --git a/src/Components/Modules/IW4MVM.hpp b/src/Components/Modules/IW4MVM.hpp deleted file mode 100644 index 92541d4f..00000000 --- a/src/Components/Modules/IW4MVM.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -// Disabled for public release -//#define COMPILE_IW4MVM - -namespace Components -{ - class IW4MVM : public Component - { - public: - IW4MVM(); - ~IW4MVM(); - }; -} diff --git a/src/Components/Modules/Lean.cpp b/src/Components/Modules/Lean.cpp index a78111c5..fad47586 100644 --- a/src/Components/Modules/Lean.cpp +++ b/src/Components/Modules/Lean.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index bc586f54..3b385506 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -170,10 +170,10 @@ namespace Components "/dev/full", "/dev/sdb", "/dev/sr0", - "/dev//dev/tty0", + "/dev/tty0", "/dev/urandom", "Snake", - "lsb_release -a", + "lsb_release -a" }; static const char* contributors[] = @@ -181,17 +181,20 @@ namespace Components "a231", "AmateurHailbut", "Aoki", + "Chase", "civil", "Dasfonia", "Deity", "Dizzy", "Dss0", + "FutureRave", "H3X1C", "HardNougat", "Homura", "INeedGames", "Killera", "Lithium", + "Louvenarde", "OneFourOne", "quaK", "RaidMax", @@ -274,7 +277,7 @@ namespace Components // Overwrite SetString Utils::Hook(0x4CE5EE, Localization::SetStringStub, HOOK_CALL).install()->quick(); - Localization::UseLocalization = Dvar::Register("ui_localize", true, Game::dvar_flag::DVAR_FLAG_NONE, "Use localization strings"); + Localization::UseLocalization = Dvar::Register("ui_localize", true, Game::dvar_flag::DVAR_NONE, "Use localization strings"); // Generate localized entries for custom classes above 10 AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index 22ad6e09..f43724f3 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -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) @@ -242,7 +242,7 @@ namespace Components Logger::Logger() { - Dvar::Register("iw4x_onelog", false, Game::dvar_flag::DVAR_FLAG_LATCHED | Game::dvar_flag::DVAR_FLAG_SAVED, "Only write the game log to the 'userraw' OS folder"); + Dvar::Register("iw4x_onelog", false, Game::dvar_flag::DVAR_LATCH | Game::dvar_flag::DVAR_ARCHIVE, "Only write the game log to the 'userraw' OS folder"); Utils::Hook(0x642139, Logger::BuildOSPathStub, HOOK_JUMP).install()->quick(); Logger::PipeOutput(nullptr); @@ -261,7 +261,7 @@ namespace Components { Command::AddSV("log_add", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; Network::Address addr(params->get(1)); @@ -273,7 +273,7 @@ namespace Components Command::AddSV("log_del", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; int num = atoi(params->get(1)); if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast(num) < Logger::LoggingAddresses[0].size()) @@ -312,7 +312,7 @@ namespace Components Command::AddSV("g_log_add", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; Network::Address addr(params->get(1)); @@ -324,7 +324,7 @@ namespace Components Command::AddSV("g_log_del", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; int num = atoi(params->get(1)); if (Utils::String::VA("%i", num) == std::string(params->get(1)) && static_cast(num) < Logger::LoggingAddresses[1].size()) 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/MapDump.cpp b/src/Components/Modules/MapDump.cpp index 4f3053d5..ba87a6ff 100644 --- a/src/Components/Modules/MapDump.cpp +++ b/src/Components/Modules/MapDump.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -112,6 +112,9 @@ namespace Components const unsigned int vertOffset = surface->tris.firstVertex + 1; const unsigned int indexOffset = surface->tris.baseIndex; + // Fuck cube maps for now + if(this->findImage(surface->material, "colorMap")->mapType == 5) continue; + auto& f = this->getFaceList(surface->material); for (unsigned short j = 0; j < surface->tris.triCount; ++j) @@ -332,33 +335,31 @@ namespace Components this->object_.append("\n"); } - void writeMaterial(Game::Material* material) + Game::GfxImage* findImage(Game::Material* material, const std::string& type) const { - std::string name = material->info.name; + Game::GfxImage* image = nullptr; - const auto pos = name.find_last_of('/'); - if (pos != std::string::npos) - { - name = name.substr(pos + 1); - } - - this->object_.append(Utils::String::VA("usemtl %s\n", name.data())); - this->object_.append("s off\n"); - - Game::GfxImage *image = nullptr; + const auto hash = Game::R_HashString(type.data()); for (char l = 0; l < material->textureCount; ++l) { - if (material->textureTable[l].nameStart == 'c' && material->textureTable[l].nameEnd == 'p') + if (material->textureTable[l].nameHash == hash) { - image = material->textureTable[l].u.image; // Hopefully our colorMap + image = material->textureTable[l].u.image; // Hopefully our map + break; } } + return image; + } + + Game::GfxImage* extractImage(Game::Material* material, const std::string& type) const + { + auto* image = this->findImage(material, type); + if (!image) { - Logger::Print("Failed to get color map for material: %s\n", material->info.name); - return; + return image; } // TODO: This is still wrong. @@ -383,12 +384,42 @@ namespace Components D3DXSaveTextureToFileA(_name.data(), D3DXIFF_PNG, image->texture.map, nullptr); } + return image; + } + + void writeMaterial(Game::Material* material) + { + std::string name = material->info.name; + + const auto pos = name.find_last_of('/'); + if (pos != std::string::npos) + { + name = name.substr(pos + 1); + } + + this->object_.append(Utils::String::VA("usemtl %s\n", name.data())); + this->object_.append("s off\n"); + + auto* colorMap = this->extractImage(material, "colorMap"); + auto* normalMap = this->extractImage(material, "normalMap"); + auto* specularMap = this->extractImage(material, "specularMap"); + this->material_.append(Utils::String::VA("\nnewmtl %s\n", name.data())); this->material_.append("Ka 1.0000 1.0000 1.0000\n"); this->material_.append("Kd 1.0000 1.0000 1.0000\n"); this->material_.append("illum 1\n"); - this->material_.append(Utils::String::VA("map_Ka textures/%s.png\n", image->name)); - this->material_.append(Utils::String::VA("map_Kd textures/%s.png\n", image->name)); + this->material_.append(Utils::String::VA("map_Ka textures/%s.png\n", colorMap->name)); + this->material_.append(Utils::String::VA("map_Kd textures/%s.png\n", colorMap->name)); + + if (specularMap) + { + this->material_.append(Utils::String::VA("map_Ks textures/%s.png\n", specularMap->name)); + } + + if (normalMap) + { + this->material_.append(Utils::String::VA("bump textures/%s.png\n", normalMap->name)); + } } void writeFaces() diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 44c3a2ec..f46fdd56 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -206,8 +206,16 @@ namespace Components if (std::find(Maps::CurrentDependencies.begin(), Maps::CurrentDependencies.end(), FastFiles::Current()) != Maps::CurrentDependencies.end() && (FastFiles::Current() != "mp_shipment_long" || Maps::CurrentMainZone != "mp_shipment")) // Shipment is a special case { - if (type == Game::XAssetType::ASSET_TYPE_CLIPMAP_MP || type == Game::XAssetType::ASSET_TYPE_CLIPMAP_SP || type == Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP || type == Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP || type == Game::XAssetType::ASSET_TYPE_GFXWORLD || type == Game::XAssetType::ASSET_TYPE_MAP_ENTS || type == Game::XAssetType::ASSET_TYPE_COMWORLD || type == Game::XAssetType::ASSET_TYPE_FXWORLD) + switch (type) { + case Game::XAssetType::ASSET_TYPE_CLIPMAP_MP: + case Game::XAssetType::ASSET_TYPE_CLIPMAP_SP: + case Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP: + case Game::XAssetType::ASSET_TYPE_GAMEWORLD_MP: + case Game::XAssetType::ASSET_TYPE_GFXWORLD: + case Game::XAssetType::ASSET_TYPE_MAP_ENTS: + case Game::XAssetType::ASSET_TYPE_COMWORLD: + case Game::XAssetType::ASSET_TYPE_FXWORLD: *restrict = true; return; } @@ -241,7 +249,7 @@ namespace Components { if (Flags::HasFlag("dump")) { - Utils::IO::WriteFile(Utils::String::VA("raw/%s.ents", name.data()), asset.mapEnts->entityString); + Utils::IO::WriteFile(Utils::String::VA("raw/%s.ents", name.data()), asset.mapEnts->entityString, true); } static std::string mapEntities; @@ -249,11 +257,11 @@ namespace Components if (ents.exists()) { mapEntities = ents.getBuffer(); - asset.mapEnts->entityString = const_cast(mapEntities.data()); + asset.mapEnts->entityString = mapEntities.data(); asset.mapEnts->numEntityChars = mapEntities.size() + 1; } } - + // This is broken if ((type == Game::XAssetType::ASSET_TYPE_MENU || type == Game::XAssetType::ASSET_TYPE_MENULIST) && Zones::Version() >= 359) { @@ -318,7 +326,7 @@ namespace Components mapname = "mp_shipment_long"; } - _snprintf_s(buffer, size, size, format, mapname); + _snprintf_s(buffer, size, _TRUNCATE, format, mapname); } void Maps::HandleAsSPMap() @@ -333,7 +341,7 @@ namespace Components { std::regex _(expression); } - catch (const std::exception e) + catch (const std::regex_error ex) { MessageBoxA(nullptr, Utils::String::VA("Invalid regular expression: %s", expression.data()), "Warning", MB_ICONEXCLAMATION); return; @@ -358,7 +366,7 @@ namespace Components { if (arena->keys[j] == "dependency"s) { - return Utils::String::Explode(arena->values[j], ' '); + return Utils::String::Split(arena->values[j], ' '); } } } @@ -439,7 +447,7 @@ namespace Components void Maps::LoadNewMapCommand(char* buffer, size_t size, const char* /*format*/, const char* mapname, const char* gametype) { unsigned int hash = Maps::GetUsermapHash(mapname); - _snprintf_s(buffer, size, size, "loadingnewmap\n%s\n%s\n%d", mapname, gametype, hash); + _snprintf_s(buffer, size, _TRUNCATE, "loadingnewmap\n%s\n%s\n%d", mapname, gametype, hash); } int Maps::TriggerReconnectForMap(Game::msg_t* msg, const char* mapname) @@ -536,7 +544,7 @@ namespace Components } } - Dvar::Register(Utils::String::VA("isDlcInstalled_%d", dlc.index), false, Game::DVAR_FLAG_USERCREATED | Game::DVAR_FLAG_WRITEPROTECTED, ""); + Dvar::Register(Utils::String::VA("isDlcInstalled_%d", dlc.index), false, Game::DVAR_EXTERNAL | Game::DVAR_WRITEPROTECTED, ""); Maps::DlcPacks.push_back(dlc); Maps::UpdateDlcStatus(); @@ -560,7 +568,7 @@ namespace Components } hasDlc.push_back(hasAllMaps); - Dvar::Var(Utils::String::VA("isDlcInstalled_%d", pack.index)).setRaw(hasAllMaps ? 1 : 0); + Dvar::Var(Utils::String::VA("isDlcInstalled_%d", pack.index)).set(hasAllMaps ? true : false); } // Must have all of dlc 3 to 5 or it causes issues @@ -571,7 +579,7 @@ namespace Components sentMessage = true; } - Dvar::Var("isDlcInstalled_All").setRaw(hasAllDlcs ? 1 : 0); + Dvar::Var("isDlcInstalled_All").set(hasAllDlcs ? true : false); } bool Maps::IsCustomMap() @@ -682,7 +690,7 @@ namespace Components Game::dvar_t* Maps::GetSpecularDvar() { Game::dvar_t*& r_specular = *reinterpret_cast(0x69F0D94); - static Game::dvar_t* r_specularCustomMaps = Game::Dvar_RegisterBool("r_specularCustomMaps", false, Game::DVAR_FLAG_SAVED, "Allows shaders to use phong specular lighting on custom maps"); + static Game::dvar_t* r_specularCustomMaps = Game::Dvar_RegisterBool("r_specularCustomMaps", false, Game::DVAR_ARCHIVE, "Allows shaders to use phong specular lighting on custom maps"); if (Maps::IsCustomMap()) { @@ -739,13 +747,30 @@ namespace Components Utils::Hook::Call(0x408910)(ent, unk, unk2); } + + bool Maps::SV_SetTriggerModelHook(Game::gentity_s* ent) { + + // Use me for debugging + //std::string classname = Game::SL_ConvertToString(ent->script_classname); + //std::string targetname = Game::SL_ConvertToString(ent->targetname); + + return Utils::Hook::Call(0x5050C0)(ent); + } + + 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); + } Maps::Maps() { Dvar::OnInit([]() { - Dvar::Register("isDlcInstalled_All", false, Game::DVAR_FLAG_USERCREATED | Game::DVAR_FLAG_WRITEPROTECTED, ""); - Dvar::Register("r_listSModels", false, Game::DVAR_FLAG_NONE, "Display a list of visible SModels"); + Dvar::Register("isDlcInstalled_All", false, Game::DVAR_EXTERNAL | Game::DVAR_WRITEPROTECTED, ""); + Dvar::Register("r_listSModels", false, Game::DVAR_NONE, "Display a list of visible SModels"); Maps::AddDlc({ 1, "Stimulus Pack", {"mp_complex", "mp_compact", "mp_storm", "mp_overgrown", "mp_crash"} }); Maps::AddDlc({ 2, "Resurgence Pack", {"mp_abandon", "mp_vacant", "mp_trailerpark", "mp_strike", "mp_fuel2"} }); @@ -755,6 +780,7 @@ namespace Components Maps::AddDlc({ 6, "Freighter", {"mp_cargoship_sh"} }); Maps::AddDlc({ 7, "Resurrection Pack", {"mp_shipment_long", "mp_rust_long", "mp_firingrange"} }); Maps::AddDlc({ 8, "Recycled Pack", {"mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} }); + Maps::AddDlc({ 9, "Classics Pack #3", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan"}}); Maps::UpdateDlcStatus(); @@ -766,7 +792,7 @@ namespace Components { if (pack.index == dlc) { - ShellExecute(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW); + ShellExecuteW(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW); return; } } @@ -778,6 +804,15 @@ namespace Components // disable turrets on CoD:OL 448+ maps for now Utils::Hook(0x5EE577, Maps::G_SpawnTurretHook, HOOK_CALL).install()->quick(); Utils::Hook(0x44A4D5, Maps::G_SpawnTurretHook, HOOK_CALL).install()->quick(); + +#ifdef DEBUG + // Check trigger models + Utils::Hook(0x5FC0F1, Maps::SV_SetTriggerModelHook, HOOK_CALL).install()->quick(); + Utils::Hook(0x5FC2671, Maps::SV_SetTriggerModelHook, HOOK_CALL).install()->quick(); + Utils::Hook(0x5050D4, Maps::CM_TriggerModelBounds, HOOK_CALL).install()->quick(); +#endif + + // //#define SORT_SMODELS #if !defined(DEBUG) || !defined(SORT_SMODELS) diff --git a/src/Components/Modules/Maps.hpp b/src/Components/Modules/Maps.hpp index cbc4a282..75f9df7a 100644 --- a/src/Components/Modules/Maps.hpp +++ b/src/Components/Modules/Maps.hpp @@ -120,5 +120,7 @@ namespace Components static void SetSpecularStub1(); static void SetSpecularStub2(); static void G_SpawnTurretHook(Game::gentity_s* ent, int unk, int unk2); + static bool SV_SetTriggerModelHook(Game::gentity_s* ent); + static int16 CM_TriggerModelBounds(int brushModelPointer, Game::Bounds* bounds); }; } diff --git a/src/Components/Modules/Materials.cpp b/src/Components/Modules/Materials.cpp index 79d70a12..69176b08 100644 --- a/src/Components/Modules/Materials.cpp +++ b/src/Components/Modules/Materials.cpp @@ -1,8 +1,7 @@ -#include "STDInclude.hpp" +#include namespace Components { - int Materials::ImageNameLength; Utils::Hook Materials::ImageVersionCheckHook; std::vector Materials::ImageTable; @@ -151,55 +150,6 @@ namespace Components } } - Game::Material* Materials::ResolveMaterial(const char* stringPtr) - { - const char* imagePtr = stringPtr + 4; - unsigned int length = static_cast(stringPtr[3] & 0xFF); - - if (strlen(imagePtr) >= length) - { - Materials::ImageNameLength = 4 + length; - std::string image(imagePtr, length); - - return Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, image.data()).material; - } - - Materials::ImageNameLength = 4; - return Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; - } - - __declspec(naked) void Materials::PostDrawMaterialStub() - { - __asm - { - mov eax, Materials::ImageNameLength - add [esp + 30h], eax - - mov eax, 5358FFh - jmp eax - } - } - - __declspec(naked) void Materials::DrawMaterialStub() - { - __asm - { - push eax - pushad - - push ecx - call Materials::ResolveMaterial - add esp, 4h - - mov [esp + 20h], eax - popad - pop eax - - push 5310F0h - retn - } - } - int Materials::WriteDeathMessageIcon(char* string, int offset, Game::Material* material) { if (!material) @@ -282,17 +232,9 @@ namespace Components Materials::Materials() { - Materials::ImageNameLength = 7; - // Allow codo images Materials::ImageVersionCheckHook.initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->install(); - // Fix material pointer exploit - Utils::Hook(0x534E0C, Materials::DrawMaterialStub, HOOK_CALL).install()->quick(); - - // Increment string pointer accordingly - Utils::Hook(0x5358FA, Materials::PostDrawMaterialStub, HOOK_JUMP).install()->quick(); - // Adapt death message to IW5 material format Utils::Hook(0x5A30D9, Materials::DeathMessageStub, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Materials.hpp b/src/Components/Modules/Materials.hpp index 2a2d55dd..38388ae6 100644 --- a/src/Components/Modules/Materials.hpp +++ b/src/Components/Modules/Materials.hpp @@ -21,15 +21,10 @@ namespace Components private: static std::vector ImageTable; static std::vector MaterialTable; - static int ImageNameLength; static Utils::Hook ImageVersionCheckHook; static void ImageVersionCheck(); - static Game::Material* ResolveMaterial(const char* stringPtr); - static void DrawMaterialStub(); - static void PostDrawMaterialStub(); - static int WriteDeathMessageIcon(char* string, int offset, Game::Material* material); static void DeathMessageStub(); diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index d18c8b9c..f29b4499 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -845,7 +845,7 @@ namespace Components Command::Add("openmenu", [](Command::Params* params) { - if (params->length() != 2) + if (params->size() != 2) { Logger::Print("USAGE: openmenu \n"); return; @@ -897,6 +897,7 @@ namespace Components Menus::Add("ui_mp/theater_menu.menu"); Menus::Add("ui_mp/pc_options_multi.menu"); Menus::Add("ui_mp/pc_options_game.menu"); + Menus::Add("ui_mp/pc_options_gamepad.menu"); Menus::Add("ui_mp/stats_reset.menu"); Menus::Add("ui_mp/stats_unlock.menu"); Menus::Add("ui_mp/security_increase_popmenu.menu"); @@ -912,7 +913,6 @@ namespace Components Menus::~Menus() { - Menus::CustomMenus.clear(); Menus::FreeEverything(); } } diff --git a/src/Components/Modules/ModList.cpp b/src/Components/Modules/ModList.cpp index dac1ecaa..955142a9 100644 --- a/src/Components/Modules/ModList.cpp +++ b/src/Components/Modules/ModList.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -93,7 +93,7 @@ namespace Components if (Dedicated::IsEnabled()) return; ModList::CurrentMod = 0; - Dvar::Register("cl_modVidRestart", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Perform a vid_restart when loading a mod."); + Dvar::Register("cl_modVidRestart", true, Game::dvar_flag::DVAR_ARCHIVE, "Perform a vid_restart when loading a mod."); UIScript::Add("LoadMods", ModList::UIScript_LoadMods); UIScript::Add("RunMod", ModList::UIScript_RunMod); @@ -101,9 +101,4 @@ namespace Components UIFeeder::Add(9.0f, ModList::GetItemCount, ModList::GetItemText, ModList::Select); } - - ModList::~ModList() - { - ModList::Mods.clear(); - } } diff --git a/src/Components/Modules/ModList.hpp b/src/Components/Modules/ModList.hpp index a871fb45..185aaed1 100644 --- a/src/Components/Modules/ModList.hpp +++ b/src/Components/Modules/ModList.hpp @@ -6,7 +6,6 @@ namespace Components { public: ModList(); - ~ModList(); static void RunMod(const std::string& mod); diff --git a/src/Components/Modules/ModelSurfs.cpp b/src/Components/Modules/ModelSurfs.cpp index 55336ee8..374b028a 100644 --- a/src/Components/Modules/ModelSurfs.cpp +++ b/src/Components/Modules/ModelSurfs.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Monitor.cpp b/src/Components/Modules/Monitor.cpp index 4f0edcb5..c3140377 100644 --- a/src/Components/Modules/Monitor.cpp +++ b/src/Components/Modules/Monitor.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #undef getch #undef ungetch #include diff --git a/src/Components/Modules/Movement.cpp b/src/Components/Modules/Movement.cpp new file mode 100644 index 00000000..57bfc72f --- /dev/null +++ b/src/Components/Modules/Movement.cpp @@ -0,0 +1,356 @@ +#include + +namespace Components +{ + Dvar::Var Movement::PlayerDuckedSpeedScale; + Dvar::Var Movement::PlayerLastStandCrawlSpeedScale; + Dvar::Var Movement::PlayerProneSpeedScale; + Dvar::Var Movement::PlayerSpectateSpeedScale; + Dvar::Var Movement::CGUfoScaler; + Dvar::Var Movement::CGNoclipScaler; + Dvar::Var Movement::BGBouncesAllAngles; + Dvar::Var Movement::BGRocketJump; + Dvar::Var Movement::BGPlayerEjection; + Dvar::Var Movement::BGPlayerCollision; + Game::dvar_t* Movement::BGBounces; + + float Movement::PM_CmdScaleForStance(const Game::pmove_s* pm) + { + assert(pm->ps != nullptr); + + const auto* playerState = pm->ps; + float scale; + + if (playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0xB) + { + scale = pm->cmd.serverTime - playerState->viewHeightLerpTime / 400.0f; + + if (0.0f <= scale) + { + if (scale > 1.0f) + { + 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 / pm->cmd.serverTime - playerState->viewHeightLerpTime; + + if (0.0f <= scale) + { + if (scale > 1.0f) + { + scale = 1.0f; + } + else if (scale != 0.0f) + { + return scale * 0.65f + (1.0f - scale) * 0.15f; + } + } + } + + scale = 1.0f; + const auto stance = Game::PM_GetEffectiveStance(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::PM_CmdScaleForStanceStub() + { + __asm + { + pushad + + push edx + call Movement::PM_CmdScaleForStance // pm + add esp, 4 + + popad + ret + } + } + + float Movement::PM_MoveScale(Game::playerState_s* ps, float forwardmove, + float rightmove, float upmove) + { + assert(ps != nullptr); + + auto max = (std::fabsf(forwardmove) < std::fabsf(rightmove)) + ? std::fabsf(rightmove) + : std::fabsf(forwardmove); + + if (std::fabsf(upmove) > max) + { + max = std::fabsf(upmove); + } + + if (max == 0.0f) + { + return 0.0f; + } + + auto total = std::sqrtf(forwardmove * forwardmove + + rightmove * rightmove + upmove * upmove); + auto scale = (ps->speed * max) / (127.0f * total); + + if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f) + { + scale *= 0.4f; + } + + if (ps->pm_type == Game::PM_NOCLIP) + { + return scale * Movement::CGNoclipScaler.get(); + } + + if (ps->pm_type == Game::PM_UFO) + { + return scale * Movement::CGUfoScaler.get(); + } + + if (ps->pm_type == Game::PM_SPECTATOR) + { + return scale * Movement::PlayerSpectateSpeedScale.get(); + } + + return scale; + } + + __declspec(naked) void Movement::PM_MoveScaleStub() + { + __asm + { + pushad + + push [esp + 0xC + 0x20] // upmove + push [esp + 0xC + 0x20] // rightmove + push [esp + 0xC + 0x20] // forwardmove + push esi // ps + call Movement::PM_MoveScale + add esp, 0x10 + + popad + ret + } + } + + __declspec(naked) void Movement::PM_StepSlideMoveStub() + { + __asm + { + // Check the value of BGBounces + push ecx + push eax + + mov eax, Movement::BGBounces + mov ecx, dword ptr [eax + 0x10] + test ecx, ecx + + pop eax + pop ecx + + // Do not bounce if BGBounces is 0 + jle noBounce + + // Bounce + push 0x4B1B34 + retn + + noBounce: + // Original game code + cmp dword ptr [esp + 0x24], 0 + push 0x4B1B48 + retn + } + } + + void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut) + { + const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1]; + + if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0) + { + velOut[0] = velIn[0]; + velOut[1] = velIn[1]; + velOut[2] = velIn[2]; + return; + } + + auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1]; + newZ = -newZ / normal[2]; + const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D) + / (newZ * newZ + lengthSquared2D)); + + if (Movement::BGBouncesAllAngles.get() + || (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f)) + { + velOut[0] = velIn[0] * lengthScale; + velOut[1] = velIn[1] * lengthScale; + velOut[2] = newZ * lengthScale; + } + } + + // Double bounces + void Movement::Jump_ClearState_Hk(Game::playerState_s* ps) + { + if (Movement::BGBounces->current.integer != Movement::DOUBLE) + { + Game::Jump_ClearState(ps); + } + } + + Game::gentity_s* Movement::Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, + float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7) + { + auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7); + + if (ent->client != nullptr && BGRocketJump.get()) + { + ent->client->ps.velocity[0] += (0 - wp->forward[0]) * 64.0f; + ent->client->ps.velocity[1] += (0 - wp->forward[1]) * 64.0f; + ent->client->ps.velocity[2] += (0 - wp->forward[2]) * 64.0f; + } + + return result; + } + + int Movement::StuckInClient_Hk(Game::gentity_s* self) + { + if (Movement::BGPlayerEjection.get()) + { + return Utils::Hook::Call(0x402D30)(self); // StuckInClient + } + + return 0; + } + + void Movement::CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, + const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles) + { + if (Movement::BGPlayerCollision.get()) + { + Utils::Hook::Call + (0x478300) + (results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace + } + } + + Game::dvar_t* Movement::Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value, + float min, float max, unsigned __int16 /*flags*/, const char* description) + { + Movement::PlayerLastStandCrawlSpeedScale = Dvar::Register(dvarName, value, + min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); + + return Movement::PlayerLastStandCrawlSpeedScale.get(); + } + + Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, + float min, float max, unsigned __int16 /*flags*/, const char* description) + { + Movement::PlayerSpectateSpeedScale = Dvar::Register(dvarName, value, + min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); + + return Movement::PlayerSpectateSpeedScale.get(); + } + + Movement::Movement() + { + Dvar::OnInit([] + { + static const char* bg_bouncesValues[] = + { + "disabled", + "enabled", + "double", + nullptr + }; + + Movement::PlayerDuckedSpeedScale = Dvar::Register("player_duckedSpeedScale", + 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The scale applied to the player speed when ducking"); + + Movement::PlayerProneSpeedScale = Dvar::Register("player_proneSpeedScale", + 0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The scale applied to the player speed when crawling"); + + // 3arc naming convention + Movement::CGUfoScaler = Dvar::Register("cg_ufo_scaler", + 6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The speed at which ufo camera moves"); + + Movement::CGNoclipScaler = Dvar::Register("cg_noclip_scaler", + 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The speed at which noclip camera moves"); + + Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces", + bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); + + Movement::BGBouncesAllAngles = Dvar::Register("bg_bouncesAllAngles", + false, Game::DVAR_CODINFO, "Force bounce from all angles"); + + Movement::BGRocketJump = Dvar::Register("bg_rocketJump", + false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); + + Movement::BGPlayerEjection = Dvar::Register("bg_playerEjection", + true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); + + Movement::BGPlayerCollision = Dvar::Register("bg_playerCollision", + true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); + }); + + // Hook PM_CmdScaleForStance in PM_CmdScale_Walk + Utils::Hook(0x572F34, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick(); + + //Hook PM_CmdScaleForStance in PM_GetMaxSpeed + Utils::Hook(0x57395F, Movement::PM_CmdScaleForStanceStub, 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(); + + // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. + Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); + + // Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip + Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); + + // Bounce logic + Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick(); + + // Rocket jump + Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon + + // Hook StuckInClient & CM_TransformedCapsuleTrace + // so we can prevent intersecting players from being pushed away from each other + Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity + Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity + } +} diff --git a/src/Components/Modules/Movement.hpp b/src/Components/Modules/Movement.hpp new file mode 100644 index 00000000..2b718258 --- /dev/null +++ b/src/Components/Modules/Movement.hpp @@ -0,0 +1,46 @@ +#pragma once + +namespace Components +{ + class Movement : public Component + { + public: + Movement(); + + private: + enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; + + static Dvar::Var PlayerDuckedSpeedScale; + static Dvar::Var PlayerLastStandCrawlSpeedScale; + static Dvar::Var PlayerProneSpeedScale; + static Dvar::Var PlayerSpectateSpeedScale; + static Dvar::Var CGUfoScaler; + static Dvar::Var CGNoclipScaler; + static Dvar::Var BGBouncesAllAngles; + static Dvar::Var BGRocketJump; + static Dvar::Var BGPlayerEjection; + static Dvar::Var BGPlayerCollision; + // Can't use Var class inside assembly stubs + static Game::dvar_t* BGBounces; + + static float PM_CmdScaleForStance(const Game::pmove_s* move); + static void PM_CmdScaleForStanceStub(); + + static float PM_MoveScale(Game::playerState_s* ps, float forwardmove, float rightmove, float upmove); + static void PM_MoveScaleStub(); + + // Bounce logic + static void PM_StepSlideMoveStub(); + static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut); + static void Jump_ClearState_Hk(Game::playerState_s* ps); + + static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7); + + // Player collison + static int StuckInClient_Hk(Game::gentity_s* self); + static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); + + static Game::dvar_t* Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); + static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); + }; +} diff --git a/src/Components/Modules/MusicalTalent.cpp b/src/Components/Modules/MusicalTalent.cpp index 359856ce..9769fcb6 100644 --- a/src/Components/Modules/MusicalTalent.cpp +++ b/src/Components/Modules/MusicalTalent.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -39,9 +39,4 @@ namespace Components MusicalTalent::Replace("music_mainmenu_mp", "hz_t_menumusic.mp3"); } - - MusicalTalent::~MusicalTalent() - { - MusicalTalent::SoundAliasList.clear(); - } } diff --git a/src/Components/Modules/MusicalTalent.hpp b/src/Components/Modules/MusicalTalent.hpp index e5bfc181..9d10d3d9 100644 --- a/src/Components/Modules/MusicalTalent.hpp +++ b/src/Components/Modules/MusicalTalent.hpp @@ -6,7 +6,6 @@ namespace Components { public: MusicalTalent(); - ~MusicalTalent(); static void Replace(const std::string& sound, const char* file); diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 37ab799d..cd573569 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -14,7 +14,7 @@ namespace Components { Game::SockadrToNetadr(addr, &this->address); } - bool Network::Address::operator==(const Network::Address &obj) const + bool Network::Address::operator==(const Network::Address& obj) const { return Game::NET_CompareAdr(this->address, obj.address); } @@ -342,6 +342,20 @@ namespace Components Game::NET_DeferPacketToClient(from, msg); } + void Network::SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg) + { + if (client->reliableAcknowledge < 0) + { + Logger::Print("Negative reliableAcknowledge from %s - cl->reliableSequence is %i, reliableAcknowledge is %i\n", + client->name, client->reliableSequence, client->reliableAcknowledge); + client->reliableAcknowledge = client->reliableSequence; + Network::SendCommand(Game::NS_SERVER, client->netchan.remoteAddress, "error", "EXE_LOSTRELIABLECOMMANDS"); + return; + } + + Utils::Hook::Call(0x414D40)(client, msg); + } + Network::Network() { AssertSize(Game::netadr_t, 20); @@ -381,16 +395,12 @@ namespace Components // Fix packets causing buffer overflow Utils::Hook(0x6267E3, Network::NET_DeferPacketToClientStub, HOOK_CALL).install()->quick(); + // Fix server freezer exploit + Utils::Hook(0x626996, Network::SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick(); + Network::Handle("resolveAddress", [](Address address, const std::string& /*data*/) { Network::SendRaw(address, address.getString()); }); } - - Network::~Network() - { - Network::SelectedPacket.clear(); - Network::PacketHandlers.clear(); - Network::StartupSignal.clear(); - } } diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index b54b3c0d..f3096350 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -52,7 +52,6 @@ namespace Components typedef void(CallbackRaw)(); Network(); - ~Network(); static unsigned short GetPort(); @@ -89,6 +88,8 @@ namespace Components static void PacketErrorCheck(); static void NET_DeferPacketToClientStub(Game::netadr_t* from, Game::msg_t* msg); + + static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg); }; } diff --git a/src/Components/Modules/News.cpp b/src/Components/Modules/News.cpp index af3176ab..a377084e 100644 --- a/src/Components/Modules/News.cpp +++ b/src/Components/Modules/News.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #define NEWS_MOTD_DEFAULT "Welcome to IW4x Multiplayer!" @@ -39,9 +39,9 @@ namespace Components { if (ZoneBuilder::IsEnabled() || Dedicated::IsEnabled()) return; // Maybe also dedi? - Dvar::Register("g_firstLaunch", true, Game::DVAR_FLAG_SAVED, ""); + Dvar::Register("g_firstLaunch", true, Game::DVAR_ARCHIVE, ""); - Dvar::Register("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_FLAG_WRITEPROTECTED, "Current version number."); + Dvar::Register("cl_updateoldversion", REVISION, REVISION, REVISION, Game::DVAR_WRITEPROTECTED, "Current version number."); UIScript::Add("checkFirstLaunch", [](UIScript::Token) { @@ -60,7 +60,7 @@ namespace Components UIScript::Add("visitWiki", [](UIScript::Token) { //Utils::OpenUrl(Utils::Cache::GetStaticUrl("/wiki/")); - Utils::OpenUrl("https://github.com/Jawesome99/IW4x/wiki"); + Utils::OpenUrl("https://github.com/Emosewaj/IW4x/wiki"); }); UIScript::Add("visitDiscord", [](UIScript::Token) diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 2d60d147..50797f93 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -60,7 +60,7 @@ namespace Components std::string nodes = Utils::Cache::GetFile("/iw4/nodes.txt"); if (nodes.empty()) return; - auto nodeList = Utils::String::Explode(nodes, '\n'); + auto nodeList = Utils::String::Split(nodes, '\n'); for (auto& node : nodeList) { Utils::String::Replace(node, "\r", ""); @@ -377,7 +377,7 @@ namespace Components Command::Add("addnode", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; Node::Add({ params->get(1) }); }); } diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 527f5b8b..564ec01b 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -79,7 +79,7 @@ namespace Components Game::dvar_t* Party::RegisterMinPlayers(const char* name, int /*value*/, int /*min*/, int max, Game::dvar_flag flag, const char* description) { - return Dvar::Register(name, 1, 1, max, Game::dvar_flag::DVAR_FLAG_WRITEPROTECTED | flag, description).get(); + return Dvar::Register(name, 1, 1, max, Game::dvar_flag::DVAR_WRITEPROTECTED | flag, description).get(); } bool Party::PlaylistAwaiting() @@ -150,8 +150,8 @@ 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(); + static Game::dvar_t* partyEnable = Dvar::Register("party_enable", Dedicated::IsEnabled(), Game::dvar_flag::DVAR_NONE, "Enable party system").get(); + Dvar::Register("xblive_privatematch", true, Game::dvar_flag::DVAR_WRITEPROTECTED, ""); // various changes to SV_DirectConnect-y stuff to allow non-party joinees Utils::Hook::Set(0x460D96, 0x90E9); @@ -249,19 +249,19 @@ namespace Components Utils::Hook::Set(0x5E3772, "sv_maxclients"); // Unlatch maxclient dvars - Utils::Hook::Xor(0x426187, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x4D374E, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x5E376A, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x4261A1, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x4D376D, Game::dvar_flag::DVAR_FLAG_LATCHED); - Utils::Hook::Xor(0x5E3789, Game::dvar_flag::DVAR_FLAG_LATCHED); + Utils::Hook::Xor(0x426187, Game::dvar_flag::DVAR_LATCH); + Utils::Hook::Xor(0x4D374E, Game::dvar_flag::DVAR_LATCH); + Utils::Hook::Xor(0x5E376A, Game::dvar_flag::DVAR_LATCH); + Utils::Hook::Xor(0x4261A1, Game::dvar_flag::DVAR_LATCH); + Utils::Hook::Xor(0x4D376D, Game::dvar_flag::DVAR_LATCH); + Utils::Hook::Xor(0x5E3789, Game::dvar_flag::DVAR_LATCH); // Patch Live_PlayerHasLoopbackAddr //Utils::Hook::Set(0x418F30, 0x90C3C033); Command::Add("connect", [](Command::Params* params) { - if (params->length() < 2) + if (params->size() < 2) { return; } @@ -513,9 +513,4 @@ namespace Components Friends::UpdateServer(address, info.get("hostname"), info.get("mapname")); }); } - - Party::~Party() - { - Party::LobbyMap.clear(); - } } diff --git a/src/Components/Modules/Party.hpp b/src/Components/Modules/Party.hpp index 974bdc2f..d8f70cf1 100644 --- a/src/Components/Modules/Party.hpp +++ b/src/Components/Modules/Party.hpp @@ -6,7 +6,6 @@ namespace Components { public: Party(); - ~Party(); static Network::Address Target(); static void Connect(Network::Address target); diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp index 896853ee..d90cadf7 100644 --- a/src/Components/Modules/PlayerName.cpp +++ b/src/Components/Modules/PlayerName.cpp @@ -1,38 +1,78 @@ -#include "STDInclude.hpp" +#include namespace Components { - std::string PlayerName::PlayerNames[18]; + Dvar::Var PlayerName::sv_allowColoredNames; - int PlayerName::GetClientName(int /*localClientNum*/, int index, char *buf, int size) + void PlayerName::UserInfoCopy(char* buffer, const char* name, const size_t size) + { + if (!sv_allowColoredNames.get()) + { + char nameBuffer[64] = { 0 }; + TextRenderer::StripColors(name, nameBuffer, sizeof(nameBuffer)); + TextRenderer::StripAllTextIcons(nameBuffer, buffer, size); + } + else + { + TextRenderer::StripAllTextIcons(name, buffer, size); + } + + std::string readablePlayerName(buffer); + readablePlayerName = Utils::String::Trim(readablePlayerName); + + if (readablePlayerName.size() < 3) + { + strncpy(buffer, "Unknown Soldier", size); + } + } + + __declspec(naked) void PlayerName::ClientUserinfoChanged() + { + __asm + { + mov eax, [esp + 4h] // length + //sub eax, 1 + push eax + + push ecx // name + push edx // buffer + + call UserInfoCopy + + add esp, 0Ch + retn + } + } + + char* PlayerName::GetClientName(int localClientNum, int index, char* buf, size_t size) + { + Game::CL_GetClientName(localClientNum, index, buf, size); + + // Append clantag to username & remove the colors + strncpy_s(buf, size, TextRenderer::StripColors(ClanTags::GetUserClantag(index, buf)).data(), size); + + return buf; + } + char* PlayerName::CleanStrStub(char* string) { - if (index < 0 || index >= 18) return 0; - return strncpy_s(buf, size, PlayerName::PlayerNames[index].data(), PlayerName::PlayerNames[index].size()) == 0; + TextRenderer::StripColors(string, string, strlen(string) + 1); + return string; } PlayerName::PlayerName() { -#if(0) // Disabled for now - { - for (int i = 0; i < ARRAYSIZE(PlayerName::PlayerNames); ++i) - { - PlayerName::PlayerNames[i] = "mumu"; - } + sv_allowColoredNames = Dvar::Register("sv_allowColoredNames", true, Game::dvar_flag::DVAR_NONE, "Allow colored names on the server"); - Utils::Hook(Game::CL_GetClientName, PlayerName::GetClientName, HOOK_JUMP).install()->quick(); - } -#endif + // Disable SV_UpdateUserinfo_f, to block changing the name ingame + Utils::Hook::Set(0x6258D0, 0xC3); - //const char* clanname = "ZOB"; - //Utils::Hook::Set(0x497656, clanname); - //Utils::Hook::Set(0x497679, clanname); - } + // Allow colored names ingame + Utils::Hook(0x5D8B40, ClientUserinfoChanged, HOOK_JUMP).install()->quick(); - PlayerName::~PlayerName() - { - for (int i = 0; i < ARRAYSIZE(PlayerName::PlayerNames); ++i) - { - PlayerName::PlayerNames[i].clear(); - } + // Though, don't apply that to overhead names. + Utils::Hook(0x581932, GetClientName, HOOK_CALL).install()->quick(); + + // Patch I_CleanStr + Utils::Hook(0x4AD470, CleanStrStub, HOOK_JUMP).install()->quick(); } } diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp index 68c3c74d..8335afda 100644 --- a/src/Components/Modules/PlayerName.hpp +++ b/src/Components/Modules/PlayerName.hpp @@ -6,11 +6,14 @@ namespace Components { public: PlayerName(); - ~PlayerName(); + + static void UserInfoCopy(char* buffer, const char* name, size_t size); private: - static std::string PlayerNames[18]; + static Dvar::Var sv_allowColoredNames; - static int GetClientName(int localClientNum, int index, char *buf, int size); + static char* CleanStrStub(char* string); + static void ClientUserinfoChanged(); + static char* GetClientName(int localClientNum, int index, char* buf, size_t size); }; } diff --git a/src/Components/Modules/Playlist.cpp b/src/Components/Modules/Playlist.cpp index 7088dbf3..059b80f1 100644 --- a/src/Components/Modules/Playlist.cpp +++ b/src/Components/Modules/Playlist.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -21,7 +21,7 @@ namespace Components Dvar::Var("xblive_privateserver").set(false); - std::string playlistFilename = Dvar::Var("playlistFilename").get(); + auto playlistFilename = Dvar::Var("playlistFilename").get(); FileSystem::File playlist(playlistFilename); if (playlist.exists()) @@ -190,11 +190,4 @@ namespace Components Network::Handle("playlistResponse", PlaylistReponse); Network::Handle("playlistInvalidPassword", PlaylistInvalidPassword); } - - Playlist::~Playlist() - { - Playlist::MapRelocation.clear(); - Playlist::CurrentPlaylistBuffer.clear(); - Playlist::ReceivedPlaylistBuffer.clear(); - } } diff --git a/src/Components/Modules/Playlist.hpp b/src/Components/Modules/Playlist.hpp index c586815f..d8e7a8fb 100644 --- a/src/Components/Modules/Playlist.hpp +++ b/src/Components/Modules/Playlist.hpp @@ -8,7 +8,6 @@ namespace Components typedef void(*Callback)(); Playlist(); - ~Playlist(); static void LoadPlaylist(); diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index b79bcdd0..de055020 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -1,8 +1,8 @@ -#include "STDInclude.hpp" +#include namespace Components { - int QuickPatch::FrameTime = 0; + Dvar::Var QuickPatch::r_customAspectRatio; void QuickPatch::UnlockStats() { @@ -86,17 +86,15 @@ namespace Components void QuickPatch::SelectStringTableEntryInDvarStub() { - Command::ClientParams args; + Command::ClientParams params; - if (args.length() >= 4) + if (params.size() >= 4) { - std::string cmd = args[0]; - std::string table = args[1]; - std::string col = args[2]; - std::string dvarName = args[3]; - Game::dvar_t* dvar = Game::Dvar_FindVar(dvarName.data()); + const auto* dvarName = params[3]; + const auto* dvar = Game::Dvar_FindVar(dvarName); - if (Command::Find(dvarName) || (dvar && (dvar->flags & (Game::DVAR_FLAG_WRITEPROTECTED | Game::DVAR_FLAG_CHEAT | Game::DVAR_FLAG_READONLY)))) + if (Command::Find(dvarName) || + (dvar != nullptr && dvar->flags & (Game::DVAR_WRITEPROTECTED | Game::DVAR_CHEAT | Game::DVAR_READONLY))) { return; } @@ -139,16 +137,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 +169,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 +181,6 @@ namespace Components } Game::dvar_t* QuickPatch::g_antilag; - __declspec(naked) void QuickPatch::ClientEventsFireWeaponStub() { __asm @@ -240,58 +236,31 @@ namespace Components } } - Game::dvar_t* QuickPatch::sv_enableBounces; - __declspec(naked) void QuickPatch::BounceStub() + Game::dvar_t* QuickPatch::Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** /*valueList*/, int defaultIndex, unsigned __int16 flags, const char* description) { - __asm + static const char* r_aspectRatioEnum[] = { - // check the value of sv_enableBounces - push eax; - mov eax, sv_enableBounces; - cmp byte ptr[eax + 16], 1; - pop eax; - - // always bounce if sv_enableBounces is set to 1 - je bounce; - - // original code - cmp dword ptr[esp + 24h], 0; - jnz dontBounce; - - bounce: - push 0x004B1B34; - retn; - - dontBounce: - push 0x004B1B48; - retn; - } - } - - 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 = - { - 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(dvarName, r_aspectRatioEnum, defaultIndex, 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() @@ -325,136 +294,87 @@ namespace Components } } - Game::dvar_t* QuickPatch::g_playerCollision; - __declspec(naked) void QuickPatch::PlayerCollisionStub() + BOOL QuickPatch::IsDynClassnameStub(char* a1) { - __asm + auto version = Zones::GetEntitiesZoneVersion(); + + if (version >= VERSION_LATEST_CODO) { - // check the value of g_playerCollision - push eax; - mov eax, g_playerCollision; - cmp byte ptr[eax + 16], 0; - pop eax; + for (auto i = 0; i < Game::spawnVars->numSpawnVars; i++) + { + char** kvPair = Game::spawnVars->spawnVars[i]; + auto key = kvPair[0]; + auto val = kvPair[1]; - // dont collide if g_playerCollision is set to 0 - je dontcollide; + bool isSpecOps = strncmp(key, "script_specialops", 17) == 0; + bool isSpecOpsOnly = val[0] == '1' && val[1] == '\0'; - // original code - mov eax, dword ptr[esp + 0xa0]; - jmp collide; + if (isSpecOps && isSpecOpsOnly) + { + // This will prevent spawning of any entity that contains "script_specialops: '1'" + // It removes extra hitboxes / meshes on 461+ CODO multiplayer maps + return TRUE; + } + } + } - collide: - push 0x00478376; - retn; + // Passthrough to the game's own IsDynClassname + return Utils::Hook::Call(0x444810)(a1); + } - dontcollide: - mov eax, dword ptr[esp + 0xa0]; - mov ecx, dword ptr[esp + 9ch]; - push eax; - push ecx; - lea edx, [esp + 48h]; - push edx; - mov eax, esi; - push 0x0047838b; - retn; + void QuickPatch::CL_KeyEvent_OnEscape() + { + if (Game::Con_CancelAutoComplete()) + return; + + if (TextRenderer::HandleFontIconAutocompleteKey(0, TextRenderer::FONT_ICON_ACI_CONSOLE, Game::K_ESCAPE)) + return; + + // Close console + Game::Key_RemoveCatcher(0, ~Game::KEYCATCH_CONSOLE); + } + + __declspec(naked) void QuickPatch::CL_KeyEvent_ConsoleEscape_Stub() + { + __asm + { + pushad + call CL_KeyEvent_OnEscape + popad + + // Exit CL_KeyEvent function + mov ebx, 0x4F66F2 + jmp ebx } } - Game::dvar_t* QuickPatch::g_playerEjection; - __declspec(naked) void QuickPatch::PlayerEjectionStub() + Game::dvar_t* QuickPatch::Dvar_RegisterUIBuildLocation(const char* dvarName, + float /*x*/, float /*y*/, float min, float max, int /*flags*/, const char* description) { - __asm - { - // check the value of g_playerEjection - push eax; - mov eax, g_playerEjection; - cmp byte ptr[eax + 16], 0; - pop eax; - - // dont eject if g_playerEjection is set to 0 - je donteject; - - // original code - cmp dword ptr[ebx + 19ch], edi; - jle eject; - - eject: - push 0x005d8152; - retn; - - donteject: - push 0x005d815b; - retn; - } - } - - template std::function < T > ImportFunction(const std::string& dll, const std::string& function) - { - auto dllHandle = GetModuleHandleA(&dll[0]); - auto procAddr = GetProcAddress(dllHandle, &function[0]); - - return std::function < T >(reinterpret_cast(procAddr)); + return Game::Dvar_RegisterVec2(dvarName, -60.0f, 474.0f, min, max, Game::DVAR_READONLY, description); } QuickPatch::QuickPatch() { - QuickPatch::FrameTime = 0; - Scheduler::OnFrame([]() + // quitHard + Command::Add("quitHard", [](Command::Params*) { - QuickPatch::FrameTime = Game::Sys_Milliseconds(); + int data = false; + const Utils::Library ntdll("ntdll.dll"); + ntdll.invokePascal("RtlAdjustPrivilege", 19, true, false, &data); + ntdll.invokePascal("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data); }); - // quit_hard - Command::Add("quit_hard", [](Command::Params*) - { - typedef enum _HARDERROR_RESPONSE_OPTION { - OptionAbortRetryIgnore, - OptionOk, - OptionOkCancel, - OptionRetryCancel, - OptionYesNo, - OptionYesNoCancel, - OptionShutdownSystem - } HARDERROR_RESPONSE_OPTION, *PHARDERROR_RESPONSE_OPTION; + // Filtering any mapents that is intended for Spec:Ops gamemode (CODO) and prevent them from spawning + Utils::Hook(0x5FBD6E, QuickPatch::IsDynClassnameStub, HOOK_CALL).install()->quick(); - typedef enum _HARDERROR_RESPONSE { - ResponseReturnToCaller, - ResponseNotHandled, - ResponseAbort, - ResponseCancel, - ResponseIgnore, - ResponseNo, - ResponseOk, - ResponseRetry, - ResponseYes - } HARDERROR_RESPONSE, *PHARDERROR_RESPONSE; - - BOOLEAN hasPerms; - HARDERROR_RESPONSE response; - - auto result = ImportFunction("ntdll.dll", "RtlAdjustPrivilege") - (19, true, false, &hasPerms); - - result = ImportFunction("ntdll.dll", "NtRaiseHardError") - (0xC000007B /*0x0000000A*/, 0, nullptr, nullptr, OptionShutdownSystem, &response); - }); - - // bounce dvar - sv_enableBounces = Game::Dvar_RegisterBool("sv_enableBounces", false, Game::DVAR_FLAG_REPLICATED, "Enables bouncing on the server"); - Utils::Hook(0x4B1B2D, QuickPatch::BounceStub, HOOK_JUMP).install()->quick(); + // Hook escape handling on open console to change behaviour to close the console instead of only canceling autocomplete + Utils::Hook(0x4F66A3, CL_KeyEvent_ConsoleEscape_Stub, HOOK_JUMP).install()->quick(); // Intermission time dvar - Game::Dvar_RegisterFloat("scr_intermissionTime", 10, 0, 120, Game::DVAR_FLAG_REPLICATED | Game::DVAR_FLAG_DEDISAVED, "Time in seconds before match server loads the next map"); + Game::Dvar_RegisterFloat("scr_intermissionTime", 10, 0, 120, Game::dvar_flag::DVAR_NONE, "Time in seconds before match server loads the next map"); - // Player Collision dvar - g_playerCollision = Game::Dvar_RegisterBool("g_playerCollision", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off"); - Utils::Hook(0x47836F, QuickPatch::PlayerCollisionStub, HOOK_JUMP).install()->quick(); - - // Player Ejection dvar - g_playerEjection = Game::Dvar_RegisterBool("g_playerEjection", true, Game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off"); - Utils::Hook(0x5D814A, QuickPatch::PlayerEjectionStub, HOOK_JUMP).install()->quick(); - - g_antilag = Game::Dvar_RegisterBool("g_antilag", true, Game::DVAR_FLAG_REPLICATED, "Perform antilag"); + g_antilag = Game::Dvar_RegisterBool("g_antilag", true, Game::DVAR_CODINFO, "Perform antilag"); Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick(); @@ -465,8 +385,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); @@ -516,16 +436,11 @@ namespace Components Utils::Hook::Set(0x60BD56, "IW4x (" VERSION ")"); // version string color - static float buildLocColor[] = { 1.0f, 1.0f, 1.0f, 0.8f }; - Utils::Hook::Set(0x43F710, buildLocColor); + static Game::vec4_t buildLocColor = { 1.0f, 1.0f, 1.0f, 0.8f }; + Utils::Hook::Set(0x43F710, buildLocColor); // Shift ui version string to the left (ui_buildlocation) - Utils::Hook::Nop(0x6310A0, 5); // Don't register the initial dvar - Utils::Hook::Nop(0x6310B8, 5); // Don't write the result - Dvar::OnInit([]() - { - *reinterpret_cast(0x62E4B64) = Game::Dvar_RegisterVec2("ui_buildLocation", -60.0f, 474.0f, -10000.0, 10000.0, Game::DVAR_FLAG_READONLY, "Where to draw the build number"); - }); + Utils::Hook(0x6310A0, QuickPatch::Dvar_RegisterUIBuildLocation, HOOK_CALL).install()->quick(); // console title if (ZoneBuilder::IsEnabled()) @@ -560,7 +475,7 @@ namespace Components // Numerical ping (cg_scoreboardPingText 1) Utils::Hook::Set(0x45888E, 1); - Utils::Hook::Set(0x45888C, Game::dvar_flag::DVAR_FLAG_CHEAT); + Utils::Hook::Set(0x45888C, Game::dvar_flag::DVAR_CHEAT); // increase font sizes for chat on higher resolutions static float float13 = 13.0f; @@ -716,7 +631,7 @@ namespace Components }); // Fix mouse pitch adjustments - Dvar::Register("ui_mousePitch", false, Game::DVAR_FLAG_SAVED, ""); + Dvar::Register("ui_mousePitch", false, Game::DVAR_ARCHIVE, ""); UIScript::Add("updateui_mousePitch", [](UIScript::Token) { if (Dvar::Var("ui_mousePitch").get()) @@ -736,7 +651,7 @@ namespace Components Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage Utils::Hook(0x407376, QuickPatch::SVCanReplaceServerCommand , HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand Utils::Hook(0x5B67ED, QuickPatch::AtolAdjustPlayerLimit , HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest - + Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f // Patch selectStringTableEntryInDvar Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub); @@ -752,14 +667,9 @@ namespace Components QuickPatch::UnlockStats(); }); - Command::Add("crash", [](Command::Params*) - { - throw new std::exception(); - }); - Command::Add("dumptechsets", [](Command::Params* param) { - if (param->length() != 2) + if (param->size() != 2) { Logger::Print("usage: dumptechsets | all\n"); return; @@ -931,6 +841,7 @@ namespace Components } }); +#ifdef DEBUG AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& /*name*/, bool* /*restrict*/) { if (type == Game::XAssetType::ASSET_TYPE_GFXWORLD) @@ -945,37 +856,10 @@ namespace Components Utils::IO::WriteFile("userraw/logs/matlog.txt", buffer); } }); - - Dvar::OnInit([] - { - Dvar::Register("r_drawAabbTrees", false, Game::DVAR_FLAG_USERCREATED, "Draw aabb trees"); - }); - - Scheduler::OnFrame([]() - { - if (!Game::CL_IsCgameInitialized() || !Dvar::Var("r_drawAabbTrees").get()) return; - - float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; - float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; - - Game::clipMap_t* clipMap = *reinterpret_cast(0x7998E0); - //Game::GfxWorld* gameWorld = *reinterpret_cast(0x66DEE94); - if (!clipMap) return; - - for (unsigned short i = 0; i < clipMap->smodelNodeCount; ++i) - { - Game::R_AddDebugBounds(cyan, &clipMap->smodelNodes[i].bounds); - } - - for (unsigned int i = 0; i < clipMap->numStaticModels; i += 2) - { - Game::R_AddDebugBounds(red, &clipMap->staticModelList[i].absBounds); - } - }); - +#endif // Dvars - Dvar::Register("ui_streamFriendly", false, Game::DVAR_FLAG_SAVED, "Stream friendly UI"); + Dvar::Register("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI"); // Debug patches #ifdef DEBUG @@ -1017,11 +901,6 @@ namespace Components } } - QuickPatch::~QuickPatch() - { - - } - bool QuickPatch::unitTest() { uint32_t randIntCount = 4'000'000; diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index 37402355..6eac85a2 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -6,16 +6,12 @@ namespace Components { public: QuickPatch(); - ~QuickPatch(); bool unitTest() override; static void UnlockStats(); - static int GetFrameTime() { return FrameTime; } private: - static int FrameTime; - static void SelectStringTableEntryInDvarStub(); static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd); @@ -28,14 +24,11 @@ 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 Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* name, char** enumValues, int defaultVal, int flags, const char* description); + static Dvar::Var r_customAspectRatio; + static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** valueList, int defaultIndex, unsigned __int16 flags, const char* description); static void SetAspectRatioStub(); static void SetAspectRatio(); @@ -43,9 +36,11 @@ namespace Components static void ClientEventsFireWeaponStub(); static void ClientEventsFireWeaponMeleeStub(); - static Game::dvar_t* g_playerCollision; - static void PlayerCollisionStub(); - static Game::dvar_t* g_playerEjection; - static void PlayerEjectionStub(); + static BOOL IsDynClassnameStub(char* a1); + + static void CL_KeyEvent_OnEscape(); + static void CL_KeyEvent_ConsoleEscape_Stub(); + + static Game::dvar_t* Dvar_RegisterUIBuildLocation(const char* dvarName, float x, float y, float min, float max, int flags, const char* description); }; } diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index a1597a0f..00606dcf 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -7,27 +7,35 @@ namespace Components std::string RCon::Password; + Dvar::Var RCon::RconPassword; + Dvar::Var RCon::RconLogRequests; + RCon::RCon() { Command::Add("rcon", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; - std::string operation = params->get(1); - if (operation == "login") + const auto* operation = params->get(1); + if (std::strcmp(operation, "login") == 0) { - if (params->length() < 3) return; + if (params->size() < 3) return; RCon::Password = params->get(2); } - else if (operation == "logout") + else if (std::strcmp(operation, "logout") == 0) { RCon::Password.clear(); } else { - if (!RCon::Password.empty() && *reinterpret_cast(0xB2C540) >= 5) // Get our state + auto addr = reinterpret_cast(0xA5EA44); + if (!RCon::Password.empty()) { - Network::Address target(reinterpret_cast(0xA5EA44)); + Network::Address target(addr); + if (!target.isValid() || target.getIP().full == 0) + { + target = Party::Target(); + } if (target.isValid()) { @@ -70,7 +78,8 @@ namespace Components Dvar::OnInit([]() { - Dvar::Register("rcon_password", "", Game::dvar_flag::DVAR_FLAG_NONE, "The password for rcon"); + RCon::RconPassword = Dvar::Register("rcon_password", "", Game::dvar_flag::DVAR_NONE, "The password for rcon"); + RCon::RconLogRequests = Dvar::Register("rcon_log_requests", false, Game::dvar_flag::DVAR_NONE, "Print remote commands in the output log"); }); Network::Handle("rcon", [](Network::Address address, const std::string& _data) @@ -94,7 +103,7 @@ namespace Components password.erase(password.begin()); } - std::string svPassword = Dvar::Var("rcon_password").get(); + const std::string svPassword = RCon::RconPassword.get(); if (svPassword.empty()) { @@ -107,9 +116,12 @@ namespace Components static std::string outputBuffer; outputBuffer.clear(); -#ifdef DEBUG - Logger::Print("Executing RCon request from %s: %s\n", address.getCString(), command.data()); +#ifndef DEBUG + if (RCon::RconLogRequests.get()) #endif + { + Logger::Print("Executing RCon request from %s: %s\n", address.getCString(), command.data()); + } Logger::PipeOutput([](const std::string& output) { @@ -164,9 +176,4 @@ namespace Components } }); } - - RCon::~RCon() - { - RCon::Password.clear(); - } } diff --git a/src/Components/Modules/RCon.hpp b/src/Components/Modules/RCon.hpp index 1cc486e5..0c0282c6 100644 --- a/src/Components/Modules/RCon.hpp +++ b/src/Components/Modules/RCon.hpp @@ -6,7 +6,6 @@ namespace Components { public: RCon(); - ~RCon(); private: class Container @@ -25,5 +24,8 @@ namespace Components // For sr0's fucking rcon command // Son of a bitch! Annoying me day and night with that shit... static std::string Password; + + static Dvar::Var RconPassword; + static Dvar::Var RconLogRequests; }; } diff --git a/src/Components/Modules/RawFiles.cpp b/src/Components/Modules/RawFiles.cpp index c6aa9115..108da3e0 100644 --- a/src/Components/Modules/RawFiles.cpp +++ b/src/Components/Modules/RawFiles.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -24,7 +24,7 @@ namespace Components Command::Add("dumpraw", [](Command::Params* params) { - if (params->length() < 2) + if (params->size() < 2) { Logger::Print("Specify a filename!\n"); return; diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index c5379905..efa91e94 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -8,6 +8,30 @@ namespace Components Utils::Signal Renderer::EndRecoverDeviceSignal; Utils::Signal Renderer::BeginRecoverDeviceSignal; + Dvar::Var Renderer::r_drawTriggers; + Dvar::Var Renderer::r_drawSceneModelCollisions; + Dvar::Var Renderer::r_drawModelBoundingBoxes; + Dvar::Var Renderer::r_drawModelNames; + Dvar::Var Renderer::r_drawAABBTrees; + Dvar::Var Renderer::r_playerDrawDebugDistance; + + float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; + float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; + float green[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; + + // R_draw model names & collisions colors + float sceneModelsColor[4] = { 1.0f, 1.0f, 0.0f, 1.0f }; + float dobjsColor[4] = { 0.0f, 1.0f, 1.0f, 1.0f }; + float staticModelsColor[4] = { 1.0f, 0.0f, 1.0f, 1.0f }; + float gentitiesColor[4] = { 1.0f, 0.5f, 0.5f, 1.0f }; + + // Trigger colors + float hurt[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; + float hurtTouch[4] = { 0.75f, 0.0f, 0.0f, 1.0f }; + float damage[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; + float once[4] = { 0.0f, 1.0f, 1.0f, 1.0f }; + float multiple[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; + __declspec(naked) void Renderer::FrameStub() { __asm @@ -108,7 +132,7 @@ namespace Components void Renderer::R_TextureFromCodeError(const char* sampler, Game::GfxCmdBufState* state) { - Game::Com_Error(0, "Tried to use '%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); } @@ -166,39 +190,319 @@ namespace Components return Utils::Hook::Call(0x005033E0)(a1, a2, a3, Utils::String::VA("%s (^3%s^7)", mat->info.name, mat->techniqueSet->name), color, a6); } + void Renderer::DebugDrawTriggers() + { + if (!r_drawTriggers.get()) return; + + auto entities = Game::g_entities; + + for (auto i = 0u; i < 0x800u; i++) + { + auto* ent = &entities[i]; + + if (ent->r.isInUse) + { + Game::Bounds b = ent->r.box; + b.midPoint[0] += ent->r.currentOrigin[0]; + b.midPoint[1] += ent->r.currentOrigin[1]; + b.midPoint[2] += ent->r.currentOrigin[2]; + + switch (ent->handler) + { + case Game::ENT_HANDLER_TRIGGER_HURT: + Game::R_AddDebugBounds(hurt, &b); + break; + + case Game::ENT_HANDLER_TRIGGER_HURT_TOUCH: + Game::R_AddDebugBounds(hurtTouch, &b); + break; + + case Game::ENT_HANDLER_TRIGGER_DAMAGE: + Game::R_AddDebugBounds(damage, &b); + break; + + case Game::ENT_HANDLER_TRIGGER_MULTIPLE: + if (ent->spawnflags & 0x40) + Game::R_AddDebugBounds(once, &b); + else + Game::R_AddDebugBounds(multiple, &b); + break; + + default: + auto rv = std::min(static_cast(ent->handler), 5.0f) / 5.0f; + auto gv = std::clamp(static_cast(ent->handler - 5), 0.f, 5.0f) / 5.0f; + auto bv = std::clamp(static_cast(ent->handler - 10), 0.f, 5.0f) / 5.0f; + + float color[4] = { rv, gv, bv, 1.0f }; + + Game::R_AddDebugBounds(color, &b); + break; + } + } + } + } + + void Renderer::DebugDrawSceneModelCollisions() + { + if (!r_drawSceneModelCollisions.get()) return; + + auto scene = Game::scene; + + for (auto i = 0; i < scene->sceneModelCount; i++) + { + if (!scene->sceneModel[i].model) + continue; + + for (auto j = 0; j < scene->sceneModel[i].model->numCollSurfs; j++) + { + auto b = scene->sceneModel[i].model->collSurfs[j].bounds; + b.midPoint[0] += scene->sceneModel[i].placement.base.origin[0]; + b.midPoint[1] += scene->sceneModel[i].placement.base.origin[1]; + b.midPoint[2] += scene->sceneModel[i].placement.base.origin[2]; + b.halfSize[0] *= scene->sceneModel[i].placement.scale; + b.halfSize[1] *= scene->sceneModel[i].placement.scale; + b.halfSize[2] *= scene->sceneModel[i].placement.scale; + + Game::R_AddDebugBounds(green, &b, &scene->sceneModel[i].placement.base.quat); + } + } + } + + void Renderer::DebugDrawModelBoundingBoxes() + { + auto val = r_drawModelBoundingBoxes.get(); + + if (!val) return; + + // Ingame only + int clientNum = Game::CG_GetClientNum(); + if (!Game::CL_IsCgameInitialized() || + clientNum >= 18 || + clientNum < 0 || + Game::g_entities[clientNum].client == nullptr) { + + return; + } + + Game::gentity_t* clientEntity = &Game::g_entities[clientNum]; + + float playerPosition[3]{ clientEntity->r.currentOrigin[0], clientEntity->r.currentOrigin[1], clientEntity->r.currentOrigin[2] }; + + const auto mapName = Dvar::Var("mapname").get(); + auto scene = Game::scene; + auto world = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_GFXWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", mapName))->asset.header.gfxWorld; + + auto drawDistance = r_playerDrawDebugDistance.get(); + auto sqrDist = drawDistance * drawDistance; + + switch (val) + { + case 1: + for (auto i = 0; i < scene->sceneModelCount; i++) + { + if (!scene->sceneModel[i].model) + continue; + + if (Utils::Vec3SqrDistance(playerPosition, scene->sceneModel[i].placement.base.origin) < sqrDist) + { + auto b = scene->sceneModel[i].model->bounds; + b.midPoint[0] += scene->sceneModel[i].placement.base.origin[0]; + b.midPoint[1] += scene->sceneModel[i].placement.base.origin[1]; + b.midPoint[2] += scene->sceneModel[i].placement.base.origin[2]; + b.halfSize[0] *= scene->sceneModel[i].placement.scale; + b.halfSize[1] *= scene->sceneModel[i].placement.scale; + b.halfSize[2] *= scene->sceneModel[i].placement.scale; + Game::R_AddDebugBounds(sceneModelsColor, &b, &scene->sceneModel[i].placement.base.quat); + } + } + break; + + case 2: + for (auto i = 0; i < scene->sceneDObjCount; i++) + { + + if (Utils::Vec3SqrDistance(playerPosition, scene->sceneDObj[i].cull.bounds.midPoint) < sqrDist) + { + scene->sceneDObj[i].cull.bounds.halfSize[0] = std::abs(scene->sceneDObj[i].cull.bounds.halfSize[0]); + scene->sceneDObj[i].cull.bounds.halfSize[1] = std::abs(scene->sceneDObj[i].cull.bounds.halfSize[1]); + scene->sceneDObj[i].cull.bounds.halfSize[2] = std::abs(scene->sceneDObj[i].cull.bounds.halfSize[2]); + + if (scene->sceneDObj[i].cull.bounds.halfSize[0] < 0 || + scene->sceneDObj[i].cull.bounds.halfSize[1] < 0 || + scene->sceneDObj[i].cull.bounds.halfSize[2] < 0) + { + + Components::Logger::Print("WARNING: Negative half size for DOBJ %s, this will cause culling issues!", scene->sceneDObj[i].obj->models[0]->name); + } + + Game::R_AddDebugBounds(dobjsColor, &scene->sceneDObj[i].cull.bounds); + } + } + break; + + case 3: + // Static models + for (size_t i = 0; i < world->dpvs.smodelCount; i++) + { + auto staticModel = world->dpvs.smodelDrawInsts[i]; + + if (Utils::Vec3SqrDistance(playerPosition, staticModel.placement.origin) < sqrDist) + { + if (staticModel.model) + { + Game::Bounds b = staticModel.model->bounds; + b.midPoint[0] += staticModel.placement.origin[0]; + b.midPoint[1] += staticModel.placement.origin[1]; + b.midPoint[2] += staticModel.placement.origin[2]; + b.halfSize[0] *= staticModel.placement.scale; + b.halfSize[1] *= staticModel.placement.scale; + b.halfSize[2] *= staticModel.placement.scale; + + Game::R_AddDebugBounds(staticModelsColor, &b); + } + } + } + break; + } + } + + void Renderer::DebugDrawModelNames() + { + auto val = r_drawModelNames.get(); + + if (!val) return; + + // Ingame only + int clientNum = Game::CG_GetClientNum(); + if (!Game::CL_IsCgameInitialized() || + clientNum >= 18 || + clientNum < 0 || + Game::g_entities[clientNum].client == nullptr) { + + return; + } + + Game::gentity_t* clientEntity = &Game::g_entities[clientNum]; + + float playerPosition[3]{ clientEntity->r.currentOrigin[0], clientEntity->r.currentOrigin[1], clientEntity->r.currentOrigin[2] }; + + const auto mapName = Dvar::Var("mapname").get(); + auto scene = Game::scene; + auto world = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_GFXWORLD, Utils::String::VA("maps/mp/%s.d3dbsp", mapName))->asset.header.gfxWorld; + + auto drawDistance = r_playerDrawDebugDistance.get(); + auto sqrDist = drawDistance * drawDistance; + + switch (val) { + case 1: + for (auto i = 0; i < scene->sceneModelCount; i++) + { + if (!scene->sceneModel[i].model) + continue; + + if (Utils::Vec3SqrDistance(playerPosition, scene->sceneModel[i].placement.base.origin) < sqrDist) + { + Game::R_AddDebugString(sceneModelsColor, scene->sceneModel[i].placement.base.origin, 1.0, scene->sceneModel[i].model->name); + } + } + break; + + case 2: + for (auto i = 0; i < scene->sceneDObjCount; i++) + { + if (scene->sceneDObj[i].obj) + { + for (int j = 0; j < scene->sceneDObj[i].obj->numModels; j++) + { + if (Utils::Vec3SqrDistance(playerPosition, scene->sceneDObj[i].placement.origin) < sqrDist) + { + Game::R_AddDebugString(dobjsColor, scene->sceneDObj[i].placement.origin, 1.0, scene->sceneDObj[i].obj->models[j]->name); + } + } + } + } + break; + + case 3: + // Static models + for (size_t i = 0; i < world->dpvs.smodelCount; i++) + { + auto staticModel = world->dpvs.smodelDrawInsts[i]; + if (staticModel.model) + { + auto dist = Utils::Vec3SqrDistance(playerPosition, staticModel.placement.origin); + if (dist < sqrDist) + { + Game::R_AddDebugString(staticModelsColor, staticModel.placement.origin, 1.0, staticModel.model->name); + } + } + } + break; + } + } + + void Renderer::DebugDrawAABBTrees() + { + if (!r_drawAABBTrees.get()) return; + + Game::clipMap_t* clipMap = *reinterpret_cast(0x7998E0); + if (!clipMap) return; + + for (unsigned short i = 0; i < clipMap->smodelNodeCount; ++i) + { + Game::R_AddDebugBounds(cyan, &clipMap->smodelNodes[i].bounds); + } + + for (unsigned int i = 0; i < clipMap->numStaticModels; i += 2) + { + Game::R_AddDebugBounds(red, &clipMap->staticModelList[i].absBounds); + } + } + Renderer::Renderer() { if (Dedicated::IsEnabled()) return; -// Renderer::OnBackendFrame([] (IDirect3DDevice9* device) -// { -// if (Game::Sys_Milliseconds() % 2) -// { -// device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 0, 0); -// } -// -// return; -// -// IDirect3DSurface9* buffer = nullptr; -// -// device->CreateOffscreenPlainSurface(Renderer::Width(), Renderer::Height(), D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &buffer, nullptr); -// device->GetFrontBufferData(0, buffer); -// -// if (buffer) -// { -// D3DSURFACE_DESC desc; -// D3DLOCKED_RECT lockedRect; -// -// buffer->GetDesc(&desc); -// -// HRESULT res = buffer->LockRect(&lockedRect, NULL, D3DLOCK_READONLY); -// -// -// buffer->UnlockRect(); -// } -// }); + Scheduler::OnFrame([]() { + if (Game::CL_IsCgameInitialized()) + { + DebugDrawAABBTrees(); + DebugDrawModelNames(); + DebugDrawModelBoundingBoxes(); + DebugDrawSceneModelCollisions(); + DebugDrawTriggers(); + } + }); - // Log broken materials + // Renderer::OnBackendFrame([] (IDirect3DDevice9* device) + // { + // if (Game::Sys_Milliseconds() % 2) + // { + // device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 0, 0); + // } + // + // return; + // + // IDirect3DSurface9* buffer = nullptr; + // + // device->CreateOffscreenPlainSurface(Renderer::Width(), Renderer::Height(), D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &buffer, nullptr); + // device->GetFrontBufferData(0, buffer); + // + // if (buffer) + // { + // D3DSURFACE_DESC desc; + // D3DLOCKED_RECT lockedRect; + // + // buffer->GetDesc(&desc); + // + // HRESULT res = buffer->LockRect(&lockedRect, NULL, D3DLOCK_READONLY); + // + // + // buffer->UnlockRect(); + // } + // }); + + // Log broken materials Utils::Hook(0x0054CAAA, Renderer::StoreGfxBufContextPtrStub1, HOOK_JUMP).install()->quick(); Utils::Hook(0x0054CF8D, Renderer::StoreGfxBufContextPtrStub2, HOOK_JUMP).install()->quick(); @@ -230,6 +534,25 @@ namespace Components // End vid_restart Utils::Hook(0x4CA3A7, Renderer::PostVidRestartStub, HOOK_CALL).install()->quick(); + + Dvar::OnInit([] + { + static const char* values[] = + { + "Disabled", + "Scene Models", + "Scene Dynamic Objects", + "GfxWorld Static Models", + nullptr + }; + + Renderer::r_drawModelBoundingBoxes = Game::Dvar_RegisterEnum("r_drawModelBoundingBoxes", values, 0, Game::DVAR_CHEAT, "Draw scene model bounding boxes"); + Renderer::r_drawSceneModelCollisions = Game::Dvar_RegisterBool("r_drawSceneModelCollisions", false, Game::DVAR_CHEAT, "Draw scene model collisions"); + Renderer::r_drawTriggers = Game::Dvar_RegisterBool("r_drawTriggers", false, Game::DVAR_CHEAT, "Draw triggers"); + Renderer::r_drawModelNames = Game::Dvar_RegisterEnum("r_drawModelNames", values, 0, Game::DVAR_CHEAT, "Draw all model names"); + Renderer::r_drawAABBTrees = Game::Dvar_RegisterBool("r_drawAabbTrees", false, Game::DVAR_CHEAT, "Draw aabb trees"); + Renderer::r_playerDrawDebugDistance = Game::Dvar_RegisterInt("r_drawDebugDistance", 1000, 0, 50000, Game::DVAR_ARCHIVE, "r_draw debug functions draw distance, relative to the player"); + }); } Renderer::~Renderer() diff --git a/src/Components/Modules/Renderer.hpp b/src/Components/Modules/Renderer.hpp index 040eb6ba..553dd336 100644 --- a/src/Components/Modules/Renderer.hpp +++ b/src/Components/Modules/Renderer.hpp @@ -35,10 +35,23 @@ namespace Components static int DrawTechsetForMaterial(int a1, float a2, float a3, const char* material, Game::vec4_t* color, int a6); + static void DebugDrawTriggers(); + static void DebugDrawSceneModelCollisions(); + static void DebugDrawModelBoundingBoxes(); + static void DebugDrawModelNames(); + static void DebugDrawAABBTrees(); + static Utils::Signal EndRecoverDeviceSignal; static Utils::Signal BeginRecoverDeviceSignal; static Utils::Signal BackendFrameSignal; static Utils::Signal SingleBackendFrameSignal; + + static Dvar::Var r_drawTriggers; + static Dvar::Var r_drawSceneModelCollisions; + static Dvar::Var r_drawModelBoundingBoxes; + static Dvar::Var r_drawModelNames; + static Dvar::Var r_drawAABBTrees; + static Dvar::Var r_playerDrawDebugDistance; }; } diff --git a/src/Components/Modules/Scheduler.cpp b/src/Components/Modules/Scheduler.cpp index 51c4f4e1..22b412c3 100644 --- a/src/Components/Modules/Scheduler.cpp +++ b/src/Components/Modules/Scheduler.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 51f0e394..5dc331a3 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -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 { @@ -352,11 +354,9 @@ namespace Components { // execute our hook pushad - pusha call Script::StoreScriptBaseProgramNum - popa popad // execute overwritten code caused by the jump hook @@ -384,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]; @@ -409,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->number]; + 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() { @@ -436,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; @@ -450,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; @@ -465,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; @@ -479,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; @@ -498,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; @@ -518,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; @@ -548,7 +650,7 @@ namespace Components { int developer = Dvar::Var("developer").get(); - if (developer > 0) + if (developer > 0 && Dedicated::IsEnabled()) Utils::Hook::Set(0x48D8C7, 0x75); }); @@ -564,6 +666,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(); @@ -592,42 +697,14 @@ 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() { - Script::ScriptName.clear(); - Script::ScriptHandles.clear(); - Script::ScriptNameStack.clear(); - Script::ScriptFunctions.clear(); Script::VMShutdownSignal.clear(); - - Script::ScriptStorage.clear(); - Script::ScriptBaseProgramNum.clear(); } } diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index dd429cde..123c4d6b 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -1,5 +1,5 @@ #pragma once -#include "Game/Structs.hpp" +#include namespace Components { @@ -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/ServerCommands.cpp b/src/Components/Modules/ServerCommands.cpp index 3700527c..7e54f8a8 100644 --- a/src/Components/Modules/ServerCommands.cpp +++ b/src/Components/Modules/ServerCommands.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -11,11 +11,11 @@ namespace Components bool ServerCommands::OnServerCommand() { - Command::ClientParams params(*Game::cmd_id); + Command::ClientParams params; - for (auto &serverCommandCB : ServerCommands::Commands) + for (const auto& serverCommandCB : ServerCommands::Commands) { - if (params.length() >= 1) + if (params.size() >= 1) { if (params.get(0)[0] == serverCommandCB.first) { @@ -65,9 +65,4 @@ namespace Components // Server command receive hook Utils::Hook(0x59449F, ServerCommands::OnServerCommandStub).install()->quick(); } - - ServerCommands::~ServerCommands() - { - ServerCommands::Commands.clear(); - } } diff --git a/src/Components/Modules/ServerCommands.hpp b/src/Components/Modules/ServerCommands.hpp index 8fc22459..61f823a6 100644 --- a/src/Components/Modules/ServerCommands.hpp +++ b/src/Components/Modules/ServerCommands.hpp @@ -6,7 +6,6 @@ namespace Components { public: ServerCommands(); - ~ServerCommands(); static void OnCommand(std::int32_t cmd, Utils::Slot cb); diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index c5b8ffe9..69df260b 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -71,10 +71,10 @@ namespace Components } } - void ServerInfo::DrawScoreboardInfo(void* a1) + void ServerInfo::DrawScoreboardInfo(int localClientNum) { Game::Font_s* font = Game::R_RegisterFont("fonts/bigfont", 0); - void* cxt = Game::UI_GetContext(a1); + void* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); std::string addressText = Network::Address(*Game::connectedHost).getString(); if (addressText == "0.0.0.0:0" || addressText == "loopback") addressText = "Listen Server"; @@ -264,7 +264,7 @@ namespace Components Dvar::Var("uiSi_ModName").set(info.get("fs_game").data() + 5); } - auto lines = Utils::String::Explode(data, '\n'); + auto lines = Utils::String::Split(data, '\n'); if (lines.size() <= 1) return; diff --git a/src/Components/Modules/ServerInfo.hpp b/src/Components/Modules/ServerInfo.hpp index 8fceb827..36dd8cf7 100644 --- a/src/Components/Modules/ServerInfo.hpp +++ b/src/Components/Modules/ServerInfo.hpp @@ -36,7 +36,7 @@ namespace Components static const char* GetPlayerText(unsigned int index, int column); static void SelectPlayer(unsigned int index); - static void DrawScoreboardInfo(void* a1); + static void DrawScoreboardInfo(int localClientNum); static void DrawScoreboardStub(); }; } diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 8ffd4861..a4e61251 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -469,9 +469,19 @@ namespace Components server.ping = (Game::Sys_Milliseconds() - i->sendTime); server.addr = address; + server.hostname = TextRenderer::StripMaterialTextIcons(server.hostname); + server.mapname = TextRenderer::StripMaterialTextIcons(server.mapname); + server.gametype = TextRenderer::StripMaterialTextIcons(server.gametype); + server.mod = TextRenderer::StripMaterialTextIcons(server.mod); + // Remove server from queue i = ServerList::RefreshContainer.servers.erase(i); + // Servers with more than 18 players or less than 0 players are faking for sure + // So lets ignore those + if (server.clients > 18 || server.maxClients > 18 || server.clients < 0 || server.maxClients < 0) + return; + // Check if already inserted and remove auto list = ServerList::GetList(); if (!list) return; @@ -529,8 +539,8 @@ namespace Components bool ServerList::CompareVersion(const std::string& version1, const std::string& version2) { - std::vector subVersions1 = Utils::String::Explode(version1, '.'); - std::vector subVersions2 = Utils::String::Explode(version2, '.'); + auto subVersions1 = Utils::String::Split(version1, '.'); + auto subVersions2 = Utils::String::Split(version2, '.'); while (subVersions1.size() >= 3) subVersions1.pop_back(); while (subVersions2.size() >= 3) subVersions2.pop_back(); @@ -582,8 +592,8 @@ namespace Components return info1->clients < info2->clients; } - std::string text1 = Utils::String::ToLower(Colors::Strip(ServerList::GetServerInfoText(info1, ServerList::SortKey, true))); - std::string text2 = Utils::String::ToLower(Colors::Strip(ServerList::GetServerInfoText(info2, ServerList::SortKey, true))); + std::string text1 = Utils::String::ToLower(TextRenderer::StripColors(ServerList::GetServerInfoText(info1, ServerList::SortKey, true))); + std::string text2 = Utils::String::ToLower(TextRenderer::StripColors(ServerList::GetServerInfoText(info2, ServerList::SortKey, true))); // ASCII-based comparison return text1.compare(text2) < 0; @@ -724,16 +734,16 @@ namespace Components Dvar::OnInit([]() { - Dvar::Register("ui_serverSelected", false, Game::dvar_flag::DVAR_FLAG_NONE, "Whether a server has been selected in the serverlist"); - Dvar::Register("ui_serverSelectedMap", "mp_afghan", Game::dvar_flag::DVAR_FLAG_NONE, "Map of the selected server"); + Dvar::Register("ui_serverSelected", false, Game::dvar_flag::DVAR_NONE, "Whether a server has been selected in the serverlist"); + Dvar::Register("ui_serverSelectedMap", "mp_afghan", Game::dvar_flag::DVAR_NONE, "Map of the selected server"); - Dvar::Register("net_serverQueryLimit", 1, 1, 10, Dedicated::IsEnabled() ? 0 : Game::dvar_flag::DVAR_FLAG_SAVED, "Amount of server queries per frame"); - Dvar::Register("net_serverFrames", 30, 1, 60, Dedicated::IsEnabled() ? 0 : Game::dvar_flag::DVAR_FLAG_SAVED, "Amount of server query frames per second"); + Dvar::Register("net_serverQueryLimit", 1, 1, 10, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server queries per frame"); + Dvar::Register("net_serverFrames", 30, 1, 60, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server query frames per second"); }); // Fix ui_netsource dvar Utils::Hook::Nop(0x4CDEEC, 5); // Don't reset the netsource when gametypes aren't loaded - Dvar::Register("ui_netSource", 1, 0, 2, Game::DVAR_FLAG_SAVED, reinterpret_cast(0x6D9F08)); + Dvar::Register("ui_netSource", 1, 0, 2, Game::DVAR_ARCHIVE, reinterpret_cast(0x6D9F08)); //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); Localization::Set("MPUI_SERVERQUERIED", "Servers: 0\nPlayers: 0 (0)"); @@ -772,8 +782,8 @@ namespace Components // Set default masterServerName + port and save it #ifdef USE_LEGACY_SERVER_LIST Utils::Hook::Set(0x60AD92, "127.0.0.1"); - Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_FLAG_SAVED); // masterServerName - Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_FLAG_SAVED); // masterPort + Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_ARCHIVE); // masterServerName + Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_ARCHIVE); // masterPort #endif // Add server list feeder @@ -870,15 +880,8 @@ namespace Components ServerList::~ServerList() { - ServerList::OnlineList.clear(); - ServerList::OfflineList.clear(); - ServerList::FavouriteList.clear(); - ServerList::VisibleList.clear(); - - { - std::lock_guard _(ServerList::RefreshContainer.mutex); - ServerList::RefreshContainer.awatingList = false; - ServerList::RefreshContainer.servers.clear(); - } + std::lock_guard _(ServerList::RefreshContainer.mutex); + ServerList::RefreshContainer.awatingList = false; + ServerList::RefreshContainer.servers.clear(); } } diff --git a/src/Components/Modules/Session.cpp b/src/Components/Modules/Session.cpp index 842d236c..09efdd1f 100644 --- a/src/Components/Modules/Session.cpp +++ b/src/Components/Modules/Session.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Singleton.cpp b/src/Components/Modules/Singleton.cpp index 8ecae19d..da478a74 100644 --- a/src/Components/Modules/Singleton.cpp +++ b/src/Components/Modules/Singleton.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Slowmotion.cpp b/src/Components/Modules/SlowMotion.cpp similarity index 82% rename from src/Components/Modules/Slowmotion.cpp rename to src/Components/Modules/SlowMotion.cpp index 7a1bce89..268a2a0a 100644 --- a/src/Components/Modules/Slowmotion.cpp +++ b/src/Components/Modules/SlowMotion.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -21,11 +21,11 @@ namespace Components __asm { pushad - push [esp + 20h] + push [esp + 24h] call SlowMotion::ApplySlowMotion + add esp, 4h - pop ecx popad retn @@ -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); } @@ -76,10 +76,10 @@ namespace Components void SlowMotion::DrawConnectionInterruptedStub(int /*a1*/) { -// if (!*reinterpret_cast(0x1AD8ED0) && !*reinterpret_cast(0x1AD8EEC) && !*reinterpret_cast(0x1AD78F8)) -// { -// Utils::Hook::Call(0x454A70)(a1); -// } + // if (!*reinterpret_cast(0x1AD8ED0) && !*reinterpret_cast(0x1AD8EEC) && !*reinterpret_cast(0x1AD78F8)) + // { + // Utils::Hook::Call(0x454A70)(a1); + // } } SlowMotion::SlowMotion() diff --git a/src/Components/Modules/Slowmotion.hpp b/src/Components/Modules/SlowMotion.hpp similarity index 100% rename from src/Components/Modules/Slowmotion.hpp rename to src/Components/Modules/SlowMotion.hpp diff --git a/src/Components/Modules/SoundMutexFix.cpp b/src/Components/Modules/SoundMutexFix.cpp new file mode 100644 index 00000000..69662c7b --- /dev/null +++ b/src/Components/Modules/SoundMutexFix.cpp @@ -0,0 +1,39 @@ +#include + +namespace Components +{ + // This component is a workaround for issue https://github.com/XLabsProject/iw4x-client/issues/80 + // In case the link goes down, this is a "game hangs randomly" issue: + // + // Investigations on the issue pointed out it comes from a situation on Intel processors where + // WaitForSingleObjectA is ignored by a thread, for some (?) reason. + // + // This locks up the game randomly, mostly at the end of rounds or when too many things happen at + // once, due to trying to stop sounds (AIL_Stop_sounds) and playing streams at the same time, + // rushing for the same resource via AIL_lock_mutex. + // + // This bug has been reproduced on mp_terminal, mp_overgrown, mp_rust, with and without bots, + // and so far this has been the only way to circumvent it afaik. This component wraps + // miles' mutex into another mutex, created below, and for some reason (?) that mutex is + // respected when miles' is not. + // + // As soon as a real fix is found, please discard this fix. In the meantime, it should not + // have side effects too bad - worst case it might cause a slight performance drop during + // team switch and intermission. + // + + std::mutex SoundMutexFix::SNDMutex; + + void __stdcall SoundMutexFix::LockSoundMutex(int unk) + { + std::lock_guard lock(SoundMutexFix::SNDMutex); + + DWORD funcPtr = *reinterpret_cast(0x6D7554); // AIL_close_stream + Utils::Hook::Call(funcPtr)(unk); + } + + SoundMutexFix::SoundMutexFix() + { + Utils::Hook(0x689EFE, &SoundMutexFix::LockSoundMutex, HOOK_JUMP).install()->quick(); + } +} \ No newline at end of file diff --git a/src/Components/Modules/SoundMutexFix.hpp b/src/Components/Modules/SoundMutexFix.hpp new file mode 100644 index 00000000..ef62050f --- /dev/null +++ b/src/Components/Modules/SoundMutexFix.hpp @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace Components +{ + class SoundMutexFix : public Component + { + public: + SoundMutexFix(); + + private: + static std::mutex SNDMutex; + static void _stdcall LockSoundMutex(int unk); + }; +} diff --git a/src/Components/Modules/StartupMessages.cpp b/src/Components/Modules/StartupMessages.cpp index 55733f9a..8b9fbe8e 100644 --- a/src/Components/Modules/StartupMessages.cpp +++ b/src/Components/Modules/StartupMessages.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -9,9 +9,9 @@ namespace Components { Dvar::OnInit([]() { - Dvar::Register("ui_startupMessage", "", Game::DVAR_FLAG_USERCREATED | Game::DVAR_FLAG_WRITEPROTECTED, ""); - Dvar::Register("ui_startupMessageTitle", "", Game::DVAR_FLAG_USERCREATED | Game::DVAR_FLAG_WRITEPROTECTED, ""); - Dvar::Register("ui_startupNextButtonText", "", Game::DVAR_FLAG_USERCREATED | Game::DVAR_FLAG_WRITEPROTECTED, ""); + Dvar::Register("ui_startupMessage", "", Game::DVAR_EXTERNAL | Game::DVAR_WRITEPROTECTED, ""); + Dvar::Register("ui_startupMessageTitle", "", Game::DVAR_EXTERNAL | Game::DVAR_WRITEPROTECTED, ""); + Dvar::Register("ui_startupNextButtonText", "", Game::DVAR_EXTERNAL | Game::DVAR_WRITEPROTECTED, ""); }); UIScript::Add("nextStartupMessage", [](UIScript::Token) @@ -23,13 +23,14 @@ namespace Components StartupMessages::TotalMessages = StartupMessages::MessageList.size(); } - std::string message = StartupMessages::MessageList.front(); - StartupMessages::MessageList.pop_front(); + const auto& message = StartupMessages::MessageList.front(); Game::Dvar_SetStringByName("ui_startupMessage", message.data()); Game::Dvar_SetStringByName("ui_startupMessageTitle", Utils::String::VA("Messages (%d/%d)", StartupMessages::TotalMessages - StartupMessages::MessageList.size(), StartupMessages::TotalMessages)); Game::Dvar_SetStringByName("ui_startupNextButtonText", StartupMessages::MessageList.size() ? "Next" : "Close"); - Game::Cbuf_AddText(0, "openmenu startup_messages"); + Game::Cbuf_AddText(0, "openmenu startup_messages\n"); + + StartupMessages::MessageList.pop_front(); }); } diff --git a/src/Components/Modules/Stats.cpp b/src/Components/Modules/Stats.cpp index b2621760..dc807c37 100644 --- a/src/Components/Modules/Stats.cpp +++ b/src/Components/Modules/Stats.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -107,9 +107,4 @@ namespace Components // Write stats to mod folder if a mod is loaded Utils::Hook(0x682F7B, Stats::SaveStats, HOOK_CALL).install()->quick(); } - - Stats::~Stats() - { - - } } diff --git a/src/Components/Modules/Stats.hpp b/src/Components/Modules/Stats.hpp index 6fa5be2c..c4de9fd9 100644 --- a/src/Components/Modules/Stats.hpp +++ b/src/Components/Modules/Stats.hpp @@ -6,7 +6,6 @@ namespace Components { public: Stats(); - ~Stats(); static bool IsMaxLevel(); diff --git a/src/Components/Modules/StringTable.cpp b/src/Components/Modules/StringTable.cpp index 600e0340..8f1d590d 100644 --- a/src/Components/Modules/StringTable.cpp +++ b/src/Components/Modules/StringTable.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -76,9 +76,4 @@ namespace Components return header; }); } - - StringTable::~StringTable() - { - StringTable::StringTableMap.clear(); - } } diff --git a/src/Components/Modules/StringTable.hpp b/src/Components/Modules/StringTable.hpp index 21701b24..118cd076 100644 --- a/src/Components/Modules/StringTable.hpp +++ b/src/Components/Modules/StringTable.hpp @@ -6,7 +6,6 @@ namespace Components { public: StringTable(); - ~StringTable(); private: static std::unordered_map StringTableMap; diff --git a/src/Components/Modules/StructuredData.cpp b/src/Components/Modules/StructuredData.cpp index f620ed09..983ee2e7 100644 --- a/src/Components/Modules/StructuredData.cpp +++ b/src/Components/Modules/StructuredData.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -311,9 +311,4 @@ namespace Components } }); } - - StructuredData::~StructuredData() - { - StructuredData::MemAllocator.clear(); - } } diff --git a/src/Components/Modules/StructuredData.hpp b/src/Components/Modules/StructuredData.hpp index 8c7a6431..3a1b42f2 100644 --- a/src/Components/Modules/StructuredData.hpp +++ b/src/Components/Modules/StructuredData.hpp @@ -24,7 +24,6 @@ namespace Components }; StructuredData(); - ~StructuredData(); private: static bool UpdateVersionOffsets(Game::StructuredDataDefSet *set, Game::StructuredDataBuffer *buffer, Game::StructuredDataDef *oldDef); diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp new file mode 100644 index 00000000..2a11a676 --- /dev/null +++ b/src/Components/Modules/TextRenderer.cpp @@ -0,0 +1,1645 @@ +#include + +namespace Game +{ + float* con_screenMin = reinterpret_cast(0xA15F48); +} + +namespace Components +{ + unsigned TextRenderer::colorTableDefault[TEXT_COLOR_COUNT] + { + ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK + ColorRgb(255, 92, 92), // TEXT_COLOR_RED + ColorRgb(0, 255, 0), // TEXT_COLOR_GREEN + ColorRgb(255, 255, 0), // TEXT_COLOR_YELLOW + ColorRgb(0, 0, 255), // TEXT_COLOR_BLUE + ColorRgb(0, 255, 255), // TEXT_COLOR_LIGHT_BLUE + ColorRgb(255, 92, 255), // TEXT_COLOR_PINK + ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT + ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS + ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES + ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW + ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER + }; + + unsigned TextRenderer::colorTableNew[TEXT_COLOR_COUNT] + { + ColorRgb(0, 0, 0), // TEXT_COLOR_BLACK + ColorRgb(255, 49, 49), // TEXT_COLOR_RED + ColorRgb(134, 192, 0), // TEXT_COLOR_GREEN + ColorRgb(255, 173, 34), // TEXT_COLOR_YELLOW + ColorRgb(0, 135, 193), // TEXT_COLOR_BLUE + ColorRgb(32, 197, 255), // TEXT_COLOR_LIGHT_BLUE + ColorRgb(151, 80, 221), // TEXT_COLOR_PINK + ColorRgb(255, 255, 255), // TEXT_COLOR_DEFAULT + ColorRgb(255, 255, 255), // TEXT_COLOR_AXIS + ColorRgb(255, 255, 255), // TEXT_COLOR_ALLIES + ColorRgb(255, 255, 255), // TEXT_COLOR_RAINBOW + ColorRgb(255, 255, 255), // TEXT_COLOR_SERVER + }; + + unsigned(*TextRenderer::currentColorTable)[TEXT_COLOR_COUNT]; + TextRenderer::FontIconAutocompleteContext TextRenderer::autocompleteContextArray[FONT_ICON_ACI_COUNT]; + std::map TextRenderer::fontIconLookup; + std::vector TextRenderer::fontIconList; + + TextRenderer::BufferedLocalizedString TextRenderer::stringHintAutoComplete(REFERENCE_HINT_AUTO_COMPLETE, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringHintModifier(REFERENCE_HINT_MODIFIER, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListHeader(REFERENCE_MODIFIER_LIST_HEADER, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListFlipHorizontal(REFERENCE_MODIFIER_LIST_FLIP_HORIZONTAL, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListFlipVertical(REFERENCE_MODIFIER_LIST_FLIP_VERTICAL, STRING_BUFFER_SIZE_SMALL); + TextRenderer::BufferedLocalizedString TextRenderer::stringListBig(REFERENCE_MODIFIER_LIST_BIG, STRING_BUFFER_SIZE_SMALL); + + Dvar::Var TextRenderer::cg_newColors; + Dvar::Var TextRenderer::cg_fontIconAutocomplete; + Dvar::Var TextRenderer::cg_fontIconAutocompleteHint; + Game::dvar_t* TextRenderer::sv_customTextColor; + Dvar::Var TextRenderer::r_colorBlind; + Game::dvar_t* TextRenderer::g_ColorBlind_MyTeam; + Game::dvar_t* TextRenderer::g_ColorBlind_EnemyTeam; + Game::dvar_t** TextRenderer::con_inputBoxColor = reinterpret_cast(0x9FD4BC); + + TextRenderer::BufferedLocalizedString::BufferedLocalizedString(const char* reference, const size_t bufferSize) + : stringReference(reference), + stringBuffer(std::make_unique(bufferSize)), + stringBufferSize(bufferSize), + stringWidth{-1} + { + + } + + void TextRenderer::BufferedLocalizedString::Cache() + { + const auto* formattingString = Game::UI_SafeTranslateString(stringReference); + + if (formattingString != nullptr) + { + strncpy(stringBuffer.get(), formattingString, stringBufferSize); + for (auto& width : stringWidth) + width = -1; + } + } + + const char* TextRenderer::BufferedLocalizedString::Format(const char* value) + { + const auto* formattingString = Game::UI_SafeTranslateString(stringReference); + if (formattingString == nullptr) + { + stringBuffer[0] = '\0'; + return stringBuffer.get(); + } + + Game::ConversionArguments conversionArguments{}; + conversionArguments.args[conversionArguments.argCount++] = value; + Game::UI_ReplaceConversions(formattingString, &conversionArguments, stringBuffer.get(), stringBufferSize); + + for (auto& width : stringWidth) + width = -1; + return stringBuffer.get(); + } + + const char* TextRenderer::BufferedLocalizedString::GetString() const + { + return stringBuffer.get(); + } + + int TextRenderer::BufferedLocalizedString::GetWidth(const FontIconAutocompleteInstance autocompleteInstance, Game::Font_s* font) + { + assert(autocompleteInstance < FONT_ICON_ACI_COUNT); + if (stringWidth[autocompleteInstance] < 0) + stringWidth[autocompleteInstance] = Game::R_TextWidth(GetString(), std::numeric_limits::max(), font); + + return stringWidth[autocompleteInstance]; + } + + TextRenderer::FontIconAutocompleteContext::FontIconAutocompleteContext() + : autocompleteActive(false), + inModifiers(false), + userClosed(false), + lastHash(0u), + results{}, + resultCount(0u), + hasMoreResults(false), + resultOffset(0u), + lastResultOffset(0u), + selectedOffset(0u), + maxFontIconWidth(0.0f), + maxMaterialNameWidth(0.0f), + stringSearchStartWith(REFERENCE_SEARCH_START_WITH, STRING_BUFFER_SIZE_BIG) + { + + } + + unsigned TextRenderer::HsvToRgb(HsvColor hsv) + { + unsigned rgb; + unsigned char region, p, q, t; + unsigned int h, s, v, remainder; + + if (hsv.s == 0) + { + rgb = ColorRgb(hsv.v, hsv.v, hsv.v); + return rgb; + } + + // converting to 16 bit to prevent overflow + h = hsv.h; + s = hsv.s; + v = hsv.v; + + region = static_cast(h / 43); + remainder = (h - (region * 43)) * 6; + + p = static_cast((v * (255 - s)) >> 8); + q = static_cast((v * (255 - ((s * remainder) >> 8))) >> 8); + t = static_cast((v * (255 - ((s * (255 - remainder)) >> 8))) >> 8); + + switch (region) + { + case 0: + rgb = ColorRgb(static_cast(v), t, p); + break; + case 1: + rgb = ColorRgb(q, static_cast(v), p); + break; + case 2: + rgb = ColorRgb(p, static_cast(v), t); + break; + case 3: + rgb = ColorRgb(p, q, static_cast(v)); + break; + case 4: + rgb = ColorRgb(t, p, static_cast(v)); + break; + default: + rgb = ColorRgb(static_cast(v), p, q); + break; + } + + return rgb; + } + + void TextRenderer::DrawAutocompleteBox(const FontIconAutocompleteContext& context, const float x, const float y, const float w, const float h, const float* color) + { + static constexpr float colorWhite[4] + { + 1.0f, + 1.0f, + 1.0f, + 1.0f + }; + const float borderColor[4] + { + color[0] * 0.5f, + color[1] * 0.5f, + color[2] * 0.5f, + color[3] + }; + + Game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0, 0.0, 0.0, 0.0, color, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x, y, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER, y, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, h, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x, y, w, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + Game::R_AddCmdDrawStretchPic(x, y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER, w, FONT_ICON_AUTOCOMPLETE_BOX_BORDER, 0.0, 0.0, 0.0, 0.0, borderColor, Game::cls->whiteMaterial); + + if (context.resultOffset > 0) + { + Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + y + FONT_ICON_AUTOCOMPLETE_BOX_BORDER, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + 1.0f, 1.0f, 0.0f, 0.0f, colorWhite, Game::sharedUiInfo->assets.scrollBarArrowDown); + } + if(context.hasMoreResults) + { + Game::R_AddCmdDrawStretchPic(x + w - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + y + h - FONT_ICON_AUTOCOMPLETE_BOX_BORDER - FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + FONT_ICON_AUTOCOMPLETE_ARROW_SIZE, + 1.0f, 1.0f, 0.0f, 0.0f, colorWhite, Game::sharedUiInfo->assets.scrollBarArrowUp); + } + } + + void TextRenderer::UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font, const float textXScale) + { + context.resultCount = 0; + context.hasMoreResults = false; + context.lastResultOffset = context.resultOffset; + + auto skipCount = context.resultOffset; + + const auto queryLen = context.lastQuery.size(); + for(const auto& fontIconEntry : fontIconList) + { + const auto compareValue = fontIconEntry.iconName.compare(0, queryLen, context.lastQuery); + + if (compareValue == 0) + { + if (skipCount > 0) + { + skipCount--; + } + else if (context.resultCount < FontIconAutocompleteContext::MAX_RESULTS) + { + context.results[context.resultCount++] = { + Utils::String::VA(":%s:", fontIconEntry.iconName.data()), + fontIconEntry.iconName + }; + } + else + context.hasMoreResults = true; + } + else if (compareValue > 0) + break; + } + + context.maxFontIconWidth = 0; + context.maxMaterialNameWidth = 0; + for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) + { + const auto& result = context.results[resultIndex]; + const auto fontIconWidth = static_cast(Game::R_TextWidth(result.fontIconName.c_str(), std::numeric_limits::max(), font)) * textXScale; + const auto materialNameWidth = static_cast(Game::R_TextWidth(result.materialName.c_str(), std::numeric_limits::max(), font)) * textXScale; + + if (fontIconWidth > context.maxFontIconWidth) + context.maxFontIconWidth = fontIconWidth; + if (materialNameWidth > context.maxMaterialNameWidth) + context.maxMaterialNameWidth = materialNameWidth; + } + } + + void TextRenderer::UpdateAutocompleteContext(FontIconAutocompleteContext& context, const Game::field_t* edit, Game::Font_s* font, const float textXScale) + { + int fontIconStart = -1; + auto inModifiers = false; + + for(auto i = 0; i < edit->cursor; i++) + { + const auto c = static_cast(edit->buffer[i]); + if (c == FONT_ICON_SEPARATOR_CHARACTER) + { + if(fontIconStart < 0) + { + fontIconStart = i + 1; + inModifiers = false; + } + else + { + fontIconStart = -1; + inModifiers = false; + } + } + else if(isspace(c)) + { + fontIconStart = -1; + inModifiers = false; + } + else if(c == FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) + { + if (fontIconStart >= 0 && !inModifiers) + { + inModifiers = true; + } + else + { + fontIconStart = -1; + inModifiers = false; + } + } + } + + if(fontIconStart < 0 // Not in fonticon sequence + || fontIconStart == edit->cursor // Did not type the first letter yet + || !isalpha(static_cast(edit->buffer[fontIconStart])) // First letter of the icon is not alphabetic + || (fontIconStart > 1 && isalnum(static_cast(edit->buffer[fontIconStart - 2]))) // Letter before sequence is alnum + ) + { + context.autocompleteActive = false; + context.userClosed = false; + context.lastHash = 0; + context.resultCount = 0; + return; + } + + context.inModifiers = inModifiers; + + // Update scroll + if(context.selectedOffset < context.resultOffset) + context.resultOffset = context.selectedOffset; + else if(context.selectedOffset >= context.resultOffset + FontIconAutocompleteContext::MAX_RESULTS) + context.resultOffset = context.selectedOffset - (FontIconAutocompleteContext::MAX_RESULTS - 1); + + // If the user closed the context do not draw or update + if (context.userClosed) + return; + + context.autocompleteActive = true; + + // No need to update results when in modifiers + if (context.inModifiers) + return; + + // Check if results need updates + const auto currentFontIconHash = Game::R_HashString(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); + if (currentFontIconHash == context.lastHash && context.lastResultOffset == context.resultOffset) + return; + + // If query was updated then reset scroll parameters + if(currentFontIconHash != context.lastHash) + { + context.resultOffset = 0; + context.selectedOffset = 0; + context.lastHash = currentFontIconHash; + } + + // Update results for query and scroll and update search string + context.lastQuery = std::string(&edit->buffer[fontIconStart], edit->cursor - fontIconStart); + context.stringSearchStartWith.Format(context.lastQuery.c_str()); + UpdateAutocompleteContextResults(context, font, textXScale); + } + + void TextRenderer::DrawAutocompleteModifiers(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) + { + assert(instance < FONT_ICON_ACI_COUNT); + const auto& context = autocompleteContextArray[instance]; + + // Check which is the longest string to be able to calculate how big the box needs to be + const auto longestStringLength = std::max(std::max(std::max(stringListHeader.GetWidth(instance, font), stringListFlipHorizontal.GetWidth(instance, font)), + stringListFlipVertical.GetWidth(instance, font)), + stringListBig.GetWidth(instance, font)); + + // Draw background box + const auto boxWidth = static_cast(longestStringLength) * textXScale; + constexpr auto totalLines = 4u; + const auto lineHeight = static_cast(font->pixelHeight) * textYScale; + DrawAutocompleteBox(context, + x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + static_cast(totalLines) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + (*con_inputBoxColor)->current.vector); + + auto currentY = y + lineHeight; + + // Draw header line: "Following modifiers are available:" + Game::R_AddCmdDrawText(stringListHeader.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + + // Draw modifier hints + Game::R_AddCmdDrawText(stringListFlipHorizontal.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + Game::R_AddCmdDrawText(stringListFlipVertical.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + Game::R_AddCmdDrawText(stringListBig.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + } + + void TextRenderer::DrawAutocompleteResults(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) + { + assert(instance < FONT_ICON_ACI_COUNT); + auto& context = autocompleteContextArray[instance]; + + const auto hintEnabled = cg_fontIconAutocompleteHint.get(); + + // Check which is the longest string to be able to calculate how big the box needs to be + auto longestStringLength = context.stringSearchStartWith.GetWidth(instance, font); + if(hintEnabled) + longestStringLength = std::max(std::max(longestStringLength, stringHintAutoComplete.GetWidth(instance, font)), stringHintModifier.GetWidth(instance, font)); + + const auto colSpacing = FONT_ICON_AUTOCOMPLETE_COL_SPACING * textXScale; + const auto boxWidth = std::max(context.maxFontIconWidth + context.maxMaterialNameWidth + colSpacing, static_cast(longestStringLength) * textXScale); + const auto lineHeight = static_cast(font->pixelHeight) * textYScale; + + // Draw background box + const auto totalLines = 1u + context.resultCount + (hintEnabled ? 2u : 0u); + const auto arrowPadding = context.resultOffset > 0 || context.hasMoreResults ? FONT_ICON_AUTOCOMPLETE_ARROW_SIZE : 0.0f; + DrawAutocompleteBox(context, + x - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + y - FONT_ICON_AUTOCOMPLETE_BOX_PADDING, + boxWidth + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2 + arrowPadding, + static_cast(totalLines) * lineHeight + FONT_ICON_AUTOCOMPLETE_BOX_PADDING * 2, + (*con_inputBoxColor)->current.vector); + + // Draw header line "Search results for: xyz" + auto currentY = y + lineHeight; + Game::R_AddCmdDrawText(context.stringSearchStartWith.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + + // Draw search results + const auto selectedIndex = context.selectedOffset - context.resultOffset; + for(auto resultIndex = 0u; resultIndex < context.resultCount; resultIndex++) + { + const auto& result = context.results[resultIndex]; + Game::R_AddCmdDrawText(result.fontIconName.c_str(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + + if (selectedIndex == resultIndex) + Game::R_AddCmdDrawText(Utils::String::VA("^2%s", result.materialName.c_str()), std::numeric_limits::max(), font, x + context.maxFontIconWidth + colSpacing, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + else + Game::R_AddCmdDrawText(result.materialName.c_str(), std::numeric_limits::max(), font, x + context.maxFontIconWidth + colSpacing, currentY, textXScale, textYScale, 0.0, TEXT_COLOR, 0); + currentY += lineHeight; + } + + // Draw extra hint if enabled + if(hintEnabled) + { + Game::R_AddCmdDrawText(stringHintAutoComplete.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, HINT_COLOR, 0); + currentY += lineHeight; + Game::R_AddCmdDrawText(stringHintModifier.GetString(), std::numeric_limits::max(), font, x, currentY, textXScale, textYScale, 0.0, HINT_COLOR, 0); + } + } + + void TextRenderer::DrawAutocomplete(const FontIconAutocompleteInstance instance, const float x, const float y, Game::Font_s* font, const float textXScale, const float textYScale) + { + assert(instance < FONT_ICON_ACI_COUNT); + const auto& context = autocompleteContextArray[instance]; + + if (context.inModifiers) + DrawAutocompleteModifiers(instance, x, y, font, textXScale, textYScale); + else + DrawAutocompleteResults(instance, x, y, font, textXScale, textYScale); + } + + void TextRenderer::Con_DrawInput_Hk(const int localClientNum) + { + // Call original function + Utils::Hook::Call(0x5A4480)(localClientNum); + + auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CONSOLE]; + if (cg_fontIconAutocomplete.get() == false) + { + autocompleteContext.autocompleteActive = false; + return; + } + + UpdateAutocompleteContext(autocompleteContext, Game::g_consoleField, Game::cls->consoleFont, 1.0f); + if (autocompleteContext.autocompleteActive) + { + const auto x = Game::conDrawInputGlob->leftX; + const auto y = Game::con_screenMin[1] + 6.0f + static_cast(2 * Game::R_TextHeight(Game::cls->consoleFont)); + DrawAutocomplete(FONT_ICON_ACI_CONSOLE, x, y, Game::cls->consoleFont, 1.0f, 1.0f); + } + } + + void TextRenderer::Field_Draw_Say(const int localClientNum, Game::field_t* edit, const int x, const int y, const int horzAlign, const int vertAlign) + { + Game::Field_Draw(localClientNum, edit, x, y, horzAlign, vertAlign); + + auto& autocompleteContext = autocompleteContextArray[FONT_ICON_ACI_CHAT]; + if (cg_fontIconAutocomplete.get() == false) + { + autocompleteContext.autocompleteActive = false; + return; + } + + auto* screenPlacement = Game::ScrPlace_GetActivePlacement(localClientNum); + const auto scale = edit->charHeight / 48.0f; + auto* font = Game::UI_GetFontHandle(screenPlacement, 0, scale); + const auto normalizedScale = Game::R_NormalizedTextScale(font, scale); + auto xx = static_cast(x); + auto yy = static_cast(y); + yy += static_cast(Game::R_TextHeight(font)) * normalizedScale * 1.5f; + auto ww = normalizedScale; + auto hh = normalizedScale; + Game::ScrPlace_ApplyRect(screenPlacement, &xx, &yy, &ww, &hh, horzAlign, vertAlign); + + UpdateAutocompleteContext(autocompleteContext, edit, font, ww); + if (autocompleteContext.autocompleteActive) + { + DrawAutocomplete(FONT_ICON_ACI_CHAT, std::floor(xx), std::floor(yy), font, ww, hh); + } + } + + void TextRenderer::AutocompleteUp(FontIconAutocompleteContext& context) + { + if (context.selectedOffset > 0) + context.selectedOffset--; + } + + void TextRenderer::AutocompleteDown(FontIconAutocompleteContext& context) + { + if (context.resultCount < FontIconAutocompleteContext::MAX_RESULTS) + { + if (context.resultCount > 0 && context.selectedOffset < context.resultOffset + context.resultCount - 1) + context.selectedOffset++; + } + else if (context.selectedOffset == context.resultOffset + context.resultCount - 1) + { + if (context.hasMoreResults) + context.selectedOffset++; + } + else + { + context.selectedOffset++; + } + } + + void TextRenderer::AutocompleteFill(const FontIconAutocompleteContext& context, Game::ScreenPlacement* scrPlace, Game::field_t* edit, const bool closeFontIcon) + { + if (context.selectedOffset >= context.resultOffset + context.resultCount) + return; + + const auto selectedResultIndex = context.selectedOffset - context.resultOffset; + std::string remainingFillData = context.results[selectedResultIndex].materialName.substr(context.lastQuery.size()); + if (closeFontIcon) + remainingFillData += ":"; + const std::string moveData(&edit->buffer[edit->cursor]); + + const auto remainingBufferCharacters = std::extent_v - edit->cursor - moveData.size() - 1; + if(remainingFillData.size() > remainingBufferCharacters) + remainingFillData = remainingFillData.erase(remainingBufferCharacters); + + if(!remainingFillData.empty()) + { + strncpy(&edit->buffer[edit->cursor], remainingFillData.c_str(), remainingFillData.size()); + strncpy(&edit->buffer[edit->cursor + remainingFillData.size()], moveData.c_str(), moveData.size()); + edit->buffer[std::extent_v - 1] = '\0'; + edit->cursor += static_cast(remainingFillData.size()); + Game::Field_AdjustScroll(scrPlace, edit); + } + } + + bool TextRenderer::AutocompleteHandleKeyDown(FontIconAutocompleteContext& context, const int key, Game::ScreenPlacement* scrPlace, Game::field_t* edit) + { + switch (key) + { + case Game::K_UPARROW: + case Game::K_KP_UPARROW: + AutocompleteUp(context); + return true; + + case Game::K_DOWNARROW: + case Game::K_KP_DOWNARROW: + AutocompleteDown(context); + return true; + + case Game::K_ENTER: + case Game::K_KP_ENTER: + if(context.resultCount > 0) + { + AutocompleteFill(context, scrPlace, edit, true); + return true; + } + return false; + + case Game::K_TAB: + AutocompleteFill(context, scrPlace, edit, false); + return true; + + case Game::K_ESCAPE: + if (!context.userClosed) + { + context.autocompleteActive = false; + context.userClosed = true; + return true; + } + return false; + + default: + return false; + } + } + + bool TextRenderer::HandleFontIconAutocompleteKey(const int localClientNum, const FontIconAutocompleteInstance autocompleteInstance, const int key) + { + assert(autocompleteInstance < FONT_ICON_ACI_COUNT); + if (autocompleteInstance >= FONT_ICON_ACI_COUNT) + return false; + + auto& autocompleteContext = autocompleteContextArray[autocompleteInstance]; + if (!autocompleteContext.autocompleteActive) + return false; + + if(autocompleteInstance == FONT_ICON_ACI_CONSOLE) + return AutocompleteHandleKeyDown(autocompleteContext, key, Game::scrPlaceFull, Game::g_consoleField); + + if(autocompleteInstance == FONT_ICON_ACI_CHAT) + return AutocompleteHandleKeyDown(autocompleteContext, key, &Game::scrPlaceView[localClientNum], &Game::playerKeys[localClientNum].chatField); + + return false; + } + + void TextRenderer::Console_Key_Hk(const int localClientNum, const int key) + { + if (HandleFontIconAutocompleteKey(localClientNum, FONT_ICON_ACI_CONSOLE, key)) + return; + + Utils::Hook::Call(0x4311E0)(localClientNum, key); + } + + bool TextRenderer::ChatHandleKeyDown(const int localClientNum, const int key) + { + return HandleFontIconAutocompleteKey(localClientNum, FONT_ICON_ACI_CHAT, key); + } + + constexpr auto Message_Key = 0x5A7E50; + __declspec(naked) void TextRenderer::Message_Key_Stub() + { + __asm + { + pushad + + push eax + push edi + call ChatHandleKeyDown + add esp, 0x8 + test al,al + jnz skipHandling + + popad + call Message_Key + ret + + skipHandling: + popad + mov al, 1 + ret + } + } + + float TextRenderer::GetMonospaceWidth(Game::Font_s* font, int rendererFlags) + { + if(rendererFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE) + return Game::R_GetCharacterGlyph(font, 'o')->dx; + + return 0.0f; + } + + void TextRenderer::GlowColor(Game::GfxColor* result, const Game::GfxColor baseColor, const Game::GfxColor forcedGlowColor, int renderFlags) + { + if (renderFlags & Game::TEXT_RENDERFLAG_GLOW_FORCE_COLOR) + { + result->array[0] = forcedGlowColor.array[0]; + result->array[1] = forcedGlowColor.array[1]; + result->array[2] = forcedGlowColor.array[2]; + } + else + { + result->array[0] = static_cast(std::floor(static_cast(static_cast(baseColor.array[0])) * 0.06f)); + result->array[1] = static_cast(std::floor(static_cast(static_cast(baseColor.array[1])) * 0.06f)); + result->array[2] = static_cast(std::floor(static_cast(static_cast(baseColor.array[2])) * 0.06f)); + } + } + + unsigned TextRenderer::R_FontGetRandomLetter(const int seed) + { + static constexpr char RANDOM_CHARACTERS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; + return RANDOM_CHARACTERS[seed % (std::extent_v -1)]; + } + + void TextRenderer::DrawTextFxExtraCharacter(Game::Material* material, const int charIndex, const float x, const float y, const float w, const float h, const float sinAngle, const float cosAngle, const unsigned color) + { + Game::RB_DrawStretchPicRotate(material, x, y, w, h, static_cast(charIndex % 16) * 0.0625f, 0.0f, static_cast(charIndex % 16) * 0.0625f + 0.0625f, 1.0f, sinAngle, cosAngle, color); + } + + Game::GfxImage* TextRenderer::GetFontIconColorMap(const Game::Material* fontIconMaterial) + { + for (auto i = 0u; i < fontIconMaterial->textureCount; i++) + { + if (fontIconMaterial->textureTable[i].nameHash == COLOR_MAP_HASH) + return fontIconMaterial->textureTable[i].u.image; + } + + return nullptr; + } + + bool TextRenderer::IsFontIcon(const char*& text, FontIconInfo& fontIcon) + { + const auto* curPos = text; + + while (*curPos != ' ' && *curPos != FONT_ICON_SEPARATOR_CHARACTER && *curPos != 0 && *curPos != FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) + curPos++; + + const auto* nameEnd = curPos; + + if(*curPos == FONT_ICON_MODIFIER_SEPARATOR_CHARACTER) + { + auto breakArgs = false; + while(!breakArgs) + { + curPos++; + switch(*curPos) + { + case FONT_ICON_MODIFIER_FLIP_HORIZONTALLY: + fontIcon.flipHorizontal = true; + break; + + case FONT_ICON_MODIFIER_FLIP_VERTICALLY: + fontIcon.flipVertical = true; + break; + + case FONT_ICON_MODIFIER_BIG: + fontIcon.big = true; + break; + + case FONT_ICON_SEPARATOR_CHARACTER: + breakArgs = true; + break; + + default: + return false; + } + } + } + + if (*curPos != FONT_ICON_SEPARATOR_CHARACTER) + return false; + + const std::string fontIconName(text, nameEnd - text); + + const auto foundFontIcon = fontIconLookup.find(fontIconName); + if (foundFontIcon == fontIconLookup.end()) + return false; + + auto& entry = foundFontIcon->second; + if(entry.material == nullptr) + { + auto* materialEntry = Game::DB_FindXAssetEntry(Game::XAssetType::ASSET_TYPE_MATERIAL, entry.materialName.data()); + if (materialEntry == nullptr) + return false; + auto* material = materialEntry->asset.header.material; + if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr) + return false; + + if(strcmp(material->techniqueSet->name, "2d") != 0) + { + Logger::Print("^1Fonticon material '%s' does not have 2d techset!\n", material->info.name); + material = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "default").material; + } + + entry.material = material; + } + + text = curPos + 1; + fontIcon.material = entry.material; + return true; + } + + float TextRenderer::GetNormalizedFontIconWidth(const FontIconInfo& fontIcon) + { + const auto* colorMap = GetFontIconColorMap(fontIcon.material); + if (colorMap == nullptr) + return 0; + const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; + auto colWidth = static_cast(colorMap->width); + auto colHeight = static_cast(colorMap->height); + if (fontIcon.material->info.textureAtlasColumnCount > 1) + colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); + if (fontIcon.material->info.textureAtlasRowCount > 1) + colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); + return (colWidth / colHeight) * sizeMultiplier; + } + + float TextRenderer::GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, const float xScale) + { + const auto* colorMap = GetFontIconColorMap(fontIcon.material); + if (colorMap == nullptr) + return 0; + const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; + auto colWidth = static_cast(colorMap->width); + auto colHeight = static_cast(colorMap->height); + if (fontIcon.material->info.textureAtlasColumnCount > 1) + colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); + if (fontIcon.material->info.textureAtlasRowCount > 1) + colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); + return static_cast(font->pixelHeight) * (colWidth / colHeight) * xScale * sizeMultiplier; + } + + float TextRenderer::DrawFontIcon(const FontIconInfo& fontIcon, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) + { + const auto* colorMap = GetFontIconColorMap(fontIcon.material); + if (colorMap == nullptr) + return 0; + + float s0, t0, s1, t1; + if(fontIcon.flipHorizontal) + { + s0 = 1.0f; + s1 = 0.0f; + } + else + { + s0 = 0.0f; + s1 = 1.0f; + } + if(fontIcon.flipVertical) + { + t0 = 1.0f; + t1 = 0.0f; + } + else + { + t0 = 0.0f; + t1 = 1.0f; + } + Game::Material_Process2DTextureCoordsForAtlasing(fontIcon.material, &s0, &s1, &t0, &t1); + const auto sizeMultiplier = fontIcon.big ? 1.5f : 1.0f; + + auto colWidth = static_cast(colorMap->width); + auto colHeight = static_cast(colorMap->height); + if (fontIcon.material->info.textureAtlasColumnCount > 1) + colWidth /= static_cast(fontIcon.material->info.textureAtlasColumnCount); + if (fontIcon.material->info.textureAtlasRowCount > 1) + colHeight /= static_cast(fontIcon.material->info.textureAtlasRowCount); + + const auto h = static_cast(font->pixelHeight) * yScale * sizeMultiplier; + const auto w = static_cast(font->pixelHeight) * (colWidth / colHeight) * xScale * sizeMultiplier; + + const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; + Game::RB_DrawStretchPicRotate(fontIcon.material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); + + return w; + } + + float TextRenderer::DrawHudIcon(const char*& text, const float x, const float y, const float sinAngle, const float cosAngle, const Game::Font_s* font, const float xScale, const float yScale, const unsigned color) + { + float s0, s1, t0, t1; + + if(*text == '\x01') + { + s0 = 0.0; + t0 = 0.0; + s1 = 1.0; + t1 = 1.0; + } + else + { + s0 = 1.0; + t0 = 0.0; + s1 = 0.0; + t1 = 1.0; + } + text++; + + if (*text == 0) + return 0; + + const auto v12 = font->pixelHeight * (*text - 16) + 16; + const auto w = static_cast((((v12 >> 24) & 0x1F) + v12) >> 5) * xScale; + text++; + + if (*text == 0) + return 0; + + const auto h = static_cast((font->pixelHeight * (*text - 16) + 16) >> 5) * yScale; + text++; + + if (*text == 0) + return 0; + + const auto materialNameLen = static_cast(*text); + text++; + + for(auto i = 0u; i < materialNameLen; i++) + { + if (text[i] == 0) + return 0; + } + + const std::string materialName(text, materialNameLen); + text += materialNameLen; + + auto* material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, materialName.data()).material; + if (material == nullptr || material->techniqueSet == nullptr || material->techniqueSet->name == nullptr || strcmp(material->techniqueSet->name, "2d") != 0) + material = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MATERIAL, "default").material; + + const auto yy = y - (h + yScale * static_cast(font->pixelHeight)) * 0.5f; + + Game::RB_DrawStretchPicRotate(material, x, yy, w, h, s0, t0, s1, t1, sinAngle, cosAngle, color); + + return w; + } + + void TextRenderer::RotateXY(const float cosAngle, const float sinAngle, const float pivotX, const float pivotY, const float x, const float y, float* outX, float* outY) + { + *outX = (x - pivotX) * cosAngle + pivotX - (y - pivotY) * sinAngle; + *outY = (y - pivotY) * cosAngle + pivotY + (x - pivotX) * sinAngle; + } + + void TextRenderer::DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow) + { + UpdateColorTable(); + + Game::GfxColor dropShadowColor{0}; + dropShadowColor.array[3] = color.array[3]; + + int randSeed = 1; + bool drawRandomCharAtEnd = false; + const auto forceMonospace = renderFlags & Game::TEXT_RENDERFLAG_FORCEMONOSPACE; + const auto monospaceWidth = GetMonospaceWidth(font, renderFlags); + auto* material = font->material; + Game::Material* glowMaterial = nullptr; + + bool decaying; + int decayTimeElapsed; + if(renderFlags & Game::TEXT_RENDERFLAG_FX_DECODE) + { + if (!Game::SetupPulseFXVars(text, maxLength, fxBirthTime, fxLetterTime, fxDecayStartTime, fxDecayDuration, &drawRandomCharAtEnd, &randSeed, &maxLength, &decaying, &decayTimeElapsed)) + return; + } + else + { + drawRandomCharAtEnd = false; + randSeed = 1; + decaying = false; + decayTimeElapsed = 0; + } + + Game::FontPassType passes[Game::FONTPASS_COUNT]; + unsigned passCount = 0; + + if(renderFlags & Game::TEXT_RENDERFLAG_OUTLINE) + { + if(renderFlags & Game::TEXT_RENDERFLAG_GLOW) + { + glowMaterial = font->glowMaterial; + passes[passCount++] = Game::FONTPASS_GLOW; + } + + passes[passCount++] = Game::FONTPASS_OUTLINE; + passes[passCount++] = Game::FONTPASS_NORMAL; + } + else + { + passes[passCount++] = Game::FONTPASS_NORMAL; + + if (renderFlags & Game::TEXT_RENDERFLAG_GLOW) + { + glowMaterial = font->glowMaterial; + passes[passCount++] = Game::FONTPASS_GLOW; + } + } + + const auto startX = x - xScale * 0.5f; + const auto startY = y - 0.5f * yScale; + + for(auto passIndex = 0u; passIndex < passCount; passIndex++) + { + float xRot, yRot; + const char* curText = text; + auto maxLengthRemaining = maxLength; + auto currentColor = color; + auto subtitleAllowGlow = false; + auto extraFxChar = 0; + auto drawExtraFxChar = false; + auto passRandSeed = randSeed; + auto count = 0; + auto xa = startX; + auto xy = startY; + + while(*curText && maxLengthRemaining) + { + if (passes[passIndex] == Game::FONTPASS_NORMAL && renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); + } + + auto letter = Game::SEH_ReadCharFromString(&curText, nullptr); + + if(letter == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR) + { + const auto colorIndex = ColorIndexForChar(*curText); + subtitleAllowGlow = false; + if (colorIndex == TEXT_COLOR_DEFAULT) + { + currentColor = color; + } + else if (renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT && colorIndex == TEXT_COLOR_GREEN) + { + constexpr Game::GfxColor altColor{ MY_ALTCOLOR_TWO }; + subtitleAllowGlow = true; + // Swap r and b for whatever reason + currentColor.packed = ColorRgba(altColor.array[2], altColor.array[1], altColor.array[0], Game::ModulateByteColors(altColor.array[3], color.array[3])); + } + else + { + const Game::GfxColor colorTableColor{ (*currentColorTable)[colorIndex] }; + // Swap r and b for whatever reason + currentColor.packed = ColorRgba(colorTableColor.array[2], colorTableColor.array[1], colorTableColor.array[0], color.array[3]); + } + + if(!(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && cursorPos > count && cursorPos < count + 2)) + { + curText++; + count += 2; + continue; + } + } + + auto finalColor = currentColor; + + if(letter == '^' && (*curText == '\x01' || *curText == '\x02')) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + xa += DrawHudIcon(curText, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, ColorRgba(255, 255, 255, finalColor.array[3])); + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + ++count; + maxLengthRemaining--; + continue; + } + + if(letter == FONT_ICON_SEPARATOR_CHARACTER) + { + FontIconInfo fontIconInfo{}; + const char* fontIconEnd = curText; + if(IsFontIcon(fontIconEnd, fontIconInfo) && !(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && cursorPos > count && cursorPos <= count + (fontIconEnd - curText))) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + + if(passes[passIndex] == Game::FONTPASS_NORMAL) + xa += DrawFontIcon(fontIconInfo, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, ColorRgba(255, 255, 255, finalColor.array[3])); + else + xa += GetFontIconWidth(fontIconInfo, font, xScale); + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + count += (fontIconEnd - curText) + 1; + maxLengthRemaining--; + curText = fontIconEnd; + continue; + } + } + + if(drawRandomCharAtEnd && maxLengthRemaining == 1) + { + letter = R_FontGetRandomLetter(Game::RandWithSeed(&passRandSeed)); + + if(Game::RandWithSeed(&passRandSeed) % 2) + { + drawExtraFxChar = true; + letter = 'O'; + } + } + + if(letter == '\n') + { + xa = startX; + xy += static_cast(font->pixelHeight) * yScale; + continue; + } + + if(letter == '\r') + { + xy += static_cast(font->pixelHeight) * yScale; + continue; + } + + auto skipDrawing = false; + if(decaying) + { + char decayAlpha; + Game::GetDecayingLetterInfo(letter, &passRandSeed, decayTimeElapsed, fxBirthTime, fxDecayDuration, currentColor.array[3], &skipDrawing, &decayAlpha, &letter, &drawExtraFxChar); + finalColor.array[3] = decayAlpha; + } + + if(drawExtraFxChar) + { + auto tempSeed = passRandSeed; + extraFxChar = Game::RandWithSeed(&tempSeed); + } + + auto glyph = Game::R_GetCharacterGlyph(font, letter); + auto xAdj = static_cast(glyph->x0) * xScale; + auto yAdj = static_cast(glyph->y0) * yScale; + + if(!skipDrawing) + { + if (passes[passIndex] == Game::FONTPASS_NORMAL) + { + if (renderFlags & Game::TEXT_RENDERFLAG_DROPSHADOW) + { + auto ofs = 1.0f; + if (renderFlags & Game::TEXT_RENDERFLAG_DROPSHADOW_EXTRA) + ofs += 1.0f; + + xRot = xa + xAdj + ofs; + yRot = xy + yAdj + ofs; + RotateXY(cosAngle, sinAngle, startX, startY, xRot, yRot, &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, dropShadowColor.packed); + else + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, dropShadowColor.packed); + } + + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj, xy + yAdj, &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, finalColor.packed); + else + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, finalColor.packed); + } + else if(passes[passIndex] == Game::FONTPASS_OUTLINE) + { + auto outlineSize = 1.0f; + if (renderFlags & Game::TEXT_RENDERFLAG_OUTLINE_EXTRA) + outlineSize = 1.3f; + + for (const auto offset : MY_OFFSETS) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj + outlineSize * offset[0], xy + yAdj + outlineSize * offset[1], &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterial, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, dropShadowColor.packed); + else + Game::RB_DrawChar(material, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, dropShadowColor.packed); + } + } + else if(passes[passIndex] == Game::FONTPASS_GLOW && ((renderFlags & Game::TEXT_RENDERFLAG_SUBTITLETEXT) == 0 || subtitleAllowGlow)) + { + GlowColor(&finalColor, finalColor, glowForcedColor, renderFlags); + + for (const auto offset : MY_OFFSETS) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa + xAdj + 2.0f * offset[0] * xScale, xy + yAdj + 2.0f * offset[1] * yScale, &xRot, &yRot); + if (drawExtraFxChar) + DrawTextFxExtraCharacter(fxMaterialGlow, extraFxChar, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, finalColor.packed); + else + Game::RB_DrawChar(glowMaterial, xRot, yRot, static_cast(glyph->pixelWidth) * xScale, static_cast(glyph->pixelHeight) * yScale, sinAngle, cosAngle, glyph, finalColor.packed); + } + } + } + + if(forceMonospace) + xa += monospaceWidth * xScale; + else + xa += static_cast(glyph->dx) * xScale; + + if (renderFlags & Game::TEXT_RENDERFLAG_PADDING) + xa += xScale * padding; + + count++; + maxLengthRemaining--; + } + + if(renderFlags & Game::TEXT_RENDERFLAG_CURSOR && count == cursorPos) + { + RotateXY(cosAngle, sinAngle, startX, startY, xa, xy, &xRot, &yRot); + Game::RB_DrawCursor(material, cursorLetter, xRot, yRot, sinAngle, cosAngle, font, xScale, yScale, color.packed); + } + } + } + + int TextRenderer::R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font) + { + auto lineWidth = 0; + auto maxWidth = 0; + + if (maxChars <= 0) + maxChars = std::numeric_limits::max(); + + if (text == nullptr) + return 0; + + auto count = 0; + while (text && *text && count < maxChars) + { + const auto letter = Game::SEH_ReadCharFromString(&text, nullptr); + if (letter == '\r' || letter == '\n') + { + lineWidth = 0; + } + else + { + if (letter == '^' && text) + { + if (*text >= COLOR_FIRST_CHAR && *text <= COLOR_LAST_CHAR) + { + text++; + continue; + } + + if (*text >= '\x01' && *text <= '\x02' && text[1] != '\0' && text[2] != '\0' && text[3] != '\0') + { + const auto width = text[1]; + const auto materialNameLength = text[3]; + + // This is how the game calculates width and height. Probably some 1 byte floating point number. + // Details to be investigated if necessary. + const auto v9 = font->pixelHeight * (width - 16) + 16; + const auto w = ((((v9 >> 24) & 0x1F) + v9) >> 5); + + lineWidth += w; + if (lineWidth > maxWidth) + maxWidth = lineWidth; + + text += 4; + for (auto currentLength = 0; currentLength < materialNameLength && *text; currentLength++) + text++; + continue; + } + } + + if (letter == FONT_ICON_SEPARATOR_CHARACTER) + { + FontIconInfo fontIconInfo{}; + const char* fontIconEnd = text; + if (IsFontIcon(fontIconEnd, fontIconInfo)) + { + lineWidth += static_cast(GetFontIconWidth(fontIconInfo, font, 1.0f)); + if (lineWidth > maxWidth) + maxWidth = lineWidth; + text = fontIconEnd; + continue; + } + } + + lineWidth += R_GetCharacterGlyph(font, letter)->dx; + if (lineWidth > maxWidth) + maxWidth = lineWidth; + count++; + } + } + + return maxWidth; + } + + unsigned int TextRenderer::ColorIndex(const char index) + { + auto result = index - '0'; + if (static_cast(result) >= TEXT_COLOR_COUNT || result < 0) result = 7; + return result; + } + + void TextRenderer::StripColors(const char* in, char* out, size_t max) + { + if (!in || !out) return; + + max--; + size_t current = 0; + while (*in != 0 && current < max) + { + const char index = *(in + 1); + if (*in == '^' && (ColorIndex(index) != 7 || index == '7')) + { + ++in; + } + else + { + *out = *in; + ++out; + ++current; + } + + ++in; + } + *out = '\0'; + } + + std::string TextRenderer::StripColors(const std::string& in) + { + char buffer[1000] = { 0 }; // Should be more than enough + StripColors(in.data(), buffer, sizeof(buffer)); + return std::string(buffer); + } + + void TextRenderer::StripMaterialTextIcons(const char* in, char* out, size_t max) + { + if (!in || !out) return; + + max--; + size_t current = 0; + while (*in != 0 && current < max) + { + if (*in == '^' && (in[1] == '\x01' || in[1] == '\x02')) + { + in += 2; + + if (*in) // width + in++; + if (*in) // height + in++; + + if(*in) // material name length + material name characters + { + const auto materialNameLength = *in; + in++; + for(auto i = 0; i < materialNameLength; i++) + { + if (*in) + in++; + } + } + } + else + { + *out = *in; + ++out; + ++current; + ++in; + } + + } + *out = '\0'; + } + + std::string TextRenderer::StripMaterialTextIcons(const std::string& in) + { + char buffer[1000] = { 0 }; // Should be more than enough + StripAllTextIcons(in.data(), buffer, sizeof(buffer)); + return std::string(buffer); + } + + void TextRenderer::StripAllTextIcons(const char* in, char* out, size_t max) + { + if (!in || !out) return; + + max--; + size_t current = 0; + while (*in != 0 && current < max) + { + if (*in == '^' && (in[1] == '\x01' || in[1] == '\x02')) + { + in += 2; + + if (*in) // width + in++; + if (*in) // height + in++; + + if(*in) // material name length + material name characters + { + const auto materialNameLength = *in; + in++; + for(auto i = 0; i < materialNameLength; i++) + { + if (*in) + in++; + } + } + + continue; + } + + if(*in == FONT_ICON_SEPARATOR_CHARACTER) + { + const auto* fontIconEndPos = &in[1]; + FontIconInfo fontIcon{}; + if(IsFontIcon(fontIconEndPos, fontIcon)) + { + in = fontIconEndPos; + continue; + } + } + + *out = *in; + ++out; + ++current; + ++in; + } + *out = '\0'; + } + + std::string TextRenderer::StripAllTextIcons(const std::string& in) + { + char buffer[1000] = { 0 }; // Should be more than enough + StripAllTextIcons(in.data(), buffer, sizeof(buffer)); + return std::string(buffer); + } + + int TextRenderer::SEH_PrintStrlenWithCursor(const char* string, const Game::field_t* field) + { + if (!string) + return 0; + + const auto cursorPos = field->cursor; + auto len = 0; + auto lenWithInvisibleTail = 0; + auto count = 0; + const auto* curText = string; + while(*curText) + { + const auto c = Game::SEH_ReadCharFromString(&curText, nullptr); + lenWithInvisibleTail = len; + + if (c == '^' && *curText >= COLOR_FIRST_CHAR && *curText <= COLOR_LAST_CHAR && !(cursorPos > count && cursorPos < count + 2)) + { + curText++; + count++; + } + else if(c != '\r' && c != '\n') + { + len++; + } + + count++; + lenWithInvisibleTail++; + } + + return lenWithInvisibleTail; + } + + __declspec(naked) void TextRenderer::Field_AdjustScroll_PrintLen_Stub() + { + __asm + { + push eax + pushad + + push esi + push [esp + 0x8 + 0x24] + call SEH_PrintStrlenWithCursor + add esp, 0x8 + mov [esp + 0x20], eax + + popad + pop eax + ret + } + } + + void TextRenderer::PatchColorLimit(const char limit) + { + Utils::Hook::Set(0x535629, limit); // DrawText2d + Utils::Hook::Set(0x4C1BE4, limit); // SEH_PrintStrlen + Utils::Hook::Set(0x4863DD, limit); // No idea :P + Utils::Hook::Set(0x486429, limit); // No idea :P + Utils::Hook::Set(0x49A5A8, limit); // No idea :P + Utils::Hook::Set(0x505721, limit); // R_TextWidth + Utils::Hook::Set(0x505801, limit); // No idea :P + Utils::Hook::Set(0x50597F, limit); // No idea :P + Utils::Hook::Set(0x5815DB, limit); // No idea :P + Utils::Hook::Set(0x592ED0, limit); // No idea :P + Utils::Hook::Set(0x5A2E2E, limit); // No idea :P + + Utils::Hook::Set(0x5A2733, static_cast(ColorIndexForChar(limit))); // No idea :P + } + + // Patches team overhead normally + bool TextRenderer::Dvar_GetUnpackedColorByName(const char* name, float* expandedColor) + { + if (r_colorBlind.get()) + { + const auto str = std::string(name); + if (str == "g_TeamColor_EnemyTeam") + { + // Dvar_GetUnpackedColor + const auto* colorblindEnemy = g_ColorBlind_EnemyTeam->current.color; + expandedColor[0] = static_cast(colorblindEnemy[0]) / 255.0f; + expandedColor[1] = static_cast(colorblindEnemy[1]) / 255.0f; + expandedColor[2] = static_cast(colorblindEnemy[2]) / 255.0f; + expandedColor[3] = static_cast(colorblindEnemy[3]) / 255.0f; + return false; + } + else if (str == "g_TeamColor_MyTeam") + { + // Dvar_GetUnpackedColor + const auto* colorblindAlly = g_ColorBlind_MyTeam->current.color; + expandedColor[0] = static_cast(colorblindAlly[0]) / 255.0f; + expandedColor[1] = static_cast(colorblindAlly[1]) / 255.0f; + expandedColor[2] = static_cast(colorblindAlly[2]) / 255.0f; + expandedColor[3] = static_cast(colorblindAlly[3]) / 255.0f; + return false; + } + } + + return true; + } + + __declspec(naked) void TextRenderer::GetUnpackedColorByNameStub() + { + __asm + { + push[esp + 8h] + push[esp + 8h] + call TextRenderer::Dvar_GetUnpackedColorByName + add esp, 8h + + test al, al + jnz continue + + retn + + continue: + push edi + mov edi, [esp + 8h] + push 406535h + retn + } + } + + void TextRenderer::UpdateColorTable() + { + if (cg_newColors.get()) + currentColorTable = &colorTableNew; + else + currentColorTable = &colorTableDefault; + + (*currentColorTable)[TEXT_COLOR_AXIS] = *reinterpret_cast(0x66E5F70); + (*currentColorTable)[TEXT_COLOR_ALLIES] = *reinterpret_cast(0x66E5F74); + (*currentColorTable)[TEXT_COLOR_RAINBOW] = HsvToRgb({ static_cast((Game::Sys_Milliseconds() / 200) % 256), 255,255 }); + (*currentColorTable)[TEXT_COLOR_SERVER] = sv_customTextColor->current.unsignedInt; + } + + void TextRenderer::InitFontIconStrings() + { + stringHintAutoComplete.Format("TAB"); + stringHintModifier.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_SEPARATOR_CHARACTER)); + stringListHeader.Cache(); + stringListFlipHorizontal.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_FLIP_HORIZONTALLY)); + stringListFlipVertical.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_FLIP_VERTICALLY)); + stringListBig.Format(Utils::String::VA("%c", FONT_ICON_MODIFIER_BIG)); + } + + void TextRenderer::InitFontIcons() + { + InitFontIconStrings(); + + fontIconList.clear(); + fontIconLookup.clear(); + + const auto fontIconTable = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, "mp/fonticons.csv").stringTable; + + if(fontIconTable->columnCount < 2 || fontIconTable->rowCount <= 0) + { + Logger::Print("^1Failed to load font icon table\n"); + return; + } + + fontIconList.reserve(fontIconTable->rowCount); + for(auto rowIndex = 0; rowIndex < fontIconTable->rowCount; rowIndex++) + { + const auto* columns = &fontIconTable->values[rowIndex * fontIconTable->columnCount]; + + if(columns[0].string == nullptr || columns[1].string == nullptr) + continue; + + if (columns[0].string[0] == '\0' || columns[1].string[1] == '\0') + continue; + + if (columns[0].string[0] == '#') + continue; + + FontIconTableEntry entry + { + columns[0].string, + columns[1].string, + nullptr + }; + + fontIconList.emplace_back(entry); + fontIconLookup.emplace(std::make_pair(entry.iconName, entry)); + } + + std::sort(fontIconList.begin(), fontIconList.end(), [](const FontIconTableEntry& a, const FontIconTableEntry& b) + { + return a.iconName < b.iconName; + }); + } + + void TextRenderer::UI_Init_Hk(const int localClientNum) + { + // Call original method + Utils::Hook::Call(0x4A57D0)(localClientNum); + + InitFontIcons(); + } + + TextRenderer::TextRenderer() + { + currentColorTable = &colorTableDefault; + + cg_newColors = Dvar::Register("cg_newColors", true, Game::dvar_flag::DVAR_ARCHIVE, "Use Warfare 2 color code style."); + cg_fontIconAutocomplete = Dvar::Register("cg_fontIconAutocomplete", true, Game::dvar_flag::DVAR_ARCHIVE, "Show autocomplete for fonticons when typing."); + cg_fontIconAutocompleteHint = Dvar::Register("cg_fontIconAutocompleteHint", true, Game::dvar_flag::DVAR_ARCHIVE, "Show hint text in autocomplete for fonticons."); + sv_customTextColor = Game::Dvar_RegisterColor("sv_customTextColor", 1, 0.7f, 0, 1, Game::dvar_flag::DVAR_CODINFO, "Color for the extended color code."); + + // Initialize font icons when initializing UI + Utils::Hook(0x4B5422, UI_Init_Hk, HOOK_CALL).install()->quick(); + + // Replace vanilla text drawing function with a reimplementation with extensions + Utils::Hook(0x535410, DrawText2D, HOOK_JUMP).install()->quick(); + + // Consider material text icons and font icons when calculating text width + Utils::Hook(0x5056C0, R_TextWidth_Hk, HOOK_JUMP).install()->quick(); + + // Patch ColorIndex + Utils::Hook(0x417770, ColorIndex, HOOK_JUMP).install()->quick(); + + // Add a colorblind mode for team colors + r_colorBlind = Dvar::Register("r_colorBlind", false, Game::dvar_flag::DVAR_ARCHIVE, "Use color-blindness-friendly colors"); + // A dark red + g_ColorBlind_EnemyTeam = Game::Dvar_RegisterColor("g_ColorBlind_EnemyTeam", 0.659f, 0.088f, 0.145f, 1, Game::dvar_flag::DVAR_ARCHIVE, "Enemy team color for colorblind mode"); + // A bright yellow + g_ColorBlind_MyTeam = Game::Dvar_RegisterColor("g_ColorBlind_MyTeam", 1, 0.859f, 0.125f, 1, Game::dvar_flag::DVAR_ARCHIVE, "Ally team color for colorblind mode"); + + // Replace team colors with colorblind team colors when colorblind is enabled + Utils::Hook(0x406530, GetUnpackedColorByNameStub, HOOK_JUMP).install()->quick(); + + // Consider the cursor being inside the color escape sequence when getting the print length for a field + Utils::Hook(0x488CBD, Field_AdjustScroll_PrintLen_Stub, HOOK_CALL).install()->quick(); + + // Draw fonticon autocompletion for say field + Utils::Hook(0x4CA1BD, Field_Draw_Say, HOOK_CALL).install()->quick(); + + // Draw fonticon autocompletion for console field + Utils::Hook(0x5A50A5, Con_DrawInput_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x5A50BB, Con_DrawInput_Hk, HOOK_CALL).install()->quick(); + + // Handle key inputs for console and chat + Utils::Hook(0x4F685C, Console_Key_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4F6694, Message_Key_Stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4F684C, Message_Key_Stub, HOOK_CALL).install()->quick(); + + PatchColorLimit(COLOR_LAST_CHAR); + } +} diff --git a/src/Components/Modules/TextRenderer.hpp b/src/Components/Modules/TextRenderer.hpp new file mode 100644 index 00000000..2ffe44f2 --- /dev/null +++ b/src/Components/Modules/TextRenderer.hpp @@ -0,0 +1,262 @@ +#pragma once + +namespace Components +{ + enum TextColor + { + TEXT_COLOR_BLACK = 0, + TEXT_COLOR_RED = 1, + TEXT_COLOR_GREEN = 2, + TEXT_COLOR_YELLOW = 3, + TEXT_COLOR_BLUE = 4, + TEXT_COLOR_LIGHT_BLUE = 5, + TEXT_COLOR_PINK = 6, + TEXT_COLOR_DEFAULT = 7, + TEXT_COLOR_AXIS = 8, + TEXT_COLOR_ALLIES = 9, + TEXT_COLOR_RAINBOW = 10, + TEXT_COLOR_SERVER = 11, // using that color in infostrings (e.g. your name) fails, ';' is an illegal character! + + TEXT_COLOR_COUNT + }; + + constexpr unsigned int ColorRgba(const uint8_t r, const uint8_t g, const uint8_t b, const uint8_t a) + { + return (r) | (g << 8) | (b << 16) | (a << 24); + } + + constexpr unsigned int ColorRgb(const uint8_t r, const uint8_t g, const uint8_t b) + { + return ColorRgba(r, g, b, 0xFF); + } + + constexpr char CharForColorIndex(const int colorIndex) + { + return static_cast('0' + colorIndex); + } + + constexpr int ColorIndexForChar(const char colorChar) + { + return colorChar - '0'; + } + + class TextRenderer : public Component + { + static constexpr auto STRING_BUFFER_SIZE_BIG = 1024; + static constexpr auto STRING_BUFFER_SIZE_SMALL = 128; + + static constexpr auto REFERENCE_SEARCH_START_WITH = "FONT_ICON_SEARCH_START_WITH"; + static constexpr auto REFERENCE_HINT_AUTO_COMPLETE = "FONT_ICON_HINT_AUTO_COMPLETE"; + static constexpr auto REFERENCE_HINT_MODIFIER = "FONT_ICON_HINT_MODIFIER"; + static constexpr auto REFERENCE_MODIFIER_LIST_HEADER = "FONT_ICON_MODIFIER_LIST_HEADER"; + static constexpr auto REFERENCE_MODIFIER_LIST_FLIP_HORIZONTAL = "FONT_ICON_MODIFIER_LIST_FLIP_HORIZONTAL"; + static constexpr auto REFERENCE_MODIFIER_LIST_FLIP_VERTICAL = "FONT_ICON_MODIFIER_LIST_FLIP_VERTICAL"; + static constexpr auto REFERENCE_MODIFIER_LIST_BIG = "FONT_ICON_MODIFIER_LIST_BIG"; + + static constexpr unsigned MY_ALTCOLOR_TWO = 0x0DCE6FFE6; + static constexpr unsigned COLOR_MAP_HASH = 0xA0AB1041; + static constexpr auto FONT_ICON_AUTOCOMPLETE_BOX_PADDING = 6.0f; + static constexpr auto FONT_ICON_AUTOCOMPLETE_BOX_BORDER = 2.0f; + static constexpr auto FONT_ICON_AUTOCOMPLETE_COL_SPACING = 12.0f; + static constexpr auto FONT_ICON_AUTOCOMPLETE_ARROW_SIZE = 12.0f; + static constexpr float MY_OFFSETS[4][2] + { + {-1.0f, -1.0f}, + {-1.0f, 1.0f}, + {1.0f, -1.0f}, + {1.0f, 1.0f}, + }; + static constexpr float WHITE_COLOR[4] + { + 1.0f, + 1.0f, + 1.0f, + 1.0f + }; + static constexpr float TEXT_COLOR[4] + { + 1.0f, + 1.0f, + 0.8f, + 1.0f + }; + static constexpr float HINT_COLOR[4] + { + 0.6f, + 0.6f, + 0.6f, + 1.0f + }; + + public: + static constexpr char FONT_ICON_SEPARATOR_CHARACTER = ':'; + static constexpr char FONT_ICON_MODIFIER_SEPARATOR_CHARACTER = '+'; + static constexpr char FONT_ICON_MODIFIER_FLIP_HORIZONTALLY = 'h'; + static constexpr char FONT_ICON_MODIFIER_FLIP_VERTICALLY = 'v'; + static constexpr char FONT_ICON_MODIFIER_BIG = 'b'; + + static constexpr char COLOR_FIRST_CHAR = '0'; + static constexpr char COLOR_LAST_CHAR = CharForColorIndex(TEXT_COLOR_COUNT - 1); + + enum FontIconAutocompleteInstance : unsigned + { + FONT_ICON_ACI_CONSOLE, + FONT_ICON_ACI_CHAT, + + FONT_ICON_ACI_COUNT + }; + + struct FontIconInfo + { + Game::Material* material; + bool flipHorizontal; + bool flipVertical; + bool big; + }; + + private: + struct FontIconTableEntry + { + std::string iconName; + std::string materialName; + Game::Material* material; + }; + + struct HsvColor + { + unsigned char h; + unsigned char s; + unsigned char v; + }; + + class FontIconAutocompleteResult + { + public: + std::string fontIconName; + std::string materialName; + }; + + class BufferedLocalizedString + { + public: + BufferedLocalizedString(const char* reference, size_t bufferSize); + void Cache(); + const char* Format(const char* value); + const char* GetString() const; + int GetWidth(FontIconAutocompleteInstance autocompleteInstance, Game::Font_s* font); + + private: + const char* stringReference; + std::unique_ptr stringBuffer; + size_t stringBufferSize; + int stringWidth[FONT_ICON_ACI_COUNT]; + }; + + class FontIconAutocompleteContext + { + public: + static constexpr auto MAX_RESULTS = 10; + + bool autocompleteActive; + bool inModifiers; + bool userClosed; + unsigned int lastHash; + std::string lastQuery; + FontIconAutocompleteResult results[MAX_RESULTS]; + size_t resultCount; + bool hasMoreResults; + size_t resultOffset; + size_t lastResultOffset; + size_t selectedOffset; + float maxFontIconWidth; + float maxMaterialNameWidth; + BufferedLocalizedString stringSearchStartWith; + + FontIconAutocompleteContext(); + }; + + static unsigned colorTableDefault[TEXT_COLOR_COUNT]; + static unsigned colorTableNew[TEXT_COLOR_COUNT]; + static unsigned(*currentColorTable)[TEXT_COLOR_COUNT]; + static FontIconAutocompleteContext autocompleteContextArray[FONT_ICON_ACI_COUNT]; + static std::map fontIconLookup; + static std::vector fontIconList; + + static BufferedLocalizedString stringHintAutoComplete; + static BufferedLocalizedString stringHintModifier; + static BufferedLocalizedString stringListHeader; + static BufferedLocalizedString stringListFlipHorizontal; + static BufferedLocalizedString stringListFlipVertical; + static BufferedLocalizedString stringListBig; + + static Dvar::Var cg_newColors; + static Dvar::Var cg_fontIconAutocomplete; + static Dvar::Var cg_fontIconAutocompleteHint; + static Game::dvar_t* sv_customTextColor; + static Dvar::Var r_colorBlind; + static Game::dvar_t* g_ColorBlind_MyTeam; + static Game::dvar_t* g_ColorBlind_EnemyTeam; + static Game::dvar_t** con_inputBoxColor; + + public: + static void DrawText2D(const char* text, float x, float y, Game::Font_s* font, float xScale, float yScale, float sinAngle, float cosAngle, Game::GfxColor color, int maxLength, int renderFlags, int cursorPos, char cursorLetter, float padding, Game::GfxColor glowForcedColor, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, Game::Material* fxMaterial, Game::Material* fxMaterialGlow); + static int R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font); + static unsigned int ColorIndex(char index); + static void StripColors(const char* in, char* out, size_t max); + static std::string StripColors(const std::string& in); + static void StripMaterialTextIcons(const char* in, char* out, size_t max); + static std::string StripMaterialTextIcons(const std::string& in); + static void StripAllTextIcons(const char* in, char* out, size_t max); + static std::string StripAllTextIcons(const std::string& in); + + static bool IsFontIcon(const char*& text, FontIconInfo& fontIcon); + static float GetNormalizedFontIconWidth(const FontIconInfo& fontIcon); + static float GetFontIconWidth(const FontIconInfo& fontIcon, const Game::Font_s* font, float xScale); + + static bool HandleFontIconAutocompleteKey(int localClientNum, FontIconAutocompleteInstance autocompleteInstance, int key); + + TextRenderer(); + + private: + static unsigned HsvToRgb(HsvColor hsv); + + static void DrawAutocompleteBox(const FontIconAutocompleteContext& context, float x, float y, float w, float h, const float* color); + static void DrawAutocompleteModifiers(FontIconAutocompleteInstance instance, float x, float y, Game::Font_s* font, float textXScale, float textYScale); + static void DrawAutocompleteResults(FontIconAutocompleteInstance instance, float x, float y, Game::Font_s* font, float textXScale, float textYScale); + static void DrawAutocomplete(FontIconAutocompleteInstance instance, float x, float y, Game::Font_s* font, float textXScale, float textYScale); + static void UpdateAutocompleteContextResults(FontIconAutocompleteContext& context, Game::Font_s* font, float textXScale); + static void UpdateAutocompleteContext(FontIconAutocompleteContext& context, const Game::field_t* edit, Game::Font_s* font, const float textXScale); + static void Field_Draw_Say(int localClientNum, Game::field_t* edit, int x, int y, int horzAlign, int vertAlign); + static void Con_DrawInput_Hk(int localClientNum); + + static void AutocompleteUp(FontIconAutocompleteContext& context); + static void AutocompleteDown(FontIconAutocompleteContext& context); + static void AutocompleteFill(const FontIconAutocompleteContext& context, Game::ScreenPlacement* scrPlace, Game::field_t* edit, bool closeFontIcon); + static bool AutocompleteHandleKeyDown(FontIconAutocompleteContext& context, int key, Game::ScreenPlacement* scrPlace, Game::field_t* edit); + static void Console_Key_Hk(int localClientNum, int key); + static bool ChatHandleKeyDown(int localClientNum, int key); + static void Message_Key_Stub(); + + static int SEH_PrintStrlenWithCursor(const char* string, const Game::field_t* field); + static void Field_AdjustScroll_PrintLen_Stub(); + + static void PatchColorLimit(char limit); + static bool Dvar_GetUnpackedColorByName(const char* name, float* expandedColor); + static void GetUnpackedColorByNameStub(); + + static Game::GfxImage* GetFontIconColorMap(const Game::Material* fontIconMaterial); + static float DrawFontIcon(const FontIconInfo& fontIcon, float x, float y, float sinAngle, float cosAngle, const Game::Font_s* font, float xScale, float yScale, unsigned color); + + static float GetMonospaceWidth(Game::Font_s* font, int rendererFlags); + static void GlowColor(Game::GfxColor* result, Game::GfxColor baseColor, Game::GfxColor forcedGlowColor, int renderFlags); + static unsigned R_FontGetRandomLetter(int seed); + static void DrawTextFxExtraCharacter(Game::Material* material, int charIndex, float x, float y, float w, float h, float sinAngle, float cosAngle, unsigned color); + static float DrawHudIcon(const char*& text, float x, float y, float sinAngle, float cosAngle, const Game::Font_s* font, float xScale, float yScale, unsigned color); + static void RotateXY(float cosAngle, float sinAngle, float pivotX, float pivotY, float x, float y, float* outX, float* outY); + static void UpdateColorTable(); + + static void InitFontIconStrings(); + static void InitFontIcons(); + static void UI_Init_Hk(int localClientNum); + }; +} diff --git a/src/Components/Modules/Theatre.cpp b/src/Components/Modules/Theatre.cpp index 1d09657c..84a95284 100644 --- a/src/Components/Modules/Theatre.cpp +++ b/src/Components/Modules/Theatre.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -342,8 +342,8 @@ namespace Components Theatre::Theatre() { - Dvar::Register("cl_autoRecord", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Automatically record games."); - Dvar::Register("cl_demosKeep", 30, 1, 999, Game::dvar_flag::DVAR_FLAG_SAVED, "How many demos to keep with autorecord."); + Dvar::Register("cl_autoRecord", true, Game::dvar_flag::DVAR_ARCHIVE, "Automatically record games."); + Dvar::Register("cl_demosKeep", 30, 1, 999, Game::dvar_flag::DVAR_ARCHIVE, "How many demos to keep with autorecord."); Utils::Hook(0x5A8370, Theatre::GamestateWriteStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5A85D2, Theatre::RecordGamestateStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Threading.cpp b/src/Components/Modules/Threading.cpp index 72e11a5a..6ca4b25c 100644 --- a/src/Components/Modules/Threading.cpp +++ b/src/Components/Modules/Threading.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/Toast.cpp b/src/Components/Modules/Toast.cpp index 9bf5c0f7..eda8240e 100644 --- a/src/Components/Modules/Toast.cpp +++ b/src/Components/Modules/Toast.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { diff --git a/src/Components/Modules/UIFeeder.cpp b/src/Components/Modules/UIFeeder.cpp index 4182ddbc..b0e554bf 100644 --- a/src/Components/Modules/UIFeeder.cpp +++ b/src/Components/Modules/UIFeeder.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -383,9 +383,9 @@ namespace Components Dvar::OnInit([]() { - Dvar::Register("ui_map_long", "Afghan", Game::dvar_flag::DVAR_FLAG_NONE, ""); - Dvar::Register("ui_map_name", "mp_afghan", Game::dvar_flag::DVAR_FLAG_NONE, ""); - Dvar::Register("ui_map_desc", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); + Dvar::Register("ui_map_long", "Afghan", Game::dvar_flag::DVAR_NONE, ""); + Dvar::Register("ui_map_name", "mp_afghan", Game::dvar_flag::DVAR_NONE, ""); + Dvar::Register("ui_map_desc", "", Game::dvar_flag::DVAR_NONE, ""); }); // Get feeder item count diff --git a/src/Components/Modules/UIScript.cpp b/src/Components/Modules/UIScript.cpp index 1ecd6d4c..cd9b713c 100644 --- a/src/Components/Modules/UIScript.cpp +++ b/src/Components/Modules/UIScript.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -15,19 +15,14 @@ namespace Components return 0; } - template<> char* UIScript::Token::get() + template<> const char* UIScript::Token::get() { if (this->isValid()) { return this->token; } - return const_cast(""); - } - - template<> const char* UIScript::Token::get() - { - return this->get(); + return ""; } template<> std::string UIScript::Token::get() diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index eb5ae9fd..e1becfe7 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -37,7 +37,10 @@ namespace Components int Weapon::ParseWeaponConfigStrings() { Command::ClientParams params; - if (params.length() <= 1) return 0; + + if (params.size() <= 1) + return 0; + int index = atoi(params[1]); if (index >= 4139) diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index 3fd16efe..a5669656 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -144,8 +144,8 @@ namespace Components Window::Window() { // Borderless window - Window::NoBorder = Dvar::Register("r_noborder", true, Game::dvar_flag::DVAR_FLAG_SAVED, "Do not use a border in windowed mode"); - Window::NativeCursor = Dvar::Register("ui_nativeCursor", false, Game::dvar_flag::DVAR_FLAG_SAVED, "Display native cursor"); + Window::NoBorder = Dvar::Register("r_noborder", true, Game::dvar_flag::DVAR_ARCHIVE, "Do not use a border in windowed mode"); + Window::NativeCursor = Dvar::Register("ui_nativeCursor", false, Game::dvar_flag::DVAR_ARCHIVE, "Display native cursor"); Utils::Hook(0x507643, Window::StyleHookStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 82477459..1715ebe7 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { @@ -10,6 +10,8 @@ namespace Components bool ZoneBuilder::Terminate; std::thread ZoneBuilder::CommandThread; + Dvar::Var ZoneBuilder::PreferDiskAssetsDvar; + ZoneBuilder::Zone::Zone(const std::string& name) : indexStart(0), externalSize(0), // Reserve 100MB by default. @@ -44,7 +46,7 @@ namespace Components if (!found) { - Logger::Error("Asset %s of type %s was loaded, but not written!", name.data(), Game::DB_GetXAssetTypeName(subAsset.type)); + Logger::Print("Asset %s of type %s was loaded, but not written!", name.data(), Game::DB_GetXAssetTypeName(subAsset.type)); } } @@ -222,8 +224,10 @@ namespace Components } Game::XAssetHeader assetHeader = AssetHandler::FindAssetForZone(type, name, this, isSubAsset); + if (!assetHeader.data) - { Logger::Error("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type)); + { + Logger::Error("Error: Missing asset '%s' of type '%s'\n", name.data(), Game::DB_GetXAssetTypeName(type)); return false; } @@ -376,7 +380,7 @@ namespace Components Game::XFileHeader header = { -#ifdef DEBUG +#ifndef GENERATE_IW4X_SPECIFIC_ZONES XFILE_MAGIC_UNSIGNED, #else XFILE_HEADER_IW4X | (static_cast(XFILE_VERSION_IW4X) << 32), @@ -392,7 +396,7 @@ namespace Components std::string zoneBuffer = this->buffer.toBuffer(); -#ifndef DEBUG +#ifdef GENERATE_IW4X_SPECIFIC_ZONES // Insert a random byte, this will destroy the whole alignment and result in a crash, if not handled zoneBuffer.insert(zoneBuffer.begin(), static_cast(Utils::Cryptography::Rand::GenerateInt())); @@ -842,12 +846,12 @@ namespace Components Command::Add("quit", [](Command::Params*) { - ZoneBuilder::Quit(); + Game::Com_Quitf_t(); }); 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 @@ -919,12 +923,6 @@ namespace Components return 0; } - void ZoneBuilder::Quit() - { - //TerminateProcess(GetCurrentProcess(), 0); - ExitProcess(0); - } - void ZoneBuilder::HandleError(int level, const char* format, ...) { char buffer[256] = { 0 }; @@ -983,7 +981,7 @@ namespace Components replacementFound = true; } } - }, false, false); + }, false); if (replacementFound) return ret; return ""; @@ -1086,9 +1084,6 @@ namespace Components // set new entry point Utils::Hook(0x4513DA, ZoneBuilder::EntryPoint, HOOK_JUMP).install()->quick(); - // set quit handler - Utils::Hook(0x4D4000, ZoneBuilder::Quit, HOOK_JUMP).install()->quick(); - // handle com_error calls Utils::Hook(0x4B22D0, ZoneBuilder::HandleError, HOOK_JUMP).install()->quick(); @@ -1115,7 +1110,7 @@ namespace Components Command::Add("verifyzone", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; /* Utils::Hook(0x4AE9C2, [] { Game::WeaponCompleteDef** varPtr = (Game::WeaponCompleteDef**)0x112A9F4; @@ -1170,7 +1165,7 @@ namespace Components Command::Add("buildzone", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; std::string zoneName = params->get(1); Logger::Print("Building zone '%s'...\n", zoneName.data()); @@ -1305,7 +1300,7 @@ namespace Components }, nullptr, false); // HACK: set language to 'techsets' to load from that dir - char* language = Utils::Hook::Get(0x649E740); + const char* language = Utils::Hook::Get(0x649E740); Utils::Hook::Set(0x649E740, "techsets"); // load generated techset fastfiles @@ -1417,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(); @@ -1452,7 +1447,7 @@ namespace Components Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr.data()); // set language back - Utils::Hook::Set(0x649E740, language); + Utils::Hook::Set(0x649E740, language); Logger::Print("Building zone 'techsets/techsets'...\n"); Zone("techsets/techsets").build(); @@ -1460,7 +1455,7 @@ namespace Components Command::Add("listassets", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; Game::XAssetType type = Game::DB_GetXAssetNameType(params->get(1)); if (type != Game::XAssetType::ASSET_TYPE_INVALID) @@ -1475,7 +1470,7 @@ namespace Components Command::Add("loadtempzone", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; if (FastFiles::Exists(params->get(1))) { @@ -1507,7 +1502,7 @@ namespace Components Command::Add("iwiDump", [](Command::Params* params) { - if (params->length() < 2) return; + if (params->size() < 2) return; std::string path = Utils::String::VA("%s\\mods\\%s\\images", Dvar::Var("fs_basepath").get(), params->get(1)); std::vector images = FileSystem::GetSysFileList(path, "iwi", false); @@ -1529,6 +1524,8 @@ namespace Components Logger::Print("%s\n", json11::Json(images).dump().data()); Logger::Print("------------------- END IWI DUMP -------------------\n"); }); + + ZoneBuilder::PreferDiskAssetsDvar = Dvar::Register("zb_prefer_disk_assets", false, Game::DVAR_NONE, "Should zonebuilder prefer in-memory assets (requirements) or disk assets, when both are present?"); } } diff --git a/src/Components/Modules/ZoneBuilder.hpp b/src/Components/Modules/ZoneBuilder.hpp index d35ec63e..5c647816 100644 --- a/src/Components/Modules/ZoneBuilder.hpp +++ b/src/Components/Modules/ZoneBuilder.hpp @@ -130,6 +130,7 @@ namespace Components static std::vector> EndAssetTrace(); static Game::XAssetHeader GetEmptyAssetIfCommon(Game::XAssetType type, const std::string& name, Zone* builder); + static Dvar::Var PreferDiskAssetsDvar; private: static int StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image); @@ -138,7 +139,6 @@ namespace Components static std::string FindMaterialByTechnique(const std::string& name); static int __stdcall EntryPoint(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/); - static void Quit(); static void HandleError(int level, const char* format, ...); static void SoftErrorAssetOverflow(); diff --git a/src/Components/Modules/Zones.cpp b/src/Components/Modules/Zones.cpp index f47850ee..24af1db6 100644 --- a/src/Components/Modules/Zones.cpp +++ b/src/Components/Modules/Zones.cpp @@ -1,13 +1,66 @@ -#include "STDInclude.hpp" +#include #pragma optimize( "", off ) namespace Components { int Zones::ZoneVersion; + int Zones::EntitiesVersion; int Zones::FxEffectIndex; char* Zones::FxEffectStrings[64]; + static std::unordered_map shellshock_replace_list = { + { "66","bg_shock_screenType" }, + { "67","bg_shock_screenBlurBlendTime"}, + { "68","bg_shock_screenBlurBlendFadeTime"}, + { "69","bg_shock_screenFlashWhiteFadeTime"}, + { "70","bg_shock_screenFlashShotFadeTime"}, + { "71","bg_shock_viewKickPeriod"}, + { "72","bg_shock_viewKickRadius"}, + { "73","bg_shock_viewKickFadeTime"}, + { "78","bg_shock_sound"}, + { "74","bg_shock_soundLoop"}, + { "75","bg_shock_soundLoopSilent"}, + { "76","bg_shock_soundEnd"}, + { "77","bg_shock_soundEndAbort"}, + { "79","bg_shock_soundFadeInTime"}, + { "80","bg_shock_soundFadeOutTime"}, + { "81","bg_shock_soundLoopFadeTime"}, + { "82","bg_shock_soundLoopEndDelay"}, + { "83","bg_shock_soundRoomType"}, + { "84","bg_shock_soundDryLevel"}, + { "85","bg_shock_soundWetLevel"}, + { "86","bg_shock_soundModEndDelay"}, + + // guessed, not sure + { "87","bg_shock_lookControl"}, + { "88","bg_shock_lookControl_maxpitchspeed"}, + { "89","bg_shock_lookControl_maxyawspeed"}, + { "90","bg_shock_lookControl_mousesensitivityscale"}, + { "91","bg_shock_lookControl_fadeTime"}, + { "92","bg_shock_movement"} + }; + + static std::unordered_map vision_replace_list = { + { "511","r_glow" }, + { "516","r_glowRadius0" }, + { "512","r_glowBloomCutoff" }, + { "513","r_glowBloomDesaturation" }, + { "514","r_glowBloomIntensity0" }, + { "520","r_filmEnable" }, + { "522","r_filmContrast" }, + { "521","r_filmBrightness" }, + { "523","r_filmDesaturation" }, + { "524","r_filmDesaturationDark" }, + { "525","r_filmInvert" }, + { "526","r_filmLightTint" }, + { "527","r_filmMediumTint" }, + { "528","r_filmDarkTint" }, + { "529","r_primaryLightUseTweaks" }, + { "530","r_primaryLightTweakDiffuseStrength" }, + { "531","r_primaryLightTweakSpecularStrength" }, + }; + Game::XAssetType currentAssetType = Game::XAssetType::ASSET_TYPE_INVALID; Game::XAssetType previousAssetType = Game::XAssetType::ASSET_TYPE_INVALID; @@ -223,30 +276,20 @@ namespace Components int size = 3112; - if (Zones::ZoneVersion >= 318) - { + if (Zones::ZoneVersion >= 461) + size = 4124; + else if (Zones::ZoneVersion >= 460) + size = 4120; + else if (Zones::ZoneVersion >= 365) + size = 3124; + else if (Zones::ZoneVersion >= 359) + size = 3120; + else if (Zones::ZoneVersion >= 332) + size = 3068; + else if (Zones::ZoneVersion >= 318) size = 3156; - if (Zones::ZoneVersion >= 332) - { - size = 3068; // We didn't adapt that, but who the fuck cares! - - if (Zones::ZoneVersion >= 359) - { - size = 3120; - - if (Zones::ZoneVersion >= 365) - { - size = 3124; - - if (Zones::ZoneVersion >= 460) - { - size = 4120; - } - } - } - } - } + int offsetShift = (Zones::ZoneVersion >= 461) ? 4 : 0; // and do the stuff Game::Load_Stream(true, varWeaponCompleteDef, size); @@ -270,7 +313,7 @@ namespace Components if (Zones::ZoneVersion >= 359) { - auto count = (Zones::Version() == 460) ? 52 : 56; + auto count = (Zones::Version() >= 460) ? 52 : 56; for (int offset = 20; offset <= count; offset += 4) { *Game::varXModelPtr = reinterpret_cast(varWeaponCompleteDef + offset); @@ -299,7 +342,7 @@ namespace Components if (Zones::ZoneVersion >= 359) { - auto stringCount = (Zones::Version() == 460) ? 62 : 52; + auto stringCount = (Zones::Version() >= 460) ? 62 : 52; auto arraySize = stringCount * 4; // 236 @@ -395,8 +438,8 @@ namespace Components // 980 if (Zones::ZoneVersion >= 359) { - auto offset = (Zones::Version() == 460) ? 1476 : 916; - auto count = (Zones::Version() == 460) ? 57 : 52; + auto offset = (Zones::Version() >= 460) ? 1476 : 916; + auto count = (Zones::Version() >= 461) ? 58 : (Zones::Version() >= 460) ? 57 : 52; // 53 soundalias name references; up to and including 1124 for (int i = 0; i < count; ++i, offset += 4) @@ -809,7 +852,7 @@ namespace Components void* vec2 = Game::DB_AllocStreamPos(3); *reinterpret_cast(varWeaponCompleteDef + 3204) = vec2; - Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3776)); + Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3776 + offsetShift)); } *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3200); @@ -820,7 +863,7 @@ namespace Components void* vec2 = Game::DB_AllocStreamPos(3); *reinterpret_cast(varWeaponCompleteDef + 3208) = vec2; - Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3778)); + Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3778 + offsetShift)); } } else if (Zones::ZoneVersion >= 359) @@ -868,22 +911,22 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3288); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3288 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3292); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3292 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3324); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3324 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3328); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3328 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3484); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3484 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3488); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3488 + offsetShift); Game::Load_XString(false); } else if (Zones::ZoneVersion >= 359) @@ -929,37 +972,37 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3492); + *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3492 + offsetShift); Game::Load_TracerDefPtr(false); - *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3496); + *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3496 + offsetShift); Game::Load_TracerDefPtr(false); - *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3500); + *Game::varTracerDefPtr = reinterpret_cast(varWeaponCompleteDef + 3500 + offsetShift); Game::Load_TracerDefPtr(false); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3528); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3528 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); // 2848 - *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3532); + *Game::varFxEffectDefHandle = reinterpret_cast(varWeaponCompleteDef + 3532 + offsetShift); Game::Load_FxEffectDefHandle(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3536); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3536 + offsetShift); Game::Load_XString(false); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3552); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3552 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3556); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3556 + offsetShift); Game::Load_snd_alias_list_nameArray(false, 4); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3572); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3572 + offsetShift); Game::Load_snd_alias_list_nameArray(false, 4); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3588); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3588 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); - *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3592); + *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + 3592 + offsetShift); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); } else if (Zones::ZoneVersion >= 359) @@ -1023,7 +1066,7 @@ namespace Components if (Zones::Version() >= 460) { - for (int i = 0, offset = 3660; i < 6; ++i, offset += 4) + for (int i = 0, offset = 3660 + offsetShift; i < 6; ++i, offset += 4) { *Game::varsnd_alias_list_name = reinterpret_cast(varWeaponCompleteDef + offset); Game::Load_SndAliasCustom(*Game::varsnd_alias_list_name); @@ -1058,25 +1101,25 @@ namespace Components if (Zones::Version() >= 460) { - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3712); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3712 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3728); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3728 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3732); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3732 + offsetShift); Game::Load_XString(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3740); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3740 + offsetShift); Game::Load_MaterialHandle(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3744); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3744 + offsetShift); Game::Load_MaterialHandle(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3748); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3748 + offsetShift); Game::Load_MaterialHandle(false); - *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3752); + *Game::varMaterialHandle = reinterpret_cast(varWeaponCompleteDef + 3752 + offsetShift); Game::Load_MaterialHandle(false); } else if (Zones::ZoneVersion >= 359) @@ -1127,35 +1170,35 @@ namespace Components if (Zones::Version() >= 460) { - if (*reinterpret_cast(varWeaponCompleteDef + 3780) == -1) + if (*reinterpret_cast(varWeaponCompleteDef + 3780 + offsetShift) == -1) { void* vec2 = Game::DB_AllocStreamPos(3); - *reinterpret_cast(varWeaponCompleteDef + 3780) = vec2; + *reinterpret_cast(varWeaponCompleteDef + 3780 + offsetShift) = vec2; - Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3776)); + Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3776 + offsetShift)); } - if (*reinterpret_cast(varWeaponCompleteDef + 3784) == -1) + if (*reinterpret_cast(varWeaponCompleteDef + 3784 + offsetShift) == -1) { void* vec2 = Game::DB_AllocStreamPos(3); - *reinterpret_cast(varWeaponCompleteDef + 3784) = vec2; + *reinterpret_cast(varWeaponCompleteDef + 3784 + offsetShift) = vec2; - Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3778)); + Game::Load_Stream(true, vec2, 8 * *reinterpret_cast(varWeaponCompleteDef + 3778 + offsetShift)); } - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3876); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3876 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3880); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3880 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3884); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3884 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3996); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 3996 + offsetShift); Game::Load_XString(false); - *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 4012); + *Game::varXString = reinterpret_cast(varWeaponCompleteDef + 4012 + offsetShift); Game::Load_XString(false); } else if (Zones::ZoneVersion >= 359) @@ -1381,7 +1424,7 @@ namespace Components char* varWeaponAttach = *reinterpret_cast(0x112ADE0); // varAddonMapEnts // and do the stuff - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { Game::Load_Stream(true, varWeaponAttach, 20); @@ -1426,7 +1469,7 @@ namespace Components bool Zones::LoadMaterialShaderArgumentArray(bool atStreamStart, Game::MaterialShaderArgument* argument, int size) { - // if (Zones::ZoneVersion >= 448 && currentAssetType == Game::XAssetType::ASSET_TYPE_FX) __debugbreak(); + // if (Zones::ZoneVersion >= 446 && currentAssetType == Game::XAssetType::ASSET_TYPE_FX) __debugbreak(); bool result = Game::Load_Stream(atStreamStart, argument, size); Game::MaterialPass* curPass = *Game::varMaterialPass; @@ -1436,13 +1479,13 @@ namespace Components { Game::MaterialShaderArgument* arg = &argument[i]; - if (Zones::Version() < 448) + if (arg->type != D3DSHADER_PARAM_REGISTER_TYPE::D3DSPR_TEXTURE && arg->type != D3DSHADER_PARAM_REGISTER_TYPE::D3DSPR_ATTROUT) + { + continue; + } + + if (Zones::Version() < 446) { - if (arg->type != D3DSHADER_PARAM_REGISTER_TYPE::D3DSPR_TEXTURE && arg->type != D3DSHADER_PARAM_REGISTER_TYPE::D3DSPR_ATTROUT) - { - continue; - } - // should be min 68 currently // >= 58 fixes foliage without bad side effects // >= 53 still has broken shadow mapping @@ -1489,7 +1532,100 @@ namespace Components { if (arg->type == 3 || arg->type == 5) { - if (Zones::Version() == 460 /*|| Zones::Version() == 448*/) // 448 is no longer compatible, needs correct mappings + // 446 is from a special client version that had lot of + // unrelased/unfinished maps, is just enough for explore, + // trees had issue with it + if (Zones::Version() == 446) + { + static std::unordered_map mapped_constants = { + { 33, 31 }, + { 34, 32 }, + { 36, 34 }, + { 39, 37 }, + { 40, 38 }, + { 42, 40 }, + { 43, 41 }, + { 45, 43 }, + { 62, 52 }, + { 63, 53 }, + { 199, 58 }, + { 259, 86 }, + { 263, 90 }, + { 271, 98 }, + { 279, 106 }, + }; + + const auto itr = mapped_constants.find(arg->u.codeConst.index); + if (itr != mapped_constants.end()) + { + arg->u.codeConst.index = itr->second; + } + } + else if (Zones::Version() == 461) + { + static std::unordered_map mapped_constants = + { + // mp_raid + { 33, 31 }, + { 34, 32 }, + { 36, 34 }, + { 39, 37 }, + { 40, 38 }, + { 42, 40 }, + { 43, 41 }, + { 45, 43 }, + { 62, 52 }, + { 63, 53 }, + { 197, 58 }, + { 202, 63 }, + { 203, 64 }, + { 261, 90 }, + { 265, 94 }, + { 269, 98 }, + { 277, 106 }, + + // mp_dome + { 38, 36 }, + { 40, 38 }, + { 118, 86 }, + }; + + const auto itr = mapped_constants.find(arg->u.codeConst.index); + if (itr != mapped_constants.end()) + { + arg->u.codeConst.index = itr->second; + } + if (arg->u.codeConst.index == 257) + { + auto techsetName = (*reinterpret_cast(0x112AE8C))->name; + + // dont know if this applies to 460 too, but I dont have 460 files to test + if (!strncmp(techsetName, "wc_unlit_add", 12) || + !strncmp(techsetName, "wc_unlit_multiply", 17) ) + { + // fixes glass and water + arg->u.codeConst.index = 116; + } + else + { + // anything else + arg->u.codeConst.index = 86; + } + } + else + { + // copy-paste from 460 + if (arg->u.codeConst.index >= 259) + { + arg->u.codeConst.index -= 171; + } + else if (arg->u.codeConst.index >= 197) + { + arg->u.codeConst.index -= 139; + } + } + } + else if (Zones::Version() == 460 /*|| Zones::Version() == 446*/) // 446 is no longer compatible, needs correct mappings { static std::unordered_map mapped_constants = { { 22, 21 }, @@ -1655,18 +1791,48 @@ namespace Components image->delayLoadPixels = image359.loaded; image->name = image359.name; + FixImageCategory(image); + // Used for later stuff (&image->delayLoadPixels)[1] = image359.pad3[1]; } else { std::memcpy(buffer + 28, buffer + (size - 4), 4); + + Game::GfxImage* image = reinterpret_cast(buffer); + FixImageCategory(image); } } return result; } + void Zones::FixImageCategory(Game::GfxImage* image) { + // CODO makes use of additional enumerator values (9, 10, 11) that don't exist in IW4 + // We have to translate them. 9 is for Reflection probes, 11 is for Compass, 10 is for Lightmap + switch (image->category) + { + case 9: + image->category = Game::ImageCategory::IMG_CATEGORY_AUTO_GENERATED; + break; + case 10: + image->category = Game::ImageCategory::IMG_CATEGORY_LIGHTMAP; + break; + case 11: + image->category = Game::ImageCategory::IMG_CATEGORY_LOAD_FROM_FILE; + break; + } + + + if (image->category > 7 || image->category < 0) { + +#ifdef DEBUG + if (IsDebuggerPresent()) __debugbreak(); +#endif + } + } + bool Zones::LoadXAsset(bool atStreamStart, char* buffer, int size) { int count = 0; @@ -1711,8 +1877,8 @@ namespace Components { // 359 and above adds an extra remapped techset ptr if (Zones::ZoneVersion >= 359) size += 4; - // 448 amd above adds an additional technique - if (Zones::ZoneVersion >= 448) size += 4; + // 446 amd above adds an additional technique + if (Zones::ZoneVersion >= 446) size += 4; bool result = Game::Load_Stream(atStreamStart, buffer, size); @@ -1724,22 +1890,22 @@ namespace Components // As MW2 flags are only 1 byte large, this won't be possible anyways int shiftTest = 4; - std::memmove(buffer + 8 + shiftTest, buffer + 12 + shiftTest, (Zones::Version() >= 448) ? 200 : 196 - shiftTest); - AssetHandler::Relocate(buffer + 12 + shiftTest, buffer + 8 + shiftTest, (Zones::Version() >= 448) ? 200 : 196 - shiftTest); + std::memmove(buffer + 8 + shiftTest, buffer + 12 + shiftTest, (Zones::Version() >= 446) ? 200 : 196 - shiftTest); + AssetHandler::Relocate(buffer + 12 + shiftTest, buffer + 8 + shiftTest, (Zones::Version() >= 446) ? 200 : 196 - shiftTest); } return result; } int Zones::LoadMaterialTechniqueArray(bool atStreamStart, int count) { - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { count += 1; } auto retval = Utils::Hook::Call(0x497020)(atStreamStart, count); - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { auto lastTechnique = **reinterpret_cast(0x112AEDC); auto varMaterialTechniqueSet = **reinterpret_cast(0x112B070); @@ -1753,11 +1919,11 @@ namespace Components bool Zones::LoadMaterial(bool atStreamStart, char* buffer, int size) { - // if (Zones::ZoneVersion >= 448 && currentAssetType == Game::ASSET_TYPE_XMODEL) __debugbreak(); + // if (Zones::ZoneVersion >= 446 && currentAssetType == Game::ASSET_TYPE_XMODEL) __debugbreak(); - bool result = Game::Load_Stream(atStreamStart, buffer, (Zones::Version() >= 448) ? 104 : size); + bool result = Game::Load_Stream(atStreamStart, buffer, (Zones::Version() >= 446) ? 104 : size); - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { char codol_material[104]; memcpy(codol_material, buffer, 104); @@ -1960,7 +2126,7 @@ namespace Components void Zones::LoadImpactFx(bool atStreamStart, char* buffer, int size) { if (Zones::Version() >= 460) size = 0xB94; - else if (Zones::Version() >= 448) size = 0xA64; + else if (Zones::Version() >= 446) size = 0xA64; else if (Zones::Version() >= VERSION_ALPHA2) size = 0x8C0; Game::Load_Stream(atStreamStart, buffer, size); @@ -1977,7 +2143,7 @@ namespace Components int Zones::ImpactFxArrayCount() { - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { return 19; } @@ -2140,7 +2306,7 @@ namespace Components int Zones::LoadRandomFxGarbage(bool atStreamStart, char* buffer, int size) { int count = 0; - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { size /= 48; count = size; @@ -2149,7 +2315,7 @@ namespace Components const auto retval = Game::Load_Stream(atStreamStart, buffer, size); - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { for (auto i = 0; i < count; i++) { @@ -2228,7 +2394,7 @@ namespace Components { int count = 0; - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { size /= 12; count = size; @@ -2237,7 +2403,7 @@ namespace Components auto retval = Game::Load_Stream(atStreamStart, buffer, size); - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { for (int i = 0; i < count; i++) { @@ -2302,8 +2468,8 @@ namespace Components cmp dword ptr[eax], 0xFFFFFFFF; je loadAssetData; - // check if FF is below 448, still load data in that case - cmp Zones::ZoneVersion, 448; + // check if FF is below 446, still load data in that case + cmp Zones::ZoneVersion, 446; jl loadAssetData; // offset to pointer magic @@ -2396,7 +2562,9 @@ namespace Components int Zones::LoadMapEnts(bool atStreamStart, Game::MapEnts* buffer, int size) { - if (Zones::Version() >= 448) + EntitiesVersion = Zones::Version(); + + if (Zones::Version() >= 446) { size /= 44; size *= 36; @@ -2553,7 +2721,7 @@ namespace Components int Zones::LoadClipMap(bool atStreamStart) { - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { AssertOffset(codolClipMap_t, pInfo, 72); @@ -3109,8 +3277,8 @@ namespace Components cmp dword ptr[edx + 4], 0xFFFFFFFF; je loadAssetData; - // check if FF is below 448, still load data in that case - cmp Zones::ZoneVersion, 448; + // check if FF is below 446, still load data in that case + cmp Zones::ZoneVersion, 446; jl loadAssetData; // offset to pointer magic @@ -3141,8 +3309,8 @@ namespace Components cmp dword ptr[eax + 0Ch], 0xFFFFFFFF; je loadAssetData; - // check if FF is below 448, still load data in that case - cmp Zones::ZoneVersion, 448; + // check if FF is below 446, still load data in that case + cmp Zones::ZoneVersion, 446; jl loadAssetData; // offset to pointer magic @@ -3173,8 +3341,8 @@ namespace Components cmp dword ptr[eax + 14h], 0xFFFFFFFF; je loadAssetData; - // check if FF is below 448, still load data in that case - cmp Zones::ZoneVersion, 448; + // check if FF is below 446, still load data in that case + cmp Zones::ZoneVersion, 446; jl loadAssetData; // offset to pointer magic @@ -3243,11 +3411,11 @@ namespace Components void Zones::LoadXModelAsset(Game::XModel** asset) { - if (Zones::Version() >= 448) + if (Zones::Version() >= 446) { for (int i = 0; i < (*asset)->numLods; i++) { - if ((*asset)->lodInfo[i].surfs == nullptr && Zones::Version() >= 448) + if ((*asset)->lodInfo[i].surfs == nullptr && Zones::Version() >= 446) { const auto name = (*asset)->name; const auto fx_model = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_XMODEL, "void").model; @@ -3273,7 +3441,7 @@ namespace Components void Zones::LoadMaterialAsset(Game::Material** asset) { - if (asset && *asset && Zones::Version() >= 448) + if (asset && *asset && Zones::Version() >= 446) { static std::vector broken_materials = { "gfx_fxt_debris_wind_ash_z10", @@ -3322,7 +3490,30 @@ namespace Components Game::DB_PopStreamPos(); } + + char* Zones::ParseVision_Stub(const char** data_p) + { + auto token = Game::Com_Parse(data_p); + + if (vision_replace_list.find(token) != vision_replace_list.end()) + { + return vision_replace_list[token].data(); + } + + return token; + } + char* Zones::ParseShellShock_Stub(const char** data_p) + { + auto token = Game::Com_Parse(data_p); + if (shellshock_replace_list.find(token) != shellshock_replace_list.end()) + { + return shellshock_replace_list[token].data(); + } + return token; + } + + Zones::Zones() { Zones::ZoneVersion = 0; @@ -3468,6 +3659,9 @@ namespace Components Utils::Hook(0x4B8FF5, Zones::Loadsunflare_t, HOOK_CALL).install()->quick(); Utils::Hook(0x418998, Zones::GameMapSpPatchStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x427A1B, Zones::LoadPathDataTail, HOOK_JUMP).install()->quick(); + + Utils::Hook(0x4B4EA1, Zones::ParseShellShock_Stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4B4F0C, Zones::ParseShellShock_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x4F4D3B, [] () { @@ -3491,6 +3685,10 @@ namespace Components Utils::Hook(0x4597DD, Zones::LoadStatement, HOOK_CALL).install()->quick(); Utils::Hook(0x471A39, Zones::LoadWindowImage, HOOK_JUMP).install()->quick(); + // Fix newer vision file + Utils::Hook(0x59A849, ParseVision_Stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x59A8AD, ParseVision_Stub, HOOK_CALL).install()->quick(); + #ifdef DEBUG // Easy dirty disk debugging Utils::Hook::Set(0x4CF7F0, 0xC3CC); @@ -3498,10 +3696,5 @@ namespace Components Utils::Hook::Set(0x6B9602,0xCCCC); #endif } - - Zones::~Zones() - { - - } } #pragma optimize( "", on ) \ No newline at end of file diff --git a/src/Components/Modules/Zones.hpp b/src/Components/Modules/Zones.hpp index f97fba5e..615bc450 100644 --- a/src/Components/Modules/Zones.hpp +++ b/src/Components/Modules/Zones.hpp @@ -3,6 +3,7 @@ #define VERSION_ALPHA2 316 #define VERSION_ALPHA3 318//319 #define VERSION_ALPHA3_DEC 319 +#define VERSION_LATEST_CODO 461 namespace Components { @@ -17,16 +18,18 @@ namespace Components }; Zones(); - ~Zones(); static void SetVersion(int version); static int Version() { return Zones::ZoneVersion; }; + static int GetEntitiesZoneVersion() { return Zones::EntitiesVersion; }; + private: static int ZoneVersion; - + static int EntitiesVersion; + static int FxEffectIndex; static char* FxEffectStrings[64]; @@ -100,5 +103,8 @@ namespace Components static void LoadMaterialAsset(Game::Material** asset); static void LoadTracerDef(bool atStreamStart, Game::TracerDef* tracer, int size); static void LoadTracerDefFxEffect(); + static void FixImageCategory(Game::GfxImage* image); + static char* ParseShellShock_Stub(const char** data_p); + static char* ParseVision_Stub(const char** data_p); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 63e01a18..95a3c7e9 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Game { @@ -30,13 +30,20 @@ namespace Game BG_GetNumWeapons_t BG_GetNumWeapons = BG_GetNumWeapons_t(0x4F5CC0); BG_GetWeaponName_t BG_GetWeaponName = BG_GetWeaponName_t(0x4E6EC0); BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj = BG_LoadWeaponDef_LoadObj_t(0x57B5F0); + BG_GetWeaponDef_t BG_GetWeaponDef = BG_GetWeaponDef_t(0x440EB0); Cbuf_AddServerText_t Cbuf_AddServerText = Cbuf_AddServerText_t(0x4BB9B0); Cbuf_AddText_t Cbuf_AddText = Cbuf_AddText_t(0x404B20); + Cbuf_InsertText_t Cbuf_InsertText = Cbuf_InsertText_t(0x4940B0); + CG_NextWeapon_f_t CG_NextWeapon_f = CG_NextWeapon_f_t(0x449DE0); CG_GetClientNum_t CG_GetClientNum = CG_GetClientNum_t(0x433700); CG_PlayBoltedEffect_t CG_PlayBoltedEffect = CG_PlayBoltedEffect_t(0x00430E10); CG_GetBoneIndex_t CG_GetBoneIndex = CG_GetBoneIndex_t(0x00504F20); + CG_ScoresDown_f_t CG_ScoresDown_f = CG_ScoresDown_f_t(0x580370); + CG_ScoresUp_f_t CG_ScoresUp_f = CG_ScoresUp_f_t(0x5802C0); + CG_ScrollScoreboardUp_t CG_ScrollScoreboardUp = CG_ScrollScoreboardUp_t(0x47A5C0); + CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown = CG_ScrollScoreboardDown_t(0x493B50); CL_GetClientName_t CL_GetClientName = CL_GetClientName_t(0x4563D0); CL_IsCgameInitialized_t CL_IsCgameInitialized = CL_IsCgameInitialized_t(0x43EB20); @@ -65,9 +72,11 @@ namespace Game Com_Parse_t Com_Parse = Com_Parse_t(0x474D60); Com_MatchToken_t Com_MatchToken = Com_MatchToken_t(0x447130); Com_SetSlowMotion_t Com_SetSlowMotion = Com_SetSlowMotion_t(0x446E20); + Com_Quitf_t Com_Quit_f = Com_Quitf_t(0x4D4000); Con_DrawMiniConsole_t Con_DrawMiniConsole = Con_DrawMiniConsole_t(0x464F30); Con_DrawSolidConsole_t Con_DrawSolidConsole = Con_DrawSolidConsole_t(0x5A5040); + Con_CancelAutoComplete_t Con_CancelAutoComplete = Con_CancelAutoComplete_t(0x435580); DB_AllocStreamPos_t DB_AllocStreamPos = DB_AllocStreamPos_t(0x418380); DB_PushStreamPos_t DB_PushStreamPos = DB_PushStreamPos_t(0x458A20); @@ -98,22 +107,24 @@ namespace Game Dvar_RegisterFloat_t Dvar_RegisterFloat = Dvar_RegisterFloat_t(0x648440); Dvar_RegisterVec2_t Dvar_RegisterVec2 = Dvar_RegisterVec2_t(0x4F6070); Dvar_RegisterVec3_t Dvar_RegisterVec3 = Dvar_RegisterVec3_t(0x4EF8E0); - Dvar_RegisterVec4_t Dvar_RegisterVec4 = Dvar_RegisterVec4_t(0x4F28E0); + Dvar_RegisterVec4_t Dvar_RegisterVec4 = Dvar_RegisterVec4_t(0x471500); Dvar_RegisterInt_t Dvar_RegisterInt = Dvar_RegisterInt_t(0x479830); Dvar_RegisterEnum_t Dvar_RegisterEnum = Dvar_RegisterEnum_t(0x412E40); Dvar_RegisterString_t Dvar_RegisterString = Dvar_RegisterString_t(0x4FC7E0); - Dvar_RegisterColor_t Dvar_RegisterColor = Dvar_RegisterColor_t(0x4F28E0);//0x471500; + Dvar_RegisterColor_t Dvar_RegisterColor = Dvar_RegisterColor_t(0x4F28E0); Dvar_GetUnpackedColorByName_t Dvar_GetUnpackedColorByName = Dvar_GetUnpackedColorByName_t(0x406530); Dvar_FindVar_t Dvar_FindVar = Dvar_FindVar_t(0x4D5390); Dvar_InfoString_Big_t Dvar_InfoString_Big = Dvar_InfoString_Big_t(0x4D98A0); Dvar_SetCommand_t Dvar_SetCommand = Dvar_SetCommand_t(0x4EE430); + Dvar_DisplayableValue_t Dvar_DisplayableValue = Dvar_DisplayableValue_t(0x4B5530); Encode_Init_t Encode_Init = Encode_Init_t(0x462AB0); Field_Clear_t Field_Clear = Field_Clear_t(0x437EB0); FreeMemory_t FreeMemory = FreeMemory_t(0x4D6640); + Free_String_t Free_String = Free_String_t(0x470E80); FS_FileExists_t FS_FileExists = FS_FileExists_t(0x4DEFA0); FS_FreeFile_t FS_FreeFile = FS_FreeFile_t(0x4416B0); @@ -128,6 +139,7 @@ namespace Game FS_FCloseFile_t FS_FCloseFile = FS_FCloseFile_t(0x462000); FS_WriteFile_t FS_WriteFile = FS_WriteFile_t(0x426450); FS_Write_t FS_Write = FS_Write_t(0x4C06E0); + FS_Printf_t FS_Printf = FS_Printf_t(0x459320); FS_Read_t FS_Read = FS_Read_t(0x4A04C0); FS_Seek_t FS_Seek = FS_Seek_t(0x4A63D0); FS_FTell_t FS_FTell = FS_FTell_t(0x4E6760); @@ -135,6 +147,7 @@ namespace Game FS_Restart_t FS_Restart = FS_Restart_t(0x461A50); FS_BuildPathToFile_t FS_BuildPathToFile = FS_BuildPathToFile_t(0x4702C0); FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440); + FS_Delete_t FS_Delete = FS_Delete_t(0x48A5B0); G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540); G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840); @@ -147,6 +160,8 @@ namespace Game Info_ValueForKey_t Info_ValueForKey = Info_ValueForKey_t(0x47C820); Key_SetCatcher_t Key_SetCatcher = Key_SetCatcher_t(0x43BD00); + Key_RemoveCatcher_t Key_RemoveCatcher = Key_RemoveCatcher_t(0x408260); + Key_IsKeyCatcherActive_t Key_IsKeyCatcherActive = Key_IsKeyCatcherActive_t(0x4DA010); LargeLocalInit_t LargeLocalInit = LargeLocalInit_t(0x4A62A0); @@ -174,8 +189,12 @@ namespace Game Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240); Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0); Menus_MenuIsInStack_t Menus_MenuIsInStack = Menus_MenuIsInStack_t(0x47ACB0); + Menu_HandleKey_t Menu_HandleKey = Menu_HandleKey_t(0x4C4A00); + Menu_GetFocused_t Menu_GetFocused = Menu_GetFocused_t(0x4AFF10); MSG_Init_t MSG_Init = MSG_Init_t(0x45FCA0); + MSG_ReadBit_t MSG_ReadBit = MSG_ReadBit_t(0x476D20); + MSG_ReadBits_t MSG_ReadBits = MSG_ReadBits_t(0x4C3900); MSG_ReadData_t MSG_ReadData = MSG_ReadData_t(0x4527C0); MSG_ReadLong_t MSG_ReadLong = MSG_ReadLong_t(0x4C9550); MSG_ReadShort_t MSG_ReadShort = MSG_ReadShort_t(0x40BDD0); @@ -271,10 +290,15 @@ namespace Game SE_Load_t SE_Load = SE_Load_t(0x502A30); SEH_StringEd_GetString_t SEH_StringEd_GetString = SEH_StringEd_GetString_t(0x44BB30); + SEH_ReadCharFromString_t SEH_ReadCharFromString = SEH_ReadCharFromString_t(0x486560); Dvar_SetFromStringByName_t Dvar_SetFromStringByName = Dvar_SetFromStringByName_t(0x4F52E0); Dvar_SetFromStringByNameFromSource_t Dvar_SetFromStringByNameFromSource = Dvar_SetFromStringByNameFromSource_t(0x4FC770); Dvar_SetStringByName_t Dvar_SetStringByName = Dvar_SetStringByName_t(0x44F060); + Dvar_SetString_t Dvar_SetString = Dvar_SetString_t(0x4A9580); + Dvar_SetBool_t Dvar_SetBool = Dvar_SetBool_t(0x4A9510); + Dvar_SetFloat_t Dvar_SetFloat = Dvar_SetFloat_t(0x40BB20); + Dvar_SetInt_t Dvar_SetInt = Dvar_SetInt_t(0x421DA0); SL_ConvertToString_t SL_ConvertToString = SL_ConvertToString_t(0x4EC1D0); SL_GetString_t SL_GetString = SL_GetString_t(0x4CDC10); @@ -294,10 +318,13 @@ namespace Game SV_GameSendServerCommand_t SV_GameSendServerCommand = SV_GameSendServerCommand_t(0x4BC3A0); SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString = SV_Cmd_TokenizeString_t(0x4B5780); SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString = SV_Cmd_EndTokenizedString_t(0x464750); + SV_Cmd_ArgvBuffer_t SV_Cmd_ArgvBuffer = SV_Cmd_ArgvBuffer_t(0x40BB60); SV_DirectConnect_t SV_DirectConnect = SV_DirectConnect_t(0x460480); SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0); SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0); SV_ClientThink_t SV_ClientThink = SV_ClientThink_t(0x44ADD0); + SV_GetPlayerByName_t SV_GetPlayerByName = SV_GetPlayerByName_t(0x6242B0); + SV_GetPlayerByNum_t SV_GetPlayerByNum = SV_GetPlayerByNum_t(0x624390); Sys_Error_t Sys_Error = Sys_Error_t(0x4E0200); Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580); @@ -312,41 +339,71 @@ namespace Game Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads = Sys_SuspendOtherThreads_t(0x45A190); Sys_ListFiles_t Sys_ListFiles = Sys_ListFiles_t(0x45A660); Sys_Milliseconds_t Sys_Milliseconds = Sys_Milliseconds_t(0x42A660); + Sys_LockWrite_t Sys_LockWrite = Sys_LockWrite_t(0x435880); + Sys_TempPriorityAtLeastNormalBegin_t Sys_TempPriorityAtLeastNormalBegin = Sys_TempPriorityAtLeastNormalBegin_t(0x478680); + Sys_TempPriorityEnd_t Sys_TempPriorityEnd = Sys_TempPriorityEnd_t(0x4DCF00); TeleportPlayer_t TeleportPlayer = TeleportPlayer_t(0x496850); UI_AddMenuList_t UI_AddMenuList = UI_AddMenuList_t(0x4533C0); + UI_GetActiveMenu_t UI_GetActiveMenu = UI_GetActiveMenu_t(0x4BE790); UI_CheckStringTranslation_t UI_CheckStringTranslation = UI_CheckStringTranslation_t(0x4FB010); UI_LoadMenus_t UI_LoadMenus = UI_LoadMenus_t(0x641460); UI_UpdateArenas_t UI_UpdateArenas = UI_UpdateArenas_t(0x4A95B0); UI_SortArenas_t UI_SortArenas = UI_SortArenas_t(0x630AE0); UI_DrawHandlePic_t UI_DrawHandlePic = UI_DrawHandlePic_t(0x4D0EA0); - UI_GetContext_t UI_GetContext = UI_GetContext_t(0x4F8940); + ScrPlace_GetActivePlacement_t ScrPlace_GetActivePlacement = ScrPlace_GetActivePlacement_t(0x4F8940); UI_TextWidth_t UI_TextWidth = UI_TextWidth_t(0x6315C0); UI_DrawText_t UI_DrawText = UI_DrawText_t(0x49C0D0); + UI_GetFontHandle_t UI_GetFontHandle = UI_GetFontHandle_t(0x4AEA60); + ScrPlace_ApplyRect_t ScrPlace_ApplyRect = ScrPlace_ApplyRect_t(0x454E20); + UI_KeyEvent_t UI_KeyEvent = UI_KeyEvent_t(0x4970F0); + UI_SafeTranslateString_t UI_SafeTranslateString = UI_SafeTranslateString_t(0x4F1700); + UI_ReplaceConversions_t UI_ReplaceConversions = UI_ReplaceConversions_t(0x4E9740); Win_GetLanguage_t Win_GetLanguage = Win_GetLanguage_t(0x45CBA0); Vec3UnpackUnitVec_t Vec3UnpackUnitVec = Vec3UnpackUnitVec_t(0x45CA90); + vectoyaw_t vectoyaw = vectoyaw_t(0x45AD10); + AngleNormalize360_t AngleNormalize360 = AngleNormalize360_t(0x438DC0); unzClose_t unzClose = unzClose_t(0x41BF20); + RB_DrawCursor_t RB_DrawCursor = RB_DrawCursor_t(0x534EA0); + + R_NormalizedTextScale_t R_NormalizedTextScale = R_NormalizedTextScale_t(0x5056A0); + + Material_Process2DTextureCoordsForAtlasing_t Material_Process2DTextureCoordsForAtlasing = Material_Process2DTextureCoordsForAtlasing_t(0x506090); + + Byte4PackRgba_t Byte4PackRgba = Byte4PackRgba_t(0x4FE910); + RandWithSeed_t RandWithSeed = RandWithSeed_t(0x495580); + GetDecayingLetterInfo_t GetDecayingLetterInfo = GetDecayingLetterInfo_t(0x5351C0); + + Field_Draw_t Field_Draw = Field_Draw_t(0x4F5B40); + Field_AdjustScroll_t Field_AdjustScroll = Field_AdjustScroll_t(0x488C10); + AimAssist_ApplyAutoMelee_t AimAssist_ApplyAutoMelee = AimAssist_ApplyAutoMelee_t(0x56A360); + + Weapon_RocketLauncher_Fire_t Weapon_RocketLauncher_Fire = Weapon_RocketLauncher_Fire_t(0x424680); + + Jump_ClearState_t Jump_ClearState = Jump_ClearState_t(0x04B3890); + PM_playerTrace_t PM_playerTrace = PM_playerTrace_t(0x458980); + PM_Trace_t PM_Trace = PM_Trace_t(0x441F60); + PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540); + XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); - DWORD* cmd_id = reinterpret_cast(0x1AAC5D0); - DWORD* cmd_argc = reinterpret_cast(0x1AAC614); - char*** cmd_argv = reinterpret_cast(0x1AAC634); - - DWORD* cmd_id_sv = reinterpret_cast(0x1ACF8A0); - DWORD* cmd_argc_sv = reinterpret_cast(0x1ACF8E4); - char*** cmd_argv_sv = reinterpret_cast(0x1ACF904); + CmdArgs* cmd_args = reinterpret_cast(0x1AAC5D0); + CmdArgs* sv_cmd_args = reinterpret_cast(0x1ACF8A0); cmd_function_t** cmd_functions = reinterpret_cast(0x1AAC658); source_t **sourceFiles = reinterpret_cast(0x7C4A98); keywordHash_t **menuParseKeywordHash = reinterpret_cast(0x63AE928); + float* cl_angles = reinterpret_cast(0xB2F8D0); + float* cgameFOVSensitivityScale = reinterpret_cast(0xB2F884); + int* svs_time = reinterpret_cast(0x31D9384); int* svs_numclients = reinterpret_cast(0x31D938C); client_t* svs_clients = reinterpret_cast(0x31D9390); @@ -415,10 +472,51 @@ 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); + GfxScene* scene = reinterpret_cast(0x6944914); + + ConDrawInputGlob* conDrawInputGlob = reinterpret_cast(0x9FD6F8); + field_t* g_consoleField = reinterpret_cast(0xA1B6B0); + + clientStatic_t* cls = reinterpret_cast(0xA7FE90); + + sharedUiInfo_t* sharedUiInfo = reinterpret_cast(0x62E4B78); + ScreenPlacement* scrPlaceFull = reinterpret_cast(0x10843F0); + ScreenPlacement* scrPlaceView = reinterpret_cast(0x1084378); + + clientActive_t* clients = reinterpret_cast(0xB2C698); + + cg_s* cgArray = reinterpret_cast(0x7F0F78); + cgs_t* cgsArray = reinterpret_cast(0x7ED3B8); + + PlayerKeyState* playerKeys = reinterpret_cast(0xA1B7D0); + kbutton_t* playersKb = reinterpret_cast(0xA1A9A8); + AimAssistGlobals* aaGlobArray = reinterpret_cast(0x7A2110); + + keyname_t* keyNames = reinterpret_cast(0x798580); + keyname_t* localizedKeyNames = reinterpret_cast(0x798880); + + GraphFloat* aaInputGraph = reinterpret_cast(0x7A2FC0); + + FastCriticalSection* db_hashCritSect = reinterpret_cast(0x16B8A54); + + vec3_t* CorrectSolidDeltas = reinterpret_cast(0x739BB8); // Count 26 + + void Sys_LockRead(FastCriticalSection* critSect) + { + InterlockedIncrement(&critSect->readCount); + while (critSect->writeCount) std::this_thread::sleep_for(1ms); + } + + void Sys_UnlockRead(FastCriticalSection* critSect) + { + assert(critSect->readCount > 0); + InterlockedDecrement(&critSect->readCount); + } + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) { int elSize = DB_GetXAssetSizeHandlers[type](); @@ -533,46 +631,38 @@ namespace Game return false; } - void DB_EnumXAssetEntries(XAssetType type, std::function callback, bool overrides, bool lock) + void DB_EnumXAssetEntries(XAssetType type, std::function callback, bool overrides) { - volatile long* lockVar = reinterpret_cast(0x16B8A54); - if (lock) InterlockedIncrement(lockVar); + Sys_LockRead(db_hashCritSect); - while (lock && *reinterpret_cast(0x16B8A58)) std::this_thread::sleep_for(1ms); - - unsigned int index = 0; - do + const auto pool = Components::Maps::GetAssetEntryPool(); + for(auto hash = 0; hash < 37000; hash++) { - unsigned short hashIndex = db_hashTable[index]; - if (hashIndex) + auto hashIndex = db_hashTable[hash]; + while(hashIndex) { - do + auto* assetEntry = &pool[hashIndex]; + + if(assetEntry->asset.type == type) { - XAssetEntry* asset = &Components::Maps::GetAssetEntryPool()[hashIndex]; - hashIndex = asset->nextHash; - if (asset->asset.type == type) + callback(assetEntry); + if (overrides) { - callback(asset); - if (overrides) + auto overrideIndex = assetEntry->nextOverride; + while (overrideIndex) { - unsigned short overrideIndex = asset->nextOverride; - if (asset->nextOverride) - { - do - { - asset = &Components::Maps::GetAssetEntryPool()[overrideIndex]; - callback(asset); - overrideIndex = asset->nextOverride; - } while (overrideIndex); - } + auto* overrideEntry = &pool[overrideIndex]; + callback(overrideEntry); + overrideIndex = overrideEntry->nextOverride; } } - } while (hashIndex); - } - ++index; - } while (index < 74000); + } - if(lock) InterlockedDecrement(lockVar); + hashIndex = assetEntry->nextHash; + } + } + + Sys_UnlockRead(db_hashCritSect); } // this cant be MessageBox because windows.h has a define that converts it to MessageBoxW. which is just stupid @@ -582,7 +672,7 @@ namespace Game { Dvar_SetStringByName("com_errorMessage", message.data()); Dvar_SetStringByName("com_errorTitle", title.data()); - Cbuf_AddText(0, "openmenu error_popmenu_lobby"); + Cbuf_AddText(0, "openmenu error_popmenu_lobby\n"); } } @@ -599,11 +689,25 @@ namespace Game return hash; } + unsigned int R_HashString(const char* string, size_t maxLen) + { + unsigned int hash = 0; + + while (*string && maxLen > 0) + { + hash = (*string | 0x20) ^ (33 * hash); + ++string; + maxLen--; + } + + return hash; + } + void SV_KickClientError(client_t* client, const std::string& reason) { if (client->state < 5) { - Components::Network::SendCommand(client->addr, "error", reason); + Components::Network::SendCommand(client->netchan.remoteAddress, "error", reason); } SV_KickClient(client, reason.data()); @@ -691,12 +795,31 @@ namespace Game return atoi(StringTable_Lookup(rankTable, 0, maxrank, 7)); } - void Vec3Normalize(vec3_t& vec) + float Vec2Normalize(vec2_t& vec) { - const float length = static_cast(std::sqrt(std::pow(vec[0], 2) + std::pow(vec[1], 2) + std::pow(vec[2], 2))); - vec[0] /= length; - vec[1] /= length; - vec[2] /= length; + const auto length = std::sqrt(vec[0] * vec[0] + vec[1] * vec[1]); + + if (length > 0.0f) + { + vec[0] /= length; + vec[1] /= length; + } + + return length; + } + + float Vec3Normalize(vec3_t& vec) + { + const auto length = std::sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]); + + if (length > 0.0f) + { + vec[0] /= length; + vec[1] /= length; + vec[2] /= length; + } + + return length; } void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out) @@ -728,6 +851,29 @@ namespace Game std::memmove(&solution[0], &res[0], sizeof(res)); } + void QuatRot(vec3_t* vec, const vec4_t* quat) + { + vec4_t q{ (*quat)[3],(*quat)[0],(*quat)[1],(*quat)[2] }; + + vec4_t res{ 0, (*vec)[0], (*vec)[1], (*vec)[2] }; + vec4_t res2; + vec4_t quat_conj{ q[0], -q[1], -q[2], -q[3] }; + QuatMultiply(&q, &res, &res2); + QuatMultiply(&res2, &quat_conj, &res); + + (*vec)[0] = res[1]; + (*vec)[1] = res[2]; + (*vec)[2] = res[3]; + } + + void QuatMultiply(const vec4_t* q1, const vec4_t* q2, vec4_t* res) + { + (*res)[0] = (*q2)[0] * (*q1)[0] - (*q2)[1] * (*q1)[1] - (*q2)[2] * (*q1)[2] - (*q2)[3] * (*q1)[3]; + (*res)[1] = (*q2)[0] * (*q1)[1] + (*q2)[1] * (*q1)[0] - (*q2)[2] * (*q1)[3] + (*q2)[3] * (*q1)[2]; + (*res)[2] = (*q2)[0] * (*q1)[2] + (*q2)[1] * (*q1)[3] + (*q2)[2] * (*q1)[0] - (*q2)[3] * (*q1)[1]; + (*res)[3] = (*q2)[0] * (*q1)[3] - (*q2)[1] * (*q1)[2] + (*q2)[2] * (*q1)[1] + (*q2)[3] * (*q1)[0]; + } + void SortWorldSurfaces(GfxWorld* world) { DWORD* specular1 = reinterpret_cast(0x69F105C); @@ -806,6 +952,91 @@ namespace Game Game::R_AddDebugLine(color, v4, v8); } + void R_AddDebugBounds(float* color, Bounds* b, const float(*quat)[4]) + { + vec3_t v[8]; + auto* center = b->midPoint; + auto* halfSize = b->halfSize; + + v[0][0] = -halfSize[0]; + v[0][1] = -halfSize[1]; + v[0][2] = -halfSize[2]; + + v[1][0] = halfSize[0]; + v[1][1] = -halfSize[1]; + v[1][2] = -halfSize[2]; + + v[2][0] = -halfSize[0]; + v[2][1] = halfSize[1]; + v[2][2] = -halfSize[2]; + + v[3][0] = halfSize[0]; + v[3][1] = halfSize[1]; + v[3][2] = -halfSize[2]; + + v[4][0] = -halfSize[0]; + v[4][1] = -halfSize[1]; + v[4][2] = halfSize[2]; + + v[5][0] = halfSize[0]; + v[5][1] = -halfSize[1]; + v[5][2] = halfSize[2]; + + v[6][0] = -halfSize[0]; + v[6][1] = halfSize[1]; + v[6][2] = halfSize[2]; + + v[7][0] = halfSize[0]; + v[7][1] = halfSize[1]; + v[7][2] = halfSize[2]; + + for(auto& vec : v) + { + QuatRot(&vec, quat); + vec[0] += center[0]; + vec[1] += center[1]; + vec[2] += center[2]; + } + + // bottom + Game::R_AddDebugLine(color, v[0], v[1]); + Game::R_AddDebugLine(color, v[1], v[3]); + Game::R_AddDebugLine(color, v[3], v[2]); + Game::R_AddDebugLine(color, v[2], v[0]); + + // top + Game::R_AddDebugLine(color, v[4], v[5]); + Game::R_AddDebugLine(color, v[5], v[7]); + Game::R_AddDebugLine(color, v[7], v[6]); + Game::R_AddDebugLine(color, v[6], v[4]); + + // verticals + Game::R_AddDebugLine(color, v[0], v[4]); + Game::R_AddDebugLine(color, v[1], v[5]); + Game::R_AddDebugLine(color, v[2], v[6]); + Game::R_AddDebugLine(color, v[3], v[7]); + } + + float GraphGetValueFromFraction(const int knotCount, const float(*knots)[2], const float fraction) + { + for (auto knotIndex = 1; knotIndex < knotCount; ++knotIndex) + { + if (knots[knotIndex][0] >= fraction) + { + const auto adjustedFraction = (fraction - knots[knotIndex - 1][0]) / (knots[knotIndex][0] - knots[knotIndex - 1][0]); + + return (knots[knotIndex][1] - knots[knotIndex - 1][1]) * adjustedFraction + knots[knotIndex - 1][1]; + } + } + + return -1.0f; + } + + float GraphFloat_GetValue(const GraphFloat* graph, const float fraction) + { + return GraphGetValueFromFraction(graph->knotCount, graph->knots, fraction) * graph->scale; + } + #pragma optimize("", off) __declspec(naked) float UI_GetScoreboardLeft(void* /*a1*/) { @@ -869,6 +1100,21 @@ namespace Game } } + bool PM_IsAdsAllowed(Game::playerState_s* playerState) + { + bool result; + + __asm + { + mov esi, playerState + mov ebx, 0x5755A0 + call ebx + mov result, al // AL + } + + return result; + } + __declspec(naked) void FS_AddLocalizedGameDirectory(const char* /*path*/, const char* /*dir*/) { __asm @@ -1043,6 +1289,32 @@ namespace Game } } + void Menu_SetNextCursorItem(Game::UiContext* a1, Game::menuDef_t* a2, int unk) + { + __asm + { + push unk + push a2 + mov eax, a1 + mov ebx, 0x639FE0 + call ebx + add esp, 0x8 // 2 args = 2x4 + } + } + + void Menu_SetPrevCursorItem(Game::UiContext* a1, Game::menuDef_t* a2, int unk) + { + __asm + { + push unk + push a2 + mov eax, a1 + mov ebx, 0x639F20 + call ebx + add esp, 0x8 // 2 args = 2x4 + } + } + __declspec(naked) void R_AddDebugLine(float* /*color*/, float* /*v1*/, float* /*v2*/) { __asm @@ -1102,5 +1374,171 @@ namespace Game retn } } + + __declspec(naked) Glyph* R_GetCharacterGlyph(Font_s* /*font*/, unsigned int /*letter*/) + { + __asm + { + push eax + pushad + + mov edi, [esp + 0x8 + 0x24] // letter + push [esp + 0x4 + 0x24] // font + mov eax, 0x5055C0 + call eax + add esp, 4 + mov [esp + 0x20], eax + + popad + pop eax + ret + } + } + + __declspec(naked) bool SetupPulseFXVars(const char* /*text*/, int /*maxLength*/, int /*fxBirthTime*/, int /*fxLetterTime*/, int /*fxDecayStartTime*/, int /*fxDecayDuration*/, bool* /*resultDrawRandChar*/, int* /*resultRandSeed*/, int* /*resultMaxLength*/, bool* /*resultDecaying*/, int* /*resultDecayTimeElapsed*/) + { + __asm + { + push eax + pushad + + mov eax, [esp + 0x08 + 0x24] // maxLength + push [esp + 0x2C + 0x24] // resultDecayTimeElapsed + push [esp + 0x2C + 0x24] // resultDecaying + push [esp + 0x2C + 0x24] // resultMaxLength + push [esp + 0x2C + 0x24] // resultRandSeed + push [esp + 0x2C + 0x24] // resultDrawRandChar + push [esp + 0x2C + 0x24] // fxDecayDuration + push [esp + 0x2C + 0x24] // fxDecayStartTime + push [esp + 0x2C + 0x24] // fxLetterTime + push [esp + 0x2C + 0x24] // fxBirthTime + push [esp + 0x28 + 0x24] // text + mov ebx, 0x535050 + call ebx + add esp, 0x28 + mov [esp + 0x20],eax + + popad + pop eax + ret + } + } + + __declspec(naked) void RB_DrawChar(Material* /*material*/, float /*x*/, float /*y*/, float /*w*/, float /*h*/, float /*sinAngle*/, float /*cosAngle*/, Glyph* /*glyph*/, unsigned int /*color*/) + { + __asm + { + pushad + + mov eax, [esp + 0x4 + 0x20] // material + mov edx, [esp + 0x20 + 0x20] // glyph + push [esp + 0x24 + 0x20] // color + push [esp + 0x20 + 0x20] // cosAngle + push [esp + 0x20 + 0x20] // sinAngle + push [esp + 0x20 + 0x20] // h + push [esp + 0x20 + 0x20] // w + push [esp + 0x20 + 0x20] // y + push [esp + 0x20 + 0x20] // x + + mov ecx, 0x534E20 + call ecx + add esp, 0x1C + + popad + ret + } + } + + __declspec(naked) void RB_DrawStretchPicRotate(Material* /*material*/, float /*x*/, float /*y*/, float /*w*/, float /*h*/, float /*s0*/, float /*t0*/, float /*s1*/, float /*t1*/, float /*sinAngle*/, float /*cosAngle*/, unsigned int /*color*/) + { + __asm + { + pushad + + mov eax, [esp + 0x4 + 0x20] // material + push [esp + 0x30 + 0x20] // color + push [esp + 0x30 + 0x20] // cosAngle + push [esp + 0x30 + 0x20] // sinAngle + push [esp + 0x30 + 0x20] // t1 + push [esp + 0x30 + 0x20] // s1 + push [esp + 0x30 + 0x20] // t0 + push [esp + 0x30 + 0x20] // s0 + push [esp + 0x30 + 0x20] // h + push [esp + 0x30 + 0x20] // w + push [esp + 0x30 + 0x20] // y + push [esp + 0x30 + 0x20] // x + mov ebx, 0x5310F0 + call ebx + add esp, 0x2C + + popad + ret + } + } + + __declspec(naked) char ModulateByteColors(char /*colorA*/, char /*colorB*/) + { + __asm + { + push eax + pushad + + mov eax, [esp + 0x4 + 0x24] // colorA + mov ecx, [esp + 0x8 + 0x24] // colorB + mov ebx, 0x5353C0 + call ebx + mov [esp + 0x20], eax + + popad + pop eax + ret + } + } + + __declspec(naked) void AimAssist_UpdateTweakables(int /*localClientNum*/) + { + __asm + { + mov eax,[esp+0x4] + mov ebx,0x569950 + call ebx + retn + } + } + + __declspec(naked) void AimAssist_UpdateAdsLerp(const AimInput* /*aimInput*/) + { + __asm + { + mov eax, [esp + 0x4] + mov ebx, 0x569AA0 + call ebx + retn + } + } + + __declspec(naked) void Dvar_SetVariant(dvar_t*, DvarValue, DvarSetSource) + { + __asm + { + pushad + + mov eax, [esp + 0x4 + 0x20] // dvar + push[esp + 0x18 + 0x20] // source + push[esp + 0x18 + 0x20] // value + push[esp + 0x18 + 0x20] // value + push[esp + 0x18 + 0x20] // value + push[esp + 0x18 + 0x20] // value + + mov ebx, 0x647400 + call ebx + add esp, 0x14 + + popad + + retn + } + } + #pragma optimize("", on) } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 8b125c82..5ec72a9b 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -40,20 +40,41 @@ namespace Game typedef void*(__cdecl * BG_LoadWeaponDef_LoadObj_t)(const char* filename); extern BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj; + typedef WeaponDef* (__cdecl * BG_GetWeaponDef_t)(int weaponIndex); + extern BG_GetWeaponDef_t BG_GetWeaponDef; + typedef void(__cdecl * Cbuf_AddServerText_t)(); extern Cbuf_AddServerText_t Cbuf_AddServerText; - typedef void(__cdecl * Cbuf_AddText_t)(int localClientNum, const char *text); + typedef void(__cdecl * Cbuf_AddText_t)(int localClientNum, const char* text); extern Cbuf_AddText_t Cbuf_AddText; + typedef void(__cdecl * Cbuf_InsertText_t)(int localClientNum, const char* text); + extern Cbuf_InsertText_t Cbuf_InsertText; + typedef int(__cdecl * CG_GetClientNum_t)(); extern CG_GetClientNum_t CG_GetClientNum; - typedef std::int32_t(__cdecl* CG_PlayBoltedEffect_t) (std::int32_t, FxEffectDef*, std::int32_t, std::uint32_t); + typedef void(__cdecl * CG_NextWeapon_f_t)(); + extern CG_NextWeapon_f_t CG_NextWeapon_f; + + typedef std::int32_t(__cdecl * CG_PlayBoltedEffect_t) (std::int32_t, FxEffectDef*, std::int32_t, std::uint32_t); extern CG_PlayBoltedEffect_t CG_PlayBoltedEffect; - typedef std::int32_t(__cdecl* CG_GetBoneIndex_t)(std::int32_t, std::uint32_t name, char* index); + typedef std::int32_t(__cdecl * CG_GetBoneIndex_t)(std::int32_t, std::uint32_t name, char* index); extern CG_GetBoneIndex_t CG_GetBoneIndex; + + typedef void(__cdecl * CG_ScoresDown_f_t)(); + extern CG_ScoresDown_f_t CG_ScoresDown_f; + + typedef void(__cdecl * CG_ScoresUp_f_t)(); + extern CG_ScoresUp_f_t CG_ScoresUp_f; + + typedef void(__cdecl * CG_ScrollScoreboardUp_t)(cg_s* cgameGlob); + extern CG_ScrollScoreboardUp_t CG_ScrollScoreboardUp; + + typedef void(__cdecl * CG_ScrollScoreboardDown_t)(cg_s* cgameGlob); + extern CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown; typedef char*(__cdecl * CL_GetClientName_t)(int localClientNum, int index, char *buf, size_t size); extern CL_GetClientName_t CL_GetClientName; @@ -103,7 +124,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, ...); @@ -115,13 +136,13 @@ namespace Game typedef void(__cdecl * Com_EndParseSession_t)(); extern Com_EndParseSession_t Com_EndParseSession; - typedef void(__cdecl * Com_BeginParseSession_t)(const char* why); + typedef void(__cdecl * Com_BeginParseSession_t)(const char* filename); extern Com_BeginParseSession_t Com_BeginParseSession; typedef void(__cdecl * Com_SetSpaceDelimited_t)(int); extern Com_SetSpaceDelimited_t Com_SetSpaceDelimited; - typedef char* (__cdecl * Com_Parse_t)(const char **data_p); + typedef char*(__cdecl * Com_Parse_t)(const char** data_p); extern Com_Parse_t Com_Parse; typedef bool (__cdecl * Com_MatchToken_t)(const char **data_p, const char* token, int size); @@ -130,12 +151,18 @@ namespace Game typedef void(__cdecl * Com_SetSlowMotion_t)(float start, float end, int duration); extern Com_SetSlowMotion_t Com_SetSlowMotion; + typedef void(__cdecl * Com_Quitf_t)(); + extern Com_Quitf_t Com_Quit_f; + typedef char* (__cdecl * Con_DrawMiniConsole_t)(int localClientNum, int xPos, int yPos, float alpha); extern Con_DrawMiniConsole_t Con_DrawMiniConsole; typedef void (__cdecl * Con_DrawSolidConsole_t)(); extern Con_DrawSolidConsole_t Con_DrawSolidConsole; + typedef bool(__cdecl * Con_CancelAutoComplete_t)(); + extern Con_CancelAutoComplete_t Con_CancelAutoComplete; + typedef char *(__cdecl *DB_AllocStreamPos_t)(int alignment); extern DB_AllocStreamPos_t DB_AllocStreamPos; @@ -205,54 +232,69 @@ namespace Game typedef void(__cdecl * DB_XModelSurfsFixup_t)(XModel* model); extern DB_XModelSurfsFixup_t DB_XModelSurfsFixup; - typedef dvar_t* (__cdecl * Dvar_RegisterBool_t)(const char* name, bool defaultVal, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterBool_t)(const char* dvarName, bool value, unsigned __int16 flags, const char* description); extern Dvar_RegisterBool_t Dvar_RegisterBool; - typedef dvar_t* (__cdecl * Dvar_RegisterFloat_t)(const char* name, float defaultVal, float min, float max, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterFloat_t)(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); extern Dvar_RegisterFloat_t Dvar_RegisterFloat; - typedef dvar_t* (__cdecl * Dvar_RegisterVec2_t)(const char* name, float defx, float defy, float min, float max, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterVec2_t)(const char* dvarName, float x, float y, float min, float max, unsigned __int16 flags, const char* description); extern Dvar_RegisterVec2_t Dvar_RegisterVec2; - typedef dvar_t* (__cdecl * Dvar_RegisterVec3_t)(const char* name, float defx, float defy, float defz, float min, float max, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterVec3_t)(const char* dvarName, float x, float y, float z, float min, float max, unsigned __int16 flags, const char* description); extern Dvar_RegisterVec3_t Dvar_RegisterVec3; - typedef dvar_t* (__cdecl * Dvar_RegisterVec4_t)(const char* name, float defx, float defy, float defz, float defw, float min, float max, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterVec4_t)(const char* dvarName, float x, float y, float z, float w, float min, float max, unsigned __int16 flags, const char* description); extern Dvar_RegisterVec4_t Dvar_RegisterVec4; - typedef dvar_t* (__cdecl * Dvar_RegisterInt_t)(const char* name, int defaultVal, int min, int max, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterInt_t)(const char* dvarName, int value, int min, int max, unsigned __int16 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* dvarName, const char** valueList, int defaultIndex, unsigned __int16 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*); + typedef dvar_t*(__cdecl * Dvar_RegisterString_t)(const char* dvarName, const char* value, unsigned __int16 flags, const char* description); extern Dvar_RegisterString_t Dvar_RegisterString; - typedef dvar_t* (__cdecl * Dvar_RegisterColor_t)(const char* name, float r, float g, float b, float a, int flags, const char* description); + typedef dvar_t*(__cdecl * Dvar_RegisterColor_t)(const char* dvarName, float r, float g, float b, float a, unsigned __int16 flags, const char* description); extern Dvar_RegisterColor_t Dvar_RegisterColor; - typedef dvar_t*(__cdecl * Dvar_SetFromStringByName_t)(const char* cvar, const char* value); + typedef void(__cdecl * Dvar_SetFromStringByName_t)(const char* dvarName, const char* string); extern Dvar_SetFromStringByName_t Dvar_SetFromStringByName; - typedef dvar_t*(__cdecl * Dvar_SetFromStringByNameFromSource_t)(const char* cvar, const char* value, DvarSetSource source); + typedef const dvar_t*(__cdecl * Dvar_SetFromStringByNameFromSource_t)(const char* dvarName, const char* string, DvarSetSource source); extern Dvar_SetFromStringByNameFromSource_t Dvar_SetFromStringByNameFromSource; - typedef void(__cdecl * Dvar_SetStringByName_t)(const char* cvar, const char* value); + typedef void(__cdecl * Dvar_SetStringByName_t)(const char* dvarName, const char* value); extern Dvar_SetStringByName_t Dvar_SetStringByName; - typedef void(__cdecl * Dvar_GetUnpackedColorByName_t)(const char* name, float* color); + typedef void(__cdecl * Dvar_SetString_t)(const dvar_t* dvar, const char* value); + extern Dvar_SetString_t Dvar_SetString; + + typedef void(__cdecl * Dvar_SetBool_t)(const dvar_t* dvar, bool enabled); + extern Dvar_SetBool_t Dvar_SetBool; + + typedef void(__cdecl * Dvar_SetFloat_t)(const dvar_t* dvar, float value); + extern Dvar_SetFloat_t Dvar_SetFloat; + + typedef void(__cdecl * Dvar_SetInt_t)(const dvar_t* dvar, int integer); + extern Dvar_SetInt_t Dvar_SetInt; + + typedef void(__cdecl * Dvar_GetUnpackedColorByName_t)(const char* dvarName, float* expandedColor); extern Dvar_GetUnpackedColorByName_t Dvar_GetUnpackedColorByName; - typedef dvar_t* (__cdecl * Dvar_FindVar_t)(const char *dvarName); + typedef dvar_t*(__cdecl * Dvar_FindVar_t)(const char* dvarName); extern Dvar_FindVar_t Dvar_FindVar; - typedef char* (__cdecl* Dvar_InfoString_Big_t)(int typeMask); + typedef char*(__cdecl * Dvar_InfoString_Big_t)(int bit); extern Dvar_InfoString_Big_t Dvar_InfoString_Big; - typedef dvar_t* (__cdecl * Dvar_SetCommand_t)(const char* name, const char* value); + typedef void(__cdecl * Dvar_SetCommand_t)(const char* dvarName, const char* string); extern Dvar_SetCommand_t Dvar_SetCommand; + typedef const char*(__cdecl * Dvar_DisplayableValue_t)(const dvar_t* dvar); + extern Dvar_DisplayableValue_t Dvar_DisplayableValue; + typedef bool(__cdecl * Encode_Init_t)(const char* ); extern Encode_Init_t Encode_Init; @@ -262,6 +304,9 @@ namespace Game typedef void(__cdecl * FreeMemory_t)(void* buffer); extern FreeMemory_t FreeMemory; + typedef void (__cdecl * Free_String_t)(const char* string); + extern Free_String_t Free_String; + typedef void(__cdecl * FS_FreeFile_t)(void* buffer); extern FS_FreeFile_t FS_FreeFile; @@ -296,6 +341,9 @@ namespace Game typedef int(__cdecl * FS_Write_t)(const void* buffer, size_t size, int file); extern FS_Write_t FS_Write; + typedef int(__cdecl * FS_Printf_t)(int file, const char* fmt, ...); + extern FS_Printf_t FS_Printf; + typedef int(__cdecl * FS_Read_t)(void* buffer, size_t size, int file); extern FS_Read_t FS_Read; @@ -317,6 +365,9 @@ namespace Game typedef iwd_t*(__cdecl * FS_IsShippedIWD_t)(const char* fullpath, const char* iwd); extern FS_IsShippedIWD_t FS_IsShippedIWD; + typedef int(__cdecl* FS_Delete_t)(const char* fileName); + extern FS_Delete_t FS_Delete; + typedef int(__cdecl* G_GetWeaponIndexForName_t)(char*); extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName; @@ -339,6 +390,12 @@ namespace Game typedef void(__cdecl * Key_SetCatcher_t)(int localClientNum, int catcher); extern Key_SetCatcher_t Key_SetCatcher; + typedef void(__cdecl * Key_RemoveCatcher_t)(int localClientNum, int andMask); + extern Key_RemoveCatcher_t Key_RemoveCatcher; + + typedef bool(__cdecl * Key_IsKeyCatcherActive_t)(int localClientNum, int catcher); + extern Key_IsKeyCatcherActive_t Key_IsKeyCatcherActive; + typedef void(__cdecl * LargeLocalInit_t)(); extern LargeLocalInit_t LargeLocalInit; @@ -411,6 +468,21 @@ namespace Game typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); extern Menus_MenuIsInStack_t Menus_MenuIsInStack; + typedef menuDef_t*(__cdecl * Menu_GetFocused_t)(UiContext* ctx); + extern Menu_GetFocused_t Menu_GetFocused; + + typedef void(__cdecl * Menu_HandleKey_t)(UiContext* ctx, menuDef_t* menu, Game::keyNum_t key, int down); + extern Menu_HandleKey_t Menu_HandleKey; + + typedef bool(__cdecl * UI_KeyEvent_t)(int clientNum, int key, int down); + extern UI_KeyEvent_t UI_KeyEvent; + + typedef const char* (__cdecl * UI_SafeTranslateString_t)(const char* reference); + extern UI_SafeTranslateString_t UI_SafeTranslateString; + + typedef void(__cdecl * UI_ReplaceConversions_t)(const char* sourceString, ConversionArguments* arguments, char* outputString, size_t outputStringSize); + extern UI_ReplaceConversions_t UI_ReplaceConversions; + typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); extern MSG_Init_t MSG_Init; @@ -420,6 +492,12 @@ namespace Game typedef int(__cdecl * MSG_ReadLong_t)(msg_t* msg); extern MSG_ReadLong_t MSG_ReadLong; + typedef int(__cdecl * MSG_ReadBit_t)(msg_t* msg); + extern MSG_ReadBit_t MSG_ReadBit; + + typedef int(__cdecl * MSG_ReadBits_t)(msg_t* msg, int bits); + extern MSG_ReadBits_t MSG_ReadBits; + typedef short(__cdecl * MSG_ReadShort_t)(msg_t* msg); extern MSG_ReadShort_t MSG_ReadShort; @@ -447,10 +525,10 @@ namespace Game typedef void(__cdecl * MSG_WriteLong_t)(msg_t *msg, int c); extern MSG_WriteLong_t MSG_WriteLong; - typedef void(*MSG_WriteShort_t)(msg_t* msg, short s); + typedef void(__cdecl * MSG_WriteShort_t)(msg_t* msg, short s); extern MSG_WriteShort_t MSG_WriteShort; - typedef void(*MSG_WriteString_t)(msg_t* msg, const char *str); + typedef void(__cdecl * MSG_WriteString_t)(msg_t* msg, const char *str); extern MSG_WriteString_t MSG_WriteString; typedef int(__cdecl * MSG_WriteBitsCompress_t)(bool trainHuffman, const char *from, char *to, int size); @@ -480,10 +558,10 @@ namespace Game typedef bool(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); extern NET_StringToAdr_t NET_StringToAdr; - typedef void(__cdecl* NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); + typedef void(__cdecl * NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); extern NET_OutOfBandPrint_t NET_OutOfBandPrint; - typedef void(__cdecl* NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char *format, int len); + typedef void(__cdecl * NET_OutOfBandData_t)(netsrc_t sock, netadr_t adr, const char *format, int len); extern NET_OutOfBandData_t NET_OutOfBandData; typedef void(__cdecl * Live_MPAcceptInvite_t)(_XSESSION_INFO *hostInfo, const int controllerIndex, bool fromGameInvite); @@ -594,7 +672,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*); @@ -621,7 +699,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*); @@ -642,6 +720,9 @@ namespace Game typedef char* (__cdecl * SEH_StringEd_GetString_t)(const char* string); extern SEH_StringEd_GetString_t SEH_StringEd_GetString; + typedef unsigned int(__cdecl* SEH_ReadCharFromString_t)(const char** text, int* isTrailingPunctuation); + extern SEH_ReadCharFromString_t SEH_ReadCharFromString; + typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); extern SL_ConvertToString_t SL_ConvertToString; @@ -681,6 +762,9 @@ namespace Game typedef void(__cdecl * SV_Cmd_EndTokenizedString_t)(); extern SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString; + typedef void(__cdecl * SV_Cmd_ArgvBuffer_t)(int arg, char* buf, int size); + extern SV_Cmd_ArgvBuffer_t SV_Cmd_ArgvBuffer; + typedef void(__cdecl * SV_SetConfigstring_t)(int index, const char* string); extern SV_SetConfigstring_t SV_SetConfigstring; @@ -690,9 +774,15 @@ namespace Game typedef bool(__cdecl * SV_Loaded_t)(); extern SV_Loaded_t SV_Loaded; - typedef void(__cdecl* SV_ClientThink_t)(client_s*, usercmd_s*); + typedef void(__cdecl * SV_ClientThink_t)(client_s*, usercmd_s*); extern SV_ClientThink_t SV_ClientThink; + typedef client_t*(__cdecl * SV_GetPlayerByName_t)(); + extern SV_GetPlayerByName_t SV_GetPlayerByName; + + typedef client_t*(__cdecl * SV_GetPlayerByNum_t)(); + extern SV_GetPlayerByNum_t SV_GetPlayerByNum; + typedef int(__cdecl * Sys_Error_t)(int, char *, ...); extern Sys_Error_t Sys_Error; @@ -723,6 +813,15 @@ namespace Game typedef int(__cdecl * Sys_Milliseconds_t)(); extern Sys_Milliseconds_t Sys_Milliseconds; + typedef void(__cdecl * Sys_LockWrite_t)(FastCriticalSection* critSect); + extern Sys_LockWrite_t Sys_LockWrite; + + typedef void(__cdecl * Sys_TempPriorityAtLeastNormalBegin_t)(TempPriority*); + extern Sys_TempPriorityAtLeastNormalBegin_t Sys_TempPriorityAtLeastNormalBegin; + + typedef void(__cdecl * Sys_TempPriorityEnd_t)(TempPriority*); + extern Sys_TempPriorityEnd_t Sys_TempPriorityEnd; + typedef void(__cdecl * TeleportPlayer_t)(gentity_t* entity, float* pos, float* orientation); extern TeleportPlayer_t TeleportPlayer; @@ -737,6 +836,9 @@ namespace Game typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; + + typedef uiMenuCommand_t(__cdecl * UI_GetActiveMenu_t)(int localClientNum); + extern UI_GetActiveMenu_t UI_GetActiveMenu; typedef char* (__cdecl * UI_CheckStringTranslation_t)(char*, char*); extern UI_CheckStringTranslation_t UI_CheckStringTranslation; @@ -753,37 +855,90 @@ namespace Game typedef void(__cdecl * UI_DrawHandlePic_t)(/*ScreenPlacement*/void *scrPlace, float x, float y, float w, float h, int horzAlign, int vertAlign, const float *color, Material *material); extern UI_DrawHandlePic_t UI_DrawHandlePic; - typedef void* (__cdecl * UI_GetContext_t)(void*); - extern UI_GetContext_t UI_GetContext; + typedef ScreenPlacement* (__cdecl * ScrPlace_GetActivePlacement_t)(int localClientNum); + extern ScrPlace_GetActivePlacement_t ScrPlace_GetActivePlacement; typedef int(__cdecl * UI_TextWidth_t)(const char *text, int maxChars, Font_s *font, float scale); extern UI_TextWidth_t UI_TextWidth; typedef void(__cdecl * UI_DrawText_t)(void* scrPlace, const char *text, int maxChars, Font_s *font, float x, float y, int horzAlign, int vertAlign, float scale, const float *color, int style); extern UI_DrawText_t UI_DrawText; + + typedef Font_s* (__cdecl* UI_GetFontHandle_t)(ScreenPlacement* scrPlace, int fontEnum, float scale); + extern UI_GetFontHandle_t UI_GetFontHandle; + + typedef void(__cdecl* ScrPlace_ApplyRect_t)(ScreenPlacement* a1, float* x, float* y, float* w, float* h, int horzAlign, int vertAlign); + extern ScrPlace_ApplyRect_t ScrPlace_ApplyRect; typedef const char * (__cdecl * Win_GetLanguage_t)(); extern Win_GetLanguage_t Win_GetLanguage; typedef void (__cdecl * Vec3UnpackUnitVec_t)(PackedUnitVec, vec3_t *); extern Vec3UnpackUnitVec_t Vec3UnpackUnitVec; + + typedef float(__cdecl * vectoyaw_t)(vec2_t* vec); + extern vectoyaw_t vectoyaw; + + typedef float(__cdecl * AngleNormalize360_t)(float val); + extern AngleNormalize360_t AngleNormalize360; typedef void(__cdecl * unzClose_t)(void* handle); extern unzClose_t unzClose; + + typedef void(__cdecl* RB_DrawCursor_t)(Material* material, char cursor, float x, float y, float sinAngle, float cosAngle, Font_s* font, float xScale, float yScale, unsigned int color); + extern RB_DrawCursor_t RB_DrawCursor; + + typedef float(__cdecl* R_NormalizedTextScale_t)(Font_s* font, float scale); + extern R_NormalizedTextScale_t R_NormalizedTextScale; + + typedef void(__cdecl * Material_Process2DTextureCoordsForAtlasing_t)(const Material* material, float* s0, float* s1, float* t0, float* t1); + extern Material_Process2DTextureCoordsForAtlasing_t Material_Process2DTextureCoordsForAtlasing; + + typedef void(__cdecl* Byte4PackRgba_t)(const float* from, char* to); + extern Byte4PackRgba_t Byte4PackRgba; + + typedef int(__cdecl* RandWithSeed_t)(int* seed); + extern RandWithSeed_t RandWithSeed; + + typedef void(__cdecl* GetDecayingLetterInfo_t)(unsigned int letter, int* randSeed, int decayTimeElapsed, int fxBirthTime, int fxDecayDuration, unsigned __int8 alpha, bool* resultSkipDrawing, char* resultAlpha, unsigned int* resultLetter, bool* resultDrawExtraFxChar); + extern GetDecayingLetterInfo_t GetDecayingLetterInfo; + + typedef void(__cdecl * Field_Draw_t)(int localClientNum, field_t* edit, int x, int y, int horzAlign, int vertAlign); + extern Field_Draw_t Field_Draw; + + typedef void(__cdecl * Field_AdjustScroll_t)(ScreenPlacement* scrPlace, field_t* edit); + extern Field_AdjustScroll_t Field_AdjustScroll; + + typedef void(__cdecl * AimAssist_ApplyAutoMelee_t)(const AimInput* input, AimOutput* output); + extern AimAssist_ApplyAutoMelee_t AimAssist_ApplyAutoMelee; + + typedef gentity_s*(__cdecl * Weapon_RocketLauncher_Fire_t)(gentity_s* ent, unsigned int weaponIndex, float spread, weaponParms* wp, const float* gunVel, lockonFireParms* lockParms, bool a7); + extern Weapon_RocketLauncher_Fire_t Weapon_RocketLauncher_Fire; + + typedef void(__cdecl * Jump_ClearState_t)(playerState_s* ps); + extern Jump_ClearState_t Jump_ClearState; + + typedef void(__cdecl * PM_playerTrace_t)(pmove_s*, trace_t*, const float*, const float*, const Bounds*, int, int); + extern PM_playerTrace_t PM_playerTrace; + + typedef void(__cdecl * PM_Trace_t)(pmove_s*, trace_t*, const float*, const float*, const Bounds*, int, int); + extern PM_Trace_t PM_Trace; + + typedef EffectiveStance(__cdecl * PM_GetEffectiveStance_t)(const playerState_s* ps); + extern PM_GetEffectiveStance_t PM_GetEffectiveStance; extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; - extern DWORD* cmd_id; - extern DWORD* cmd_argc; - extern char*** cmd_argv; - - extern DWORD* cmd_id_sv; - extern DWORD* cmd_argc_sv; - extern char*** cmd_argv_sv; + constexpr auto CMD_MAX_NESTING = 8; + extern CmdArgs* cmd_args; + extern CmdArgs* sv_cmd_args; extern cmd_function_t** cmd_functions; + extern float* cl_angles; + extern float* cgameFOVSensitivityScale; + extern int* svs_time; extern int* svs_numclients; extern client_t* svs_clients; @@ -817,6 +972,8 @@ namespace Game extern int* demoRecording; extern int* serverMessageSequence; + constexpr auto MAX_GENTITIES = 2048u; + constexpr auto ENTITYNUM_NONE = MAX_GENTITIES - 1; extern gentity_t* g_entities; extern netadr_t* connectedHost; @@ -854,12 +1011,49 @@ namespace Game extern XZone* g_zones; extern unsigned short* db_hashTable; - extern ScriptContainer* scriptContainer; + extern scrVmPub_t* scrVmPub; extern clientstate_t* clcState; + extern GfxScene* scene; + + extern ConDrawInputGlob* conDrawInputGlob; + extern field_t* g_consoleField; + + extern clientStatic_t* cls; + + extern sharedUiInfo_t* sharedUiInfo; + extern ScreenPlacement* scrPlaceFull; + extern ScreenPlacement* scrPlaceView; + + extern clientActive_t* clients; + + extern cg_s* cgArray; + extern cgs_t* cgsArray; + + extern PlayerKeyState* playerKeys; + extern kbutton_t* playersKb; + extern AimAssistGlobals* aaGlobArray; + + constexpr auto KEY_NAME_COUNT = 95; + constexpr auto LOCALIZED_KEY_NAME_COUNT = 95; + extern keyname_t* keyNames; + extern keyname_t* localizedKeyNames; + + constexpr auto AIM_ASSIST_GRAPH_COUNT = 4u; + extern GraphFloat* aaInputGraph; + + extern vec3_t* CorrectSolidDeltas; + + extern FastCriticalSection* db_hashCritSect; + + void Sys_LockRead(FastCriticalSection* critSect); + void Sys_UnlockRead(FastCriticalSection* critSect); + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); void Menu_FreeItemMemory(Game::itemDef_s* item); + void Menu_SetNextCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); + void Menu_SetPrevCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); const char* TableLookup(StringTable* stringtable, int row, int column); const char* UI_LocalizeMapName(const char* mapName); const char* UI_LocalizeGameType(const char* gameType); @@ -869,15 +1063,18 @@ namespace Game XAssetType DB_GetXAssetNameType(const char* name); int DB_GetZoneIndex(const std::string& name); bool DB_IsZoneLoaded(const char* zone); - void DB_EnumXAssetEntries(XAssetType type, std::function callback, bool overrides, bool lock); + void DB_EnumXAssetEntries(XAssetType type, std::function callback, bool overrides); XAssetHeader DB_FindXAssetDefaultHeaderInternal(XAssetType type); XAssetEntry* DB_FindXAssetEntry(XAssetType type, const char* name); void FS_AddLocalizedGameDirectory(const char *path, const char *dir); + bool PM_IsAdsAllowed(Game::playerState_s* playerState); + void ShowMessageBox(const std::string& message, const std::string& title); unsigned int R_HashString(const char* string); + unsigned int R_HashString(const char* string, size_t maxLen); void R_LoadSunThroughDvars(const char* mapname, sunflare_t* sun); void R_SetSunFromDvars(sunflare_t* sun); @@ -903,12 +1100,30 @@ namespace Game void Image_Setup(GfxImage* image, unsigned int width, unsigned int height, unsigned int depth, unsigned int flags, _D3DFORMAT format); - void Vec3Normalize(vec3_t& vec); + float Vec2Normalize(vec2_t& vec); + float Vec3Normalize(vec3_t& vec); void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out); void MatrixVecMultiply(const float(&mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution); + void QuatRot(vec3_t* vec, const vec4_t* quat); + void QuatMultiply(const vec4_t* q1, const vec4_t* q2, vec4_t* res); void SortWorldSurfaces(GfxWorld* world); void R_AddDebugLine(float* color, float* v1, float* v2); void R_AddDebugString(float *color, float *pos, float scale, const char *str); void R_AddDebugBounds(float* color, Bounds* b); + void R_AddDebugBounds(float* color, Bounds* b, const float(*quat)[4]); + + Glyph* R_GetCharacterGlyph(Font_s* font, unsigned int letter); + bool SetupPulseFXVars(const char* text, int maxLength, int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, bool* resultDrawRandChar, int* resultRandSeed, int* resultMaxLength, bool* resultDecaying, int* resultDecayTimeElapsed); + void RB_DrawChar(Material* material, float x, float y, float w, float h, float sinAngle, float cosAngle, Glyph* glyph, unsigned int color); + void RB_DrawStretchPicRotate(Material* material, float x, float y, float w, float h, float s0, float t0, float s1, float t1, float sinAngle, float cosAngle, unsigned int color); + char ModulateByteColors(char colorA, char colorB); + + float GraphGetValueFromFraction(int knotCount, const float(*knots)[2], float fraction); + float GraphFloat_GetValue(const GraphFloat* graph, const float fraction); + + void AimAssist_UpdateTweakables(int localClientNum); + void AimAssist_UpdateAdsLerp(const AimInput* input); + + void Dvar_SetVariant(dvar_t* var, DvarValue value, DvarSetSource source); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 6636d28d..fd4acc95 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1,6 +1,6 @@ #pragma once -#define PROTOCOL 0x95 +#define PROTOCOL 0x96 #define NUM_CUSTOM_CLASSES 15 #define SEMANTIC_WATER_MAP 11 #define FX_ELEM_FIELD_COUNT 90 @@ -74,29 +74,73 @@ namespace Game ASSET_TYPE_INVALID = -1, }; - typedef enum + enum dvar_flag : unsigned __int16 { - DVAR_FLAG_NONE = 0x0, //no flags - DVAR_FLAG_SAVED = 0x1, //saves in config_mp.cfg for clients - DVAR_FLAG_LATCHED = 0x2, //no changing apart from initial value (although it might apply on a map reload, I think) - DVAR_FLAG_CHEAT = 0x4, //cheat - DVAR_FLAG_REPLICATED = 0x8, //on change, this is sent to all clients (if you are host) - DVAR_FLAG_UNKNOWN10 = 0x10, //unknown - DVAR_FLAG_UNKNOWN20 = 0x20, //unknown - DVAR_FLAG_UNKNOWN40 = 0x40, //unknown - DVAR_FLAG_UNKNOWN80 = 0x80, //unknown - DVAR_FLAG_USERCREATED = 0x100, //a 'set' type command created it - DVAR_FLAG_USERINFO = 0x200, //userinfo? - DVAR_FLAG_SERVERINFO = 0x400, //in the getstatus oob - DVAR_FLAG_WRITEPROTECTED = 0x800, //write protected - DVAR_FLAG_UNKNOWN1000 = 0x1000, //unknown - DVAR_FLAG_READONLY = 0x2000, //read only (same as 0x800?) - DVAR_FLAG_UNKNOWN4000 = 0x4000, //unknown - DVAR_FLAG_UNKNOWN8000 = 0x8000, //unknown - DVAR_FLAG_UNKNOWN10000 = 0x10000, //unknown - DVAR_FLAG_DEDISAVED = 0x1000000, //unknown - DVAR_FLAG_NONEXISTENT = 0xFFFFFFFF //no such dvar - } dvar_flag; + DVAR_NONE = 0x0, // No flags + DVAR_ARCHIVE = 0x1, // Set to cause it to be saved to config_mp.cfg of the client + DVAR_LATCH = 0x2, // Will only change when C code next does a Dvar_Get(), so it can't be changed + // without proper initialization. Modified will be set, even though the value hasn't changed yet + DVAR_CHEAT = 0x4, // Can not be changed if cheats are disabled + DVAR_CODINFO = 0x8, // On change, this is sent to all clients (if you are host) + DVAR_SCRIPTINFO = 0x10, + DVAR_UNKNOWN20 = 0x20, + DVAR_CHANGEABLE_RESET = 0x40, + DVAR_UNKNOWN80 = 0x80, + DVAR_EXTERNAL = 0x100, // Created by a set command + DVAR_USERINFO = 0x200, // Sent to server on connect or change + DVAR_SERVERINFO = 0x400, // Sent in response to front end requests + DVAR_WRITEPROTECTED = 0x800, + DVAR_SYSTEMINFO = 0x1000, // Will be duplicated on all clients + DVAR_READONLY = 0x2000, // Read only (same as DVAR_WRITEPROTECTED?) + DVAR_SAVED = 0x4000, + DVAR_AUTOEXEC = 0x8000, + }; + + enum ImageCategory : char + { + IMG_CATEGORY_UNKNOWN = 0x0, + IMG_CATEGORY_AUTO_GENERATED = 0x1, + IMG_CATEGORY_LIGHTMAP = 0x2, + IMG_CATEGORY_LOAD_FROM_FILE = 0x3, + IMG_CATEGORY_RAW = 0x4, + IMG_CATEGORY_FIRST_UNMANAGED = 0x5, + IMG_CATEGORY_WATER = 0x5, + IMG_CATEGORY_RENDERTARGET = 0x6, + IMG_CATEGORY_TEMP = 0x7, + }; + + enum buttons_t + { + KB_LEFT = 0x0, + KB_RIGHT = 0x1, + KB_FORWARD = 0x2, + KB_BACK = 0x3, + KB_LOOKUP = 0x4, + KB_LOOKDOWN = 0x5, + KB_MOVELEFT = 0x6, + KB_MOVERIGHT = 0x7, + KB_STRAFE = 0x8, + KB_SPEED = 0x9, + KB_UP = 0xA, + KB_DOWN = 0xB, + KB_ANYUP = 0xC, + KB_MLOOK = 0xD, + KB_ATTACK = 0xE, + KB_BREATH = 0xF, + KB_FRAG = 0x10, + KB_OFFHANDSECONDARY = 0x11, + KB_MELEE = 0x12, + KB_ACTIVATE = 0x13, + KB_RELOAD = 0x14, + KB_USE_RELOAD = 0x15, + KB_PRONE = 0x16, + KB_CROUCH = 0x17, + KB_THROW = 0x18, + KB_SPRINT = 0x19, + KB_NIGHTVISION = 0x1A, + KB_TALK = 0x1B, + NUM_BUTTONS = 0x1C + }; enum DvarSetSource { @@ -106,7 +150,7 @@ namespace Game DVAR_SOURCE_DEVGUI = 0x3, }; - typedef enum + typedef enum : char { DVAR_TYPE_BOOL = 0x0, DVAR_TYPE_FLOAT = 0x1, @@ -124,13 +168,72 @@ namespace Game typedef enum { CS_FREE = 0x0, - CS_UNKNOWN1 = 0x1, - CS_UNKNOWN2 = 0x2, + CS_ZOMBIE = 0x1, + CS_RECONNECTING = 0x2, CS_CONNECTED = 0x3, CS_CLIENTLOADING = 0x4, 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; + + enum entityFlag + { + FL_GODMODE = 0x1, + FL_DEMI_GODMODE = 0x2, + FL_NOTARGET = 0x4, + FL_NO_KNOCKBACK = 0x8, + FL_NO_RADIUS_DAMAGE = 0x10, + FL_SUPPORTS_LINKTO = 0x1000, + FL_NO_AUTO_ANIM_UPDATE = 0x2000, + FL_GRENADE_TOUCH_DAMAGE = 0x4000, + FL_STABLE_MISSILES = 0x20000, + FL_REPEAT_ANIM_UPDATE = 0x40000, + FL_VEHICLE_TARGET = 0x80000, + FL_GROUND_ENT = 0x100000, + FL_CURSOR_HINT = 0x200000, + FL_MISSILE_ATTRACTOR = 0x800000, + FL_WEAPON_BEING_GRABBED = 0x1000000, + FL_DELETE = 0x2000000, + FL_BOUNCE = 0x4000000, + FL_MOVER_SLIDE = 0x8000000 + }; + + typedef enum + { + HITLOC_NONE, + HITLOC_HELMET, + HITLOC_HEAD, + HITLOC_NECK, + HITLOC_TORSO_UPR, + HITLOC_TORSO_LWR, + HITLOC_R_ARM_UPR, + HITLOC_L_ARM_UPR, + HITLOC_R_ARM_LWR, + HITLOC_L_ARM_LWR, + HITLOC_R_HAND, + HITLOC_L_HAND, + HITLOC_R_LEG_UPR, + HITLOC_L_LEG_UPR, + HITLOC_R_LEG_LWR, + HITLOC_L_LEG_LWR, + HITLOC_R_FOOT, + HITLOC_L_FOOT, + HITLOC_GUN, + HITLOC_SHIELD, + HITLOC_NUM + } hitLocation_t; + struct FxEffectDef; struct pathnode_t; struct pathnode_tree_t; @@ -139,6 +242,17 @@ namespace Game struct MenuEventHandlerSet; struct menuDef_t; + struct CmdArgs + { + int nesting; + int localClientNum[8]; + int controllerIndex[8]; + int argc[8]; + const char** argv[8]; + }; + + static_assert(sizeof(CmdArgs) == 132); + typedef struct cmd_function_s { cmd_function_s *next; @@ -160,6 +274,174 @@ namespace Game }; #pragma pack(pop) + enum KeyCatch_t + { + KEYCATCH_MASK_ANY = -1, + KEYCATCH_CONSOLE = 0x1, + KEYCATCH_UNKNOWN2 = 0x2, + KEYCATCH_UNKNOWN4 = 0x4, + KEYCATCH_LOCATION_SELECTION = 0x8, + KEYCATCH_UI = 0x10, + KEYCATCH_CHAT = 0x20, + KEYCATCH_UNKNOWN40 = 0x40, + KEYCATCH_UNKNOWN80 = 0x80, + KEYCATCH_UNKNOWN100 = 0x100, + }; + + enum keyNum_t + { + K_NONE = 0x0, + K_FIRSTGAMEPADBUTTON_RANGE_1 = 0x1, // First Gamepad 1 + K_BUTTON_A = 0x1, + K_BUTTON_B = 0x2, + K_BUTTON_X = 0x3, + K_BUTTON_Y = 0x4, + K_BUTTON_LSHLDR = 0x5, + K_BUTTON_RSHLDR = 0x6, + K_LASTGAMEPADBUTTON_RANGE_1 = 0x6, // Last Gamepad 1 + K_TAB = 0x9, + K_ENTER = 0xD, + K_FIRSTGAMEPADBUTTON_RANGE_2 = 0xE, // First Gamepad 2 + K_BUTTON_START = 0xE, + K_BUTTON_BACK = 0xF, + K_BUTTON_LSTICK = 0x10, + K_BUTTON_RSTICK = 0x11, + K_BUTTON_LTRIG = 0x12, + K_BUTTON_RTRIG = 0x13, + K_FIRSTDPAD = 0x14, // First Dpad + K_DPAD_UP = 0x14, + K_DPAD_DOWN = 0x15, + K_DPAD_LEFT = 0x16, + K_DPAD_RIGHT = 0x17, + K_LASTDPAD = 0x17, // Last Dpad + K_DPAD_LEFTRIGHT = 0x18, + K_DPAD_UPDOWN = 0x19, + K_LASTGAMEPADBUTTON_RANGE_2 = 0x19, // Last Gamepad 2 + K_ESCAPE = 0x1B, + K_FIRSTGAMEPADBUTTON_RANGE_3 = 0x1C, // First Gamepad 3 + K_FIRSTAPAD = 0x1C, // First APad + K_APAD_UP = 0x1C, + K_APAD_DOWN = 0x1D, + K_APAD_LEFT = 0x1E, + K_APAD_RIGHT = 0x1F, + K_LASTAPAD = 0x1F, // Last APad + K_LASTGAMEPADBUTTON_RANGE_3 = 0x1F, // Last Gamepad 3 + K_SPACE = 0x20, + K_BACKSPACE = 0x7F, + K_ASCII_FIRST = 0x80, + K_ASCII_181 = 0x80, + K_ASCII_191 = 0x81, + K_ASCII_223 = 0x82, + K_ASCII_224 = 0x83, + K_ASCII_225 = 0x84, + K_ASCII_228 = 0x85, + K_ASCII_229 = 0x86, + K_ASCII_230 = 0x87, + K_ASCII_231 = 0x88, + K_ASCII_232 = 0x89, + K_ASCII_233 = 0x8A, + K_ASCII_236 = 0x8B, + K_ASCII_241 = 0x8C, + K_ASCII_242 = 0x8D, + K_ASCII_243 = 0x8E, + K_ASCII_246 = 0x8F, + K_ASCII_248 = 0x90, + K_ASCII_249 = 0x91, + K_ASCII_250 = 0x92, + K_ASCII_252 = 0x93, + K_END_ASCII_CHARS = 0x94, + K_COMMAND = 0x96, + K_CAPSLOCK = 0x97, + K_POWER = 0x98, + K_PAUSE = 0x99, + K_UPARROW = 0x9A, + K_DOWNARROW = 0x9B, + K_LEFTARROW = 0x9C, + K_RIGHTARROW = 0x9D, + K_ALT = 0x9E, + K_CTRL = 0x9F, + K_SHIFT = 0xA0, + K_INS = 0xA1, + K_DEL = 0xA2, + K_PGDN = 0xA3, + K_PGUP = 0xA4, + K_HOME = 0xA5, + K_END = 0xA6, + K_F1 = 0xA7, + K_F2 = 0xA8, + K_F3 = 0xA9, + K_F4 = 0xAA, + K_F5 = 0xAB, + K_F6 = 0xAC, + K_F7 = 0xAD, + K_F8 = 0xAE, + K_F9 = 0xAF, + K_F10 = 0xB0, + K_F11 = 0xB1, + K_F12 = 0xB2, + K_F13 = 0xB3, + K_F14 = 0xB4, + K_F15 = 0xB5, + K_KP_HOME = 0xB6, + K_KP_UPARROW = 0xB7, + K_KP_PGUP = 0xB8, + K_KP_LEFTARROW = 0xB9, + K_KP_5 = 0xBA, + K_KP_RIGHTARROW = 0xBB, + K_KP_END = 0xBC, + K_KP_DOWNARROW = 0xBD, + K_KP_PGDN = 0xBE, + K_KP_ENTER = 0xBF, + K_KP_INS = 0xC0, + K_KP_DEL = 0xC1, + K_KP_SLASH = 0xC2, + K_KP_MINUS = 0xC3, + K_KP_PLUS = 0xC4, + K_KP_NUMLOCK = 0xC5, + K_KP_STAR = 0xC6, + K_KP_EQUALS = 0xC7, + K_MOUSE1 = 0xC8, + K_MOUSE2 = 0xC9, + K_MOUSE3 = 0xCA, + K_MOUSE4 = 0xCB, + K_MOUSE5 = 0xCC, + K_MWHEELDOWN = 0xCD, + K_MWHEELUP = 0xCE, + K_AUX1 = 0xCF, + K_AUX2 = 0xD0, + K_AUX3 = 0xD1, + K_AUX4 = 0xD2, + K_AUX5 = 0xD3, + K_AUX6 = 0xD4, + K_AUX7 = 0xD5, + K_AUX8 = 0xD6, + K_AUX9 = 0xD7, + K_AUX10 = 0xD8, + K_AUX11 = 0xD9, + K_AUX12 = 0xDA, + K_AUX13 = 0xDB, + K_AUX14 = 0xDC, + K_AUX15 = 0xDD, + K_AUX16 = 0xDE, + K_LAST_KEY = 0xDF, + }; + + enum uiMenuCommand_t + { + UIMENU_NONE = 0x0, + UIMENU_MAIN = 0x1, + UIMENU_INGAME = 0x2, + UIMENU_PREGAME = 0x3, + UIMENU_POSTGAME = 0x4, + UIMENU_SCRIPT_POPUP = 0x5, + UIMENU_SCOREBOARD = 0x6, + UIMENU_PARTY = 0x7, + UIMENU_GAMELOBBY = 0x8, + UIMENU_PRIVATELOBBY = 0x9, + UIMENU_ENDOFGAME = 0xA, + UIMENU_MIGRATION = 0xB, + }; + struct __declspec(align(4)) PhysPreset { const char *name; @@ -488,8 +770,8 @@ namespace Game const char *name; char gameFlags; char sortKey; - char textureAtlasRowCount; - char textureAtlasColumnCount; + unsigned char textureAtlasRowCount; + unsigned char textureAtlasColumnCount; GfxDrawSurf drawSurf; unsigned int surfaceTypeBits; unsigned __int16 hashIndex; @@ -587,6 +869,729 @@ namespace Game MaterialShaderArgument *args; }; + /* 9045 */ + struct visionSetVars_t + { + bool glowEnable; + float glowBloomCutoff; + float glowBloomDesaturation; + float glowBloomIntensity0; + float glowBloomIntensity1; + float glowRadius0; + float glowRadius1; + float glowSkyBleedIntensity0; + float glowSkyBleedIntensity1; + bool filmEnable; + float filmBrightness; + float filmContrast; + float filmDesaturation; + float filmDesaturationDark; + bool filmInvert; + float filmLightTint[3]; + float filmMediumTint[3]; + float filmDarkTint[3]; + bool charPrimaryUseTweaks; + float charPrimaryDiffuseScale; + float charPrimarySpecularScale; + }; + + struct visField_t + { + const char* name; + int offset; + int fieldType; + }; + + enum OffhandClass + { + OFFHAND_CLASS_NONE = 0x0, + OFFHAND_CLASS_FRAG_GRENADE = 0x1, + OFFHAND_CLASS_SMOKE_GRENADE = 0x2, + OFFHAND_CLASS_FLASH_GRENADE = 0x3, + OFFHAND_CLASS_THROWINGKNIFE = 0x4, + OFFHAND_CLASS_OTHER = 0x5, + OFFHAND_CLASS_COUNT = 0x6, + }; + + enum ViewLockTypes + { + PLAYERVIEWLOCK_NONE = 0x0, + PLAYERVIEWLOCK_FULL = 0x1, + PLAYERVIEWLOCK_WEAPONJITTER = 0x2, + PLAYERVIEWLOCKCOUNT = 0x3, + }; + + struct SprintState + { + int sprintButtonUpRequired; + int sprintDelay; + int lastSprintStart; + int lastSprintEnd; + int sprintStartMaxLength; + }; + + + /* 1018 */ + struct MantleState + { + float yaw; + int timer; + int transIndex; + int flags; + }; + + /* 1019 */ + struct PlayerActiveWeaponState + { + int weapAnim; + int weaponTime; + int weaponDelay; + int weaponRestrictKickTime; + int weaponState; + int weapHandFlags; + unsigned int weaponShotCount; + }; + + /* 1020 */ + struct PlayerEquippedWeaponState + { + bool usedBefore; + bool dualWielding; + char weaponModel; + bool needsRechamber[2]; + }; + + /* 1021 */ + struct GlobalAmmo + { + int ammoType; + int ammoCount; + }; + + /* 1022 */ + struct ClipAmmo + { + int clipIndex; + int ammoCount[2]; + }; + + enum PlayerHandIndex + { + WEAPON_HAND_RIGHT = 0x0, + WEAPON_HAND_LEFT = 0x1, + NUM_WEAPON_HANDS = 0x2, + WEAPON_HAND_DEFAULT = 0x0, + }; + + /* 1023 */ + struct PlayerWeaponCommonState + { + int offHandIndex; + OffhandClass offhandPrimary; + OffhandClass offhandSecondary; + unsigned int weapon; + unsigned int primaryWeaponForAltMode; + int weapFlags; + float fWeaponPosFrac; + float aimSpreadScale; + int adsDelayTime; + int spreadOverride; + int spreadOverrideState; + PlayerHandIndex lastWeaponHand; + GlobalAmmo ammoNotInClip[15]; + ClipAmmo ammoInClip[15]; + int weapLockFlags; + int weapLockedEntnum; + float weapLockedPos[3]; + int weaponIdleTime; + }; + + enum ActionSlotType + { + ACTIONSLOTTYPE_DONOTHING = 0x0, + ACTIONSLOTTYPE_SPECIFYWEAPON = 0x1, + ACTIONSLOTTYPE_ALTWEAPONTOGGLE = 0x2, + ACTIONSLOTTYPE_NIGHTVISION = 0x3, + ACTIONSLOTTYPECOUNT = 0x4, + }; + + /* 1024 */ + struct ActionSlotParam_SpecifyWeapon + { + unsigned int index; + }; + + /* 1025 */ + struct ActionSlotParam + { + ActionSlotParam_SpecifyWeapon specifyWeapon; + }; + + enum objectiveState_t + { + OBJST_EMPTY = 0x0, + OBJST_ACTIVE = 0x1, + OBJST_INVISIBLE = 0x2, + OBJST_DONE = 0x3, + OBJST_CURRENT = 0x4, + OBJST_FAILED = 0x5, + OBJST_NUMSTATES = 0x6, + }; + + /* 1026 */ + struct objective_t + { + objectiveState_t state; + float origin[3]; + int entNum; + int teamNum; + int icon; + }; + + + /* 104 */ + enum he_type_t + { + HE_TYPE_FREE = 0x0, + HE_TYPE_TEXT = 0x1, + HE_TYPE_VALUE = 0x2, + HE_TYPE_PLAYERNAME = 0x3, + HE_TYPE_MAPNAME = 0x4, + HE_TYPE_GAMETYPE = 0x5, + HE_TYPE_MATERIAL = 0x6, + HE_TYPE_TIMER_DOWN = 0x7, + HE_TYPE_TIMER_UP = 0x8, + HE_TYPE_TIMER_STATIC = 0x9, + HE_TYPE_TENTHS_TIMER_DOWN = 0xA, + HE_TYPE_TENTHS_TIMER_UP = 0xB, + HE_TYPE_TENTHS_TIMER_STATIC = 0xC, + HE_TYPE_CLOCK_DOWN = 0xD, + HE_TYPE_CLOCK_UP = 0xE, + HE_TYPE_WAYPOINT = 0xF, + HE_TYPE_COUNT = 0x10, + }; + + struct hud_color + { + char r; + char g; + char b; + char a; + }; + + /* 1028 */ + union hudelem_color_t + { + hud_color __s0; + int rgba; + }; + + struct hudelem_s + { + he_type_t type; + float x; + float y; + float z; + int targetEntNum; + float fontScale; + float fromFontScale; + int fontScaleStartTime; + int fontScaleTime; + int font; + int alignOrg; + int alignScreen; + hudelem_color_t color; + hudelem_color_t fromColor; + int fadeStartTime; + int fadeTime; + int label; + int width; + int height; + int materialIndex; + int fromWidth; + int fromHeight; + int scaleStartTime; + int scaleTime; + float fromX; + float fromY; + int fromAlignOrg; + int fromAlignScreen; + int moveStartTime; + int moveTime; + int time; + int duration; + float value; + int text; + float sort; + hudelem_color_t glowColor; + int fxBirthTime; + int fxLetterTime; + int fxDecayStartTime; + int fxDecayDuration; + int soundID; + int flags; + }; + + struct $3EB5F037EADAEE8E2FA2A1F9FFF31312 + { + hudelem_s current[31]; + hudelem_s archival[31]; + }; + + enum playerStateFlag + { + PMF_PRONE = 0x1, + PMF_DUCKED = 0x2, + PMF_MANTLE = 0x4, + PMF_LADDER = 0x8, + PMF_SIGHT_AIMING = 0x10, + PMF_BACKWARDS_RUN = 0x20, + PMF_WALKING = 0x40, + PMF_TIME_HARDLANDING = 0x80, + PMF_TIME_KNOCKBACK = 0x100, + PMF_PRONEMOVE_OVERRIDDEN = 0x200, + PMF_RESPAWNED = 0x400, + PMF_FROZEN = 0x800, + PMF_LADDER_FALL = 0x1000, + PMF_JUMPING = 0x2000, + PMF_SPRINTING = 0x4000, + PMF_SHELLSHOCKED = 0x8000, + PMF_MELEE_CHARGE = 0x10000, + PMF_NO_SPRINT = 0x20000, + PMF_NO_JUMP = 0x40000, + PMF_REMOTE_CONTROLLING = 0x80000, + PMF_ANIM_SCRIPTED = 0x100000, + PMF_DIVING = 0x400000 + }; + + enum pmtype_t + { + PM_NORMAL = 0x0, + PM_NORMAL_LINKED = 0x1, + PM_NOCLIP = 0x2, + PM_UFO = 0x3, + PM_MPVIEWER = 0x4, + PM_SPECTATOR = 0x5, + PM_INTERMISSION = 0x6, + PM_LASTSTAND = 0x7, + PM_DEAD = 0x8, + PM_DEAD_LINKED = 0x9, + }; + + enum playerEFlag + { + EF_NONSOLID_BMODEL = 0x1, + EF_TELEPORT_BIT = 0x2, + EF_CROUCHING = 0x4, + EF_PRONE = 0x8, + EF_NODRAW = 0x20, + EF_TIMED_OBJECT = 0x40, + EF_VOTED = 0x80, + EF_TALK = 0x100, + EF_FIRING = 0x200, + EF_TURRET_ACTIVE_PRONE = 0x400, + EF_TURRET_ACTIVE_DUCK = 0x800, + EF_LOCK_LIGHT_VIS = 0x1000, + EF_AIM_ASSIST = 0x2000, + EF_LOOP_RUMBLE = 0x4000, + EF_LASER_SIGHT = 0x8000, + EF_MANTLE = 0x10000, + EF_DEAD = 0x20000, + EF_ADS = 0x40000, + EF_NEW = 0x80000, + EF_VEHICLE_ACTIVE = 0x100000, + EF_JAMMING = 0x200000, + EF_COMPASS_PING = 0x400000, + EF_SOFT = 0x800000 + }; + + enum playerLinkFlag + { + PLF_ANGLES_LOCKED = 0x1, + PLF_USES_OFFSET = 0x2, + PLF_WEAPONVIEW_ONLY = 0x4 + }; + + struct playerState_s + { + int commandTime; + int pm_type; + int pm_time; + int pm_flags; + int otherFlags; + int linkFlags; + int bobCycle; + float origin[3]; + float velocity[3]; + int grenadeTimeLeft; + int throwbackGrenadeOwner; + int throwbackGrenadeTimeLeft; + unsigned int throwbackWeaponIndex; + int remoteEyesEnt; + int remoteEyesTagname; + int remoteControlEnt; + int foliageSoundTime; + int gravity; + float leanf; + int speed; + float delta_angles[3]; + int groundEntityNum; + float vLadderVec[3]; + int jumpTime; + float jumpOriginZ; + int legsTimer; + int legsAnim; + int torsoTimer; + int torsoAnim; + int legsAnimDuration; + int torsoAnimDuration; + int damageTimer; + int damageDuration; + int flinchYawAnim; + int corpseIndex; + int movementDir; + int eFlags; + int eventSequence; + int events[4]; + unsigned int eventParms[4]; + int oldEventSequence; + int unpredictableEventSequence; + int unpredictableEventSequenceOld; + int unpredictableEvents[4]; + unsigned int unpredictableEventParms[4]; + int clientNum; // 260 + int viewmodelIndex; + float viewangles[3]; + int viewHeightTarget; + float viewHeightCurrent; + int viewHeightLerpTime; + int viewHeightLerpTarget; + int viewHeightLerpDown; + float viewAngleClampBase[2]; + float viewAngleClampRange[2]; + int damageEvent; + int damageYaw; + int damagePitch; + int damageCount; + int damageFlags; + int stats[4]; + float proneDirection; + float proneDirectionPitch; + float proneTorsoPitch; + ViewLockTypes viewlocked; + int viewlocked_entNum; + float linkAngles[3]; + float linkWeaponAngles[3]; + int linkWeaponEnt; + int loopSound; + int cursorHint; + int cursorHintString; + int cursorHintEntIndex; + int cursorHintDualWield; + int iCompassPlayerInfo; + int radarEnabled; + int radarBlocked; + int radarMode; + int locationSelectionInfo; + SprintState sprintState; + float holdBreathScale; + int holdBreathTimer; + float moveSpeedScaleMultiplier; + MantleState mantleState; + PlayerActiveWeaponState weapState[2]; + unsigned int weaponsEquipped[15]; + PlayerEquippedWeaponState weapEquippedData[15]; + PlayerWeaponCommonState weapCommon; + float meleeChargeYaw; + int meleeChargeDist; + int meleeChargeTime; + unsigned int perks[2]; + unsigned int perkSlots[8]; + ActionSlotType actionSlotType[4]; + ActionSlotParam actionSlotParam[4]; + int weaponHudIconOverrides[6]; + int animScriptedType; + int shellshockIndex; + int shellshockTime; + int shellshockDuration; + float dofNearStart; + float dofNearEnd; + float dofFarStart; + float dofFarEnd; + float dofNearBlur; + float dofFarBlur; + float dofViewmodelStart; + float dofViewmodelEnd; + objective_t objective[32]; + int deltaTime; + int killCamEntity; + int killCamLookAtEntity; + int killCamClientNum; + $3EB5F037EADAEE8E2FA2A1F9FFF31312 hud; + unsigned int partBits[6]; + int recoilScale; + int diveDirection; + int stunTime; + }; + + enum LocSelInputState + { + LOC_SEL_INPUT_NONE = 0x0, + LOC_SEL_INPUT_CONFIRM = 0x1, + LOC_SEL_INPUT_CANCEL = 0x2, + }; + + struct field_t + { + int cursor; + int scroll; + int drawWidth; + int widthInPixels; + float charHeight; + int fixedSize; + char buffer[256]; + }; + + struct KeyState + { + int down; + int repeats; + const char* binding; + }; + + struct PlayerKeyState + { + field_t chatField; + int chat_team; + int overstrikeMode; + int anyKeyDown; + KeyState keys[256]; + LocSelInputState locSelInputState; + }; + + struct keyname_t + { + const char* name; + int keynum; + }; + + struct clSnapshot_t + { + playerState_s ps; + int valid; + int snapFlags; + int serverTime; + int messageNum; + int deltaNum; + int ping; + int cmdNum; + int numEntities; + int numClients; + int parseEntitiesIndex; + int parseClientsIndex; + int serverCommandNum; + }; + + enum StanceState + { + CL_STANCE_STAND = 0x0, + CL_STANCE_CROUCH = 0x1, + CL_STANCE_PRONE = 0x2, + }; + + struct ClientArchiveData + { + int serverTime; + float origin[3]; + float velocity[3]; + int bobCycle; + int movementDir; + float viewangles[3]; + int locationSelectionInfo; + float selectedLocation[2]; + float selectedLocationAngle; + }; + + struct outPacket_t + { + int p_cmdNumber; + int p_serverTime; + int p_realtime; + }; + + enum team_t + { + TEAM_FREE = 0x0, + TEAM_AXIS = 0x1, + TEAM_ALLIES = 0x2, + TEAM_SPECTATOR = 0x3, + TEAM_NUM_TEAMS = 0x4, + }; + + struct clientState_s + { + int clientIndex; + team_t team; + int modelindex; + int dualWielding; + int riotShieldNext; + int attachModelIndex[6]; + int attachTagIndex[6]; + char name[16]; + float maxSprintTimeMultiplier; + int rank; + int prestige; + unsigned int perks[2]; + int diveState; + int voiceConnectivityBits; + unsigned int playerCardIcon; + unsigned int playerCardTitle; + unsigned int playerCardNameplate; + }; + + enum usercmdButtonBits + { + CMD_BUTTON_ATTACK = 0x1, + CMD_BUTTON_SPRINT = 0x2, + CMD_BUTTON_MELEE = 0x4, + CMD_BUTTON_ACTIVATE = 0x8, + CMD_BUTTON_RELOAD = 0x10, + CMD_BUTTON_USE_RELOAD = 0x20, + CMD_BUTTON_PRONE = 0x100, + CMD_BUTTON_CROUCH = 0x200, + CMD_BUTTON_UP = 0x400, + CMD_BUTTON_ADS = 0x800, + CMD_BUTTON_DOWN = 0x1000, + CMD_BUTTON_BREATH = 0x2000, + CMD_BUTTON_FRAG = 0x4000, + CMD_BUTTON_OFFHAND_SECONDARY = 0x8000, + CMD_BUTTON_THROW = 0x80000, + }; + +#pragma pack(push, 4) + struct usercmd_s + { + int serverTime; + int buttons; + int angles[3]; + unsigned __int16 weapon; + unsigned __int16 primaryWeaponForAltMode; + unsigned __int16 offHandIndex; + char forwardmove; + char rightmove; + float meleeChargeYaw; + char meleeChargeDist; + char selectedLoc[2]; + char selectedLocAngle; + char remoteControlAngles[2]; + }; +#pragma pack(pop) + + struct LerpEntityState + { + char pad[0x70]; + }; + + struct clientLinkInfo_t + { + __int16 parentId; + char tagName; + char flags; + }; + + struct entityState_s + { + int number; + int eType; + LerpEntityState lerp; + int time2; + int otherEntityNum; + int attackerEntityNum; + int groundEntityNum; + int loopSound; + int surfType; + + union + { + int brushModel; + int triggerModel; + int item; + int xmodel; + int primaryLight; + } index; + + int clientNum; + int iHeadIcon; + int iHeadIconTeam; + int solid; + unsigned int eventParm; + int eventSequence; + int events[4]; + unsigned int eventParms[4]; + unsigned __int16 weapon; + int legsAnim; + int torsoAnim; + int un1; + int un2; + clientLinkInfo_t clientLinkInfo; + unsigned int partBits[6]; + int clientMask[1]; + }; + + struct clientActive_t + { + bool usingAds; + int timeoutcount; + clSnapshot_t snap; + bool alwaysFalse; + int serverTime; + int oldServerTime; + int oldFrameServerTime; + int serverTimeDelta; + int oldSnapServerTime; + int extrapolatedSnapshot; + int newSnapshots; + int serverId; + char mapname[64]; + int parseEntitiesIndex; + int parseClientsIndex; + int mouseDx[2]; + int mouseDy[2]; + int mouseIndex; + bool stanceHeld; + StanceState stance; + StanceState stancePosition; + int stanceTime; + int cgameUserCmdWeapon; + int cgameUserCmdOffHandIndex; + float cgameFOVSensitivityScale; + float cgameMaxPitchSpeed; + float cgameMaxYawSpeed; + float cgameKickAngles[3]; + float cgameOrigin[3]; + float cgameVelocity[3]; + float cgameViewangles[3]; + int cgameBobCycle; + int cgameMovementDir; + int cgameExtraButtons; + int cgamePredictedDataServerTime; + float clViewangles[3]; + usercmd_s cmds[128]; + int cmdNumber; + ClientArchiveData clientArchive[256]; + int clientArchiveIndex; + int packetBackupCount; + int packetBackupMask; + int parseEntitiesCount; + int parseClientsCount; + outPacket_t outPackets[32]; + clSnapshot_t snapshots[32]; + entityState_s parseEntities[19200]; + clientState_s parseClients[576]; + int corruptedTranslationFile; + char translationVersion[256]; + }; + struct MaterialTechnique { const char *name; @@ -722,9 +1727,9 @@ namespace Game { MaterialInfo info; char stateBitsEntry[48]; - char textureCount; - char constantCount; - char stateBitsCount; + unsigned char textureCount; + unsigned char constantCount; + unsigned char stateBitsCount; char stateFlags; char cameraRegion; MaterialTechniqueSet *techniqueSet; @@ -893,7 +1898,7 @@ namespace Game struct MSSChannelMap { - int speakerCount; + unsigned int speakerCount; MSSSpeakerLevels speakers[6]; }; @@ -937,7 +1942,7 @@ namespace Game { const char *aliasName; snd_alias_t *head; - int count; + unsigned int count; }; struct cStaticModel_s @@ -1080,7 +2085,7 @@ namespace Game struct __declspec(align(4)) MapEnts { const char *name; - char *entityString; + const char *entityString; int numEntityChars; MapTriggers trigger; Stage *stages; @@ -2333,14 +3338,14 @@ namespace Game unsigned int unsignedInt; float value; float vector[4]; - const char *string; - char color[4]; + const char* string; + unsigned char color[4]; }; struct $BFBB53559BEAC4289F32B924847E59CB { int stringCount; - const char **strings; + const char** strings; }; struct $9CA192F9DB66A3CB7E01DE78A0DEA53D @@ -2365,35 +3370,35 @@ namespace Game struct dvar_t { - const char *name; - const char *description; + const char* name; + const char* description; unsigned int flags; - char type; + dvar_type type; bool modified; DvarValue current; DvarValue latched; DvarValue reset; DvarLimits domain; - bool(__cdecl *domainFunc)(dvar_t *, DvarValue); + bool(__cdecl * domainFunc)(dvar_t*, DvarValue); dvar_t *hashNext; }; struct StaticDvar { - dvar_t *dvar; - char *dvarName; + dvar_t* dvar; + char* dvarName; }; struct StaticDvarList { int numStaticDvars; - StaticDvar **staticDvars; + StaticDvar** staticDvars; }; struct StringList { int totalStrings; - const char **strings; + const char** strings; }; struct ExpressionSupportingData @@ -2703,18 +3708,6 @@ namespace Game WEAPON_FIRETYPE_BURSTFIRE_FIRST = 0x2, WEAPON_FIRETYPE_BURSTFIRE_LAST = 0x4, }; - - enum OffhandClass - { - OFFHAND_CLASS_NONE = 0x0, - OFFHAND_CLASS_FRAG_GRENADE = 0x1, - OFFHAND_CLASS_SMOKE_GRENADE = 0x2, - OFFHAND_CLASS_FLASH_GRENADE = 0x3, - OFFHAND_CLASS_THROWINGKNIFE = 0x4, - OFFHAND_CLASS_OTHER = 0x5, - OFFHAND_CLASS_COUNT = 0x6, - }; - enum weapStance_t { WEAPSTANCE_STAND = 0x0, @@ -3691,6 +4684,19 @@ namespace Game AddonMapEnts *addonMapEnts; }; + /* 9210 */ + struct weaponParms + { + float forward[3]; + float right[3]; + float up[3]; + float muzzleTrace[3]; + float gunForward[3]; + unsigned int weaponIndex; + const WeaponDef* weapDef; + const WeaponCompleteDef* weapCompleteDef; + }; + struct XAsset { XAssetType type; @@ -3707,10 +4713,10 @@ namespace Game { XAsset asset; char zoneIndex; - volatile char inuse; + volatile char inuseMask; + bool printedMissingAsset; unsigned __int16 nextHash; unsigned __int16 nextOverride; - unsigned __int16 usageFrame; }; enum XFileLanguage : unsigned char @@ -3963,25 +4969,6 @@ namespace Game }; #pragma pack(pop) -#pragma pack(push, 4) - struct usercmd_s - { - int serverTime; - int buttons; - int angles[3]; - unsigned __int16 weapon; - unsigned __int16 primaryWeaponForAltMode; - unsigned __int16 offHandIndex; - char forwardmove; - char rightmove; - float meleeChargeYaw; - char meleeChargeDist; - char selectedLoc[2]; - char selectedLocAngle; - char remoteControlAngles[2]; - }; -#pragma pack(pop) - typedef char mapname_t[40]; struct traceWork_t @@ -4073,18 +5060,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 @@ -4202,6 +5209,35 @@ namespace Game char ipx[10]; }; + struct netProfileInfo_t + { + char __pad0[0x5E0]; + }; + + static_assert(sizeof(netProfileInfo_t) == 0x5E0); + + struct netchan_t + { + int outgoingSequence; + netsrc_t sock; + int dropped; + int incomingSequence; + netadr_t remoteAddress; + int qport; + int fragmentSequence; + int fragmentLength; + char* fragmentBuffer; + int fragmentBufferSize; + int unsentFragments; + int unsentFragmentStart; + int unsentLength; + char* unsentBuffer; + int unsentBufferSize; + netProfileInfo_t prof; + }; + + static_assert(sizeof(netchan_t) == 0x62C); + struct FxEditorElemAtlas { int behavior; @@ -4452,76 +5488,190 @@ namespace Game PLAYER_FLAG_FROZEN = 1 << 2, }; + typedef enum + { + SESS_STATE_PLAYING = 0x0, + SESS_STATE_DEAD = 0x1, + SESS_STATE_SPECTATOR = 0x2, + SESS_STATE_INTERMISSION = 0x3 + } sessionState_t; + + typedef enum + { + CON_DISCONNECTED = 0x0, + CON_CONNECTING = 0x1, + CON_CONNECTED = 0x2 + } clientConnected_t; + typedef struct gclient_s { - unsigned char pad[12764]; - unsigned int team; + playerState_s ps; + sessionState_t sessionState; // 12572 + char pad0[40]; + clientConnected_t connected; // 12616 + char pad1[144]; + unsigned int team; // 12764 char pad2[436]; - int flags; - char pad3[724]; + int flags; // 13204 + int spectatorClient; + int lastCmdTime; + int buttons; + int oldbuttons; // 13220 + int latched_buttons; // 13224 + int buttonsSinceLastFrame; // 13228 + char pad3[700]; // 13232 } gclient_t; + static_assert(sizeof(gclient_t) == 13932); + + struct EntHandle + { + unsigned __int16 number; + unsigned __int16 infoIndex; + }; + + struct entityShared_t + { + char isLinked; + char modelType; + char svFlags; + char isInUse; + Bounds box; + int contents; + Bounds absBox; + float currentOrigin[3]; + float currentAngles[3]; + EntHandle ownerNum; + int eventTime; + }; + + enum EntHandler + { + ENT_HANDLER_NULL = 0x0, + ENT_HANDLER_TRIGGER_MULTIPLE = 0x1, + ENT_HANDLER_TRIGGER_HURT = 0x2, + ENT_HANDLER_TRIGGER_HURT_TOUCH = 0x3, + ENT_HANDLER_TRIGGER_DAMAGE = 0x4, + ENT_HANDLER_SCRIPT_MOVER = 0x5, + ENT_HANDLER_SCRIPT_MODEL = 0x6, + ENT_HANDLER_GRENADE = 0x7, + ENT_HANDLER_TIMED_OBJECT = 0x8, + ENT_HANDLER_ROCKET = 0x9, + ENT_HANDLER_CLIENT = 0xA, + ENT_HANDLER_CLIENT_SPECTATOR = 0xB, + ENT_HANDLER_CLIENT_DEAD = 0xC, + ENT_HANDLER_PLAYER_CLONE = 0xD, + ENT_HANDLER_TURRET_INIT = 0xE, + ENT_HANDLER_TURRET = 0xF, + ENT_HANDLER_DROPPED_ITEM = 0x10, + ENT_HANDLER_ITEM_INIT = 0x11, + ENT_HANDLER_ITEM = 0x12, + ENT_HANDLER_PRIMARY_LIGHT = 0x13, + ENT_HANDLER_PLAYER_BLOCK = 0x14, + ENT_HANDLER_VEHICLE = 0x15, + + ENT_HANDLER_COUNT + }; + typedef struct gentity_s { - int number; - unsigned char pad[308]; // 4 - float origin[3]; // 312 - float angles[3]; // 324 - char pad2[8]; + entityState_s s; + entityShared_t r; gclient_t* client; // 344 - unsigned char pad3[28]; - short classname; - short pad4; - unsigned char pad5[248]; + void /*Turret*/* turret; + void /*Vehicle*/* vehicle; + int physObjId; + unsigned __int16 model; + unsigned char physicsObject; + unsigned char takedamage; + unsigned char active; + unsigned char handler; + unsigned char team; + bool freeAfterEvent; + __int16 padding_short; + unsigned __int16 classname; + unsigned __int16 script_classname; + unsigned __int16 script_linkName; + unsigned __int16 target; + unsigned __int16 targetname; + unsigned int attachIgnoreCollision; + int spawnflags; + int flags; + int eventTime; + int clipmask; + int processedFrame; + EntHandle parent; + int nextthink; + int health; + int maxHealth; + int damage; + int count; + EntHandle missileTargetEnt; + EntHandle remoteControlledOwner; + gentity_s* tagChildren; + unsigned __int16 attachModelNames[19]; + unsigned __int16 attachTagNames[19]; + int useCount; + gentity_s* nextFree; + int birthTime; + char pad[100]; } gentity_t; + static_assert(sizeof(gentity_s) == 0x274); + + struct lockonFireParms + { + bool lockon; + gentity_s* target; + float targetPosOrOffset[3]; + bool topFire; + }; + #pragma pack(push, 1) + typedef struct client_s { - // 0 - clientstate_t state; - // 4 - char _pad[4]; - // 8 - int deltaMessage; - // 12 - char __pad[12]; - // 24 - int outgoingSequence; - // 28 - char pad[12]; - // 40 - netadr_t addr; - // 60 - char pad1[1568]; - // 1628 - char connectInfoString[1024]; - // 2652 - char pad2[133192]; - // 135844 - char name[16]; - // 135860 - char pad3[12]; - // 135872 - int snapNum; - // 135876 - int pad4; - // 135880 - short ping; - // 135882 - //char pad5[142390]; - char pad5[133158]; - // 269040 - int isBot; - // 269044 - char pad6[9228]; - // 278272 - unsigned __int64 steamid; - // 278280 - char pad7[403592]; + clientstate_t state; // 0 + char __pad0[4]; // 4 + int deltaMessage; // 8 + char __pad1[12]; // 12 + netchan_t netchan; // 24 + char __pad2[20]; // 1604 + const char* delayDropReason; // 1624 + char connectInfoString[1024]; // 1628 + char __pad3[132096]; // 2652 + int reliableSequence; // 134748 + int reliableAcknowledge; // 134752 + int reliableSent; // 134756 + int messageAcknowledge; // 134760 + int gamestateMessageNum; // 134764 + int challenge; // 134768 + usercmd_s lastUsercmd; // 134772 + int lastClientCommand; // 134812 + char lastClientCommandString[1024]; // 134816 + gentity_t* gentity; // 135840 + char name[16]; // 135844 + char __pad4[4]; // 135860 + int lastPacketTime; // 135864 + int lastConnectTime; // 135868 + int snapNum; // 135872 + int __pad5; // 135876 + short ping; // 135880 + char __pad6[14]; // 135882 + int pureAuthentic; // 135896 + char __pad7[133138]; // 135900 + short scriptID; // 269038 + int isBot; // 269040 + int serverID; // 269044 + char __pad8[9224]; // 269048 + unsigned __int64 steamID; // 278272 + char __pad9[403592]; // 278280 } client_t; + #pragma pack(pop) + static_assert(sizeof(client_t) == 0xA6790); + struct CModelAllocData { void* mainArray; @@ -4816,6 +5966,1236 @@ namespace Game GfxCmdBufState *state; }; + struct GfxDrawGroupSetupFields + { + unsigned __int16 materialSortedIndex : 15; + unsigned __int16 useHeroLighting : 1; + char sceneLightIndex; + char surfType; + }; + + union GfxDrawGroupSetup + { + GfxDrawGroupSetupFields fields; + unsigned int packed; + }; + + struct GfxMarkSurfLightingFields + { + char lmapIndex; + char reflectionProbeIndex; + unsigned __int16 modelIndex; + }; + + union GfxMarkSurfLighting + { + GfxMarkSurfLightingFields fields; + unsigned int packed; + }; + + struct GfxMarkSurf + { + GfxDrawGroupSetup drawGroup; + unsigned __int16* indices; + unsigned __int16 triCount; + char modelType; + char pad; + GfxMarkSurfLighting lighting; + }; + + struct GfxCodeSurf + { + GfxDrawGroupSetup drawGroup; + unsigned int triCount; + unsigned __int16* indices; + unsigned __int16 argOffset; + unsigned __int16 argCount; + }; + + struct __declspec(align(4)) GfxGlassSurf + { + GfxDrawGroupSetup drawGroup; + char pad; + char reflectionProbeIndex; + unsigned __int16 triCount; + unsigned __int16* indices; + unsigned __int16 lightingHandle; + }; + + struct GfxCloudSurfFields + { + unsigned __int16 materialSortedIndex; + char cloudDataIndex; + char surfType; + }; + + union GfxCloudSurf + { + GfxCloudSurfFields fields; + unsigned int packed; + }; + + struct GfxSparkSurfFields + { + unsigned __int16 materialSortedIndex; + unsigned __int16 sparkDataIndex; + }; + + union GfxSparkSurf + { + GfxSparkSurfFields fields; + unsigned int packed; + }; + + struct GfxSceneDef + { + int time; + float floatTime; + float viewOffset[3]; + GfxImage* sunShadowImage; + float sunShadowPixelAdjust[4]; + }; + + struct GfxLight + { + char type; + char canUseShadowMap; + char unused[2]; + float color[3]; + float dir[3]; + float origin[3]; + float radius; + float cosHalfFovOuter; + float cosHalfFovInner; + int exponent; + unsigned int spotShadowIndex; + GfxLightDef* def; + }; + + struct GfxVisibleLight + { + char pad[0x2004]; + }; + + struct GfxEntity + { + unsigned int renderFxFlags; + float materialTime; + }; + + struct GfxSkinnedXModelSurfs + { + void* firstSurf; + }; + + struct GfxSceneEntityCull + { + volatile unsigned int state; + Bounds bounds; + GfxSkinnedXModelSurfs skinnedSurfs; + }; + + union GfxSceneEntityInfo + { + void/*cpose_t*/* pose; + unsigned __int16* cachedLightingHandle; + }; + + struct DSkelPartBits + { + int anim[6]; + int control[6]; + int worldCtrl[6]; + int skel[6]; + }; + + struct DSkel + { + DSkelPartBits partBits; + int timeStamp; + /*DObjAnimMat*/void* mat; + }; + + struct DObj + { + /*XAnimTree_s*/ void* tree; + unsigned __int16 duplicateParts; + unsigned __int16 entnum; + char duplicatePartsSize; + char numModels; + char numBones; + char flags; + unsigned int ignoreCollision; + volatile int locked; + DSkel skel; + float radius; + unsigned int hidePartBits[6]; + XModel** models; + }; + + struct GfxSceneEntity + { + float lightingOrigin[3]; + GfxPlacement placement; + GfxSceneEntityCull cull; + char lods[32]; + unsigned __int32 gfxEntIndex : 7; + unsigned __int32 entnum : 12; + unsigned __int32 renderFxFlags : 13; + DObj* obj; + GfxSceneEntityInfo info; + char reflectionProbeIndex; + }; + + struct GfxScaledPlacement + { + GfxPlacement base; + float scale; + }; + + struct GfxSceneModel + { + XModelDrawInfo info; + XModel* model; + DObj* obj; + GfxScaledPlacement placement; + unsigned __int32 gfxEntIndex : 7; + unsigned __int32 entnum : 12; + unsigned __int32 renderFxFlags : 13; + float radius; + unsigned __int16* cachedLightingHandle; + float lightingOrigin[3]; + char reflectionProbeIndex; + char lod; + }; + + struct __declspec(align(4)) GfxSceneBrush + { + BModelDrawInfo info; + unsigned __int16 entnum; + GfxBrushModel* bmodel; + GfxPlacement placement; + char reflectionProbeIndex; + }; + + union GfxSceneGlass + { + struct + { + bool rendered; + char reflectionProbeIndex; + unsigned __int16 lightingHandle; + }; + unsigned int packed; + }; + + union GfxEntCellRefInfo + { + float radius; + GfxBrushModel* bmodel; + }; + + struct GfxSceneDpvs + { + unsigned int localClientNum; + char* entVisData[7]; + unsigned __int16* sceneXModelIndex; + unsigned __int16* sceneDObjIndex; + GfxEntCellRefInfo* entInfo[4]; + }; + + struct __declspec(align(64)) GfxScene + { + GfxCodeSurf codeEmissiveSurfs[2048]; + GfxCodeSurf codeTransSurfs[640]; + GfxMarkSurf markSurfs[1536]; + GfxGlassSurf glassSurfs[768]; + GfxCloudSurf cloudSurfs[256]; + GfxDrawSurf drawSurfsDepthHack[32]; + GfxDrawSurf drawSurfsLitOpaque[8192]; + GfxDrawSurf drawSurfsLitTrans[2048]; + GfxDrawSurf drawSurfsEmissive[8192]; + GfxDrawSurf drawSurfsSunShadow0[4096]; + GfxDrawSurf drawSurfsSunShadow1[8192]; + GfxDrawSurf drawSurfsSpotShadow0[896]; + GfxDrawSurf drawSurfsSpotShadow1[896]; + GfxDrawSurf drawSurfsSpotShadow2[896]; + GfxDrawSurf drawSurfsSpotShadow3[896]; + unsigned int sceneLightIsUsed[32]; + unsigned int cachedSceneLightIsUsed[4][32]; + GfxSparkSurf sparkSurfs[64]; + unsigned int drawSurfLimit[10]; + volatile int drawSurfCount[10]; + GfxDrawSurf* drawSurfs[10]; + volatile int codeSurfUser[2]; + volatile int markMeshGuard; + unsigned int codeEmissiveSurfCount; + unsigned int codeTransSurfCount; + unsigned int markSurfCount; + unsigned int glassSurfCount; + GfxSceneDef def; + unsigned int addedLightCount; + GfxLight addedLight[32]; + bool isAddedLightCulled[32]; + float dynamicSpotLightNearPlaneOffset; + float dynamicSpotLightLength; + GfxVisibleLight visLight[4]; + GfxVisibleLight visLightShadow[1]; + unsigned int* entOverflowedDrawBuf; + volatile int gfxEntCount; + GfxEntity gfxEnts[128]; + int sceneDObjCount; + int preClientSceneDObjCount; + int sceneDObjCountAtMark; + GfxSceneEntity sceneDObj[520]; + char sceneDObjVisData[7][512]; + int sceneDObjMarkableViewmodelIndex; + unsigned int sceneDObjFirstViewmodelIndex; + unsigned int sceneDObjViewmodelCount; + volatile int sceneModelCount; + int sceneModelCountAtMark; + int sceneDObjModelCount; + GfxSceneModel sceneModel[1024]; + char sceneModelVisData[7][1024]; + volatile int sceneBrushCount; + int sceneBrushCountAtMark; + GfxSceneBrush sceneBrush[512]; + char sceneBrushVisData[3][512]; + GfxSceneGlass sceneGlass[1024]; + unsigned int sceneDynModelCount; + unsigned int sceneDynBrushCount; + int gfxEntCountAtMark; + GfxSceneDpvs dpvs; + int updateSound; + int allowAddDObj; + }; + + enum TextRenderFlags + { + TEXT_RENDERFLAG_FORCEMONOSPACE = 0x1, + TEXT_RENDERFLAG_CURSOR = 0x2, + TEXT_RENDERFLAG_DROPSHADOW = 0x4, + TEXT_RENDERFLAG_DROPSHADOW_EXTRA = 0x8, + TEXT_RENDERFLAG_GLOW = 0x10, + TEXT_RENDERFLAG_GLOW_FORCE_COLOR = 0x20, + TEXT_RENDERFLAG_FX_DECODE = 0x40, + TEXT_RENDERFLAG_PADDING = 0x80, + TEXT_RENDERFLAG_SUBTITLETEXT = 0x100, + TEXT_RENDERFLAG_CINEMATIC = 0x200, + TEXT_RENDERFLAG_OUTLINE = 0x400, + TEXT_RENDERFLAG_OUTLINE_EXTRA = 0x800, + }; + + enum FontPassType + { + FONTPASS_NORMAL = 0x0, + FONTPASS_GLOW = 0x1, + FONTPASS_OUTLINE = 0x2, + FONTPASS_COUNT = 0x3, + }; + + struct AimInput + { + float deltaTime; + float deltaTimeScaled; + float pitch; + float pitchAxis; + float pitchMax; + float yaw; + float yawAxis; + float yawMax; + float forwardAxis; + float rightAxis; + int buttons; + int localClientNum; + }; + + struct AimOutput + { + float pitch; + float yaw; + float meleeChargeYaw; + char meleeChargeDist; + }; + + struct clientLogo_t + { + int startTime; + int duration; + int fadein; + int fadeout; + Material* material[2]; + }; + + struct vidConfig_t + { + unsigned int sceneWidth; + unsigned int sceneHeight; + unsigned int displayWidth; + unsigned int displayHeight; + unsigned int displayFrequency; + int isFullscreen; + float aspectRatioWindow; + float aspectRatioScenePixel; + float aspectRatioDisplayPixel; + unsigned int maxTextureSize; + unsigned int maxTextureMaps; + bool deviceSupportsGamma; + }; + + struct trDebugLine_t + { + float start[3]; + float end[3]; + float color[4]; + int depthTest; + }; + + struct trDebugString_t + { + float xyz[3]; + float color[4]; + float scale; + char text[96]; + }; + + struct clientDebugStringInfo_t + { + int max; + int num; + trDebugString_t* strings; + int* durations; + }; + + struct clientDebugLineInfo_t + { + int max; + int num; + trDebugLine_t* lines; + int* durations; + }; + + struct clientDebug_t + { + int prevFromServer; + int fromServer; + clientDebugStringInfo_t clStrings; + clientDebugStringInfo_t svStringsBuffer; + clientDebugStringInfo_t svStrings; + clientDebugLineInfo_t clLines; + clientDebugLineInfo_t svLinesBuffer; + clientDebugLineInfo_t svLines; + }; + + struct ClientMatchData + { + char def[64]; + char data[1024]; + }; + + struct clientStatic_t + { + int quit; + int hunkUsersStarted; + char servername[256]; + int rendererStarted; + int soundStarted; + int uiStarted; + int frametime; + float frametime_base; + int realtime; + bool gpuSyncedPrevFrame; + bool inputUpdatedPrevFrame; + clientLogo_t logo; + float mapCenter[3]; + int lastServerPinged; + int pingedServerCount; + int totalServersParsed; + int pingUpdateSource; + Material* whiteMaterial; + Material* consoleMaterial; + Font_s* consoleFont; + vidConfig_t vidConfig; + clientDebug_t debug; + int doVidRestart; + ClientMatchData matchData; + XNADDR xnaddrs[18]; + float debugRenderPos[3]; + int skelValid; + int skelTimeStamp; + volatile int skelMemPos; + char skelMemory[262144]; + char* skelMemoryStart; + bool allowedAllocSkel; + int serverId; + gameState_t gameState; + clSnapshot_t noDeltaSnapshot; + int nextNoDeltaEntity; + entityState_s noDeltaEntities[1024]; + }; + + struct ConDrawInputGlob + { + char autoCompleteChoice[64]; + int matchIndex; + int matchCount; + const char* inputText; + int inputTextLen; + bool hasExactMatch; + bool mayAutoComplete; + float x; + float y; + float leftX; + float fontHeight; + }; + + struct ScreenPlacement + { + float scaleVirtualToReal[2]; + float scaleVirtualToFull[2]; + float scaleRealToVirtual[2]; + float realViewportPosition[2]; + float realViewportSize[2]; + float virtualViewableMin[2]; + float virtualViewableMax[2]; + float realViewableMin[2]; + float realViewableMax[2]; + float virtualAdjustableMin[2]; + float virtualAdjustableMax[2]; + float realAdjustableMin[2]; + float realAdjustableMax[2]; + float subScreenLeft; + }; + + struct serverStatusInfo_t + { + char address[64]; + const char* lines[128][4]; + char text[1024]; + char pings[54]; + int numLines; + }; + + struct pendingServer_t + { + char adrstr[64]; + char name[64]; + int startTime; + int serverNum; + int valid; + }; + + struct pendingServerStatus_t + { + int num; + pendingServer_t server[16]; + }; + + struct pinglist_t + { + char adrstr[64]; + int start; + }; + + struct serverStatus_s + { + pinglist_t pingList[16]; + int numqueriedservers; + int currentping; + int nextpingtime; + int maxservers; + int refreshtime; + int numServers; + int sortKey; + int sortDir; + int lastCount; + int refreshActive; + int currentServer; + int displayServers[20000]; + int numDisplayServers; + int serverCount; + int numPlayersOnServers; + int nextDisplayRefresh; + int nextSortTime; + int motdLen; + int motdWidth; + int motdPaintX; + int motdPaintX2; + int motdOffset; + int motdTime; + char motd[1024]; + }; + + struct mapInfo + { + char mapName[32]; + char mapLoadName[16]; + char mapDescription[32]; + char mapLoadImage[32]; + char mapCustomKey[32][16]; + char mapCustomValue[32][64]; + int mapCustomCount; + int teamMembers; + int typeBits; + int timeToBeat[32]; + int active; + }; + + struct gameTypeInfo + { + char gameType[12]; + char gameTypeName[32]; + }; + + struct CachedAssets_t + { + Material* scrollBarArrowUp; + Material* scrollBarArrowDown; + Material* scrollBarArrowLeft; + Material* scrollBarArrowRight; + Material* scrollBar; + Material* scrollBarThumb; + Material* sliderBar; + Material* sliderThumb; + Material* whiteMaterial; + Material* cursor; + Material* textDecodeCharacters; + Material* textDecodeCharactersGlow; + Font_s* bigFont; + Font_s* smallFont; + Font_s* consoleFont; + Font_s* boldFont; + Font_s* textFont; + Font_s* extraBigFont; + Font_s* objectiveFont; + Font_s* hudBigFont; + Font_s* hudSmallFont; + snd_alias_list_t* itemFocusSound; + }; + + struct sharedUiInfo_t + { + CachedAssets_t assets; + int playerCount; + char playerNames[18][32]; + char teamNames[18][32]; + int playerClientNums[18]; + volatile int updateGameTypeList; + int numGameTypes; + gameTypeInfo gameTypes[32]; + int numCustomGameTypes; + gameTypeInfo customGameTypes[32]; + char customGameTypeCancelState[2048]; + int numJoinGameTypes; + gameTypeInfo joinGameTypes[32]; + volatile int updateArenas; + int mapCount; + mapInfo mapList[128]; + int mapIndexSorted[128]; + bool mapsAreSorted; + Material* serverHardwareIconList[9]; + unsigned __int64 partyMemberXuid; + Material* talkingIcons[2]; + serverStatus_s serverStatus; + char serverStatusAddress[64]; + serverStatusInfo_t serverStatusInfo; + int nextServerStatusRefresh; + pendingServerStatus_t pendingServerStatus; + }; + + struct GraphFloat + { + char name[64]; + float knots[32][2]; + unsigned __int16 knotCount; + float scale; + }; + + struct __declspec(align(8)) cg_s + { + playerState_s predictedPlayerState; + char _pad0[0x254]; + void* snap; + void* nextSnap; + char _pad1[0x673DC]; + int frametime; // + 0x6A754 + int time; + int oldTime; + int physicalsTime; + char _pad2[0x9600]; // + 0x6A758 + float compassMapWorldSize[2]; // + 0x73D64 + char _pad3[0x74]; // + 0x73D6C + float selectedLocation[2]; // + 0x73DE0 + float selectedLocationAngle; + float selectedAngleLocation[2]; + float selectedLocationPrev[2]; + float selectedLocationAnglePrev; + char _pad4[0x89740]; + }; + + static_assert(sizeof(cg_s) == 0xFD540); + + static constexpr auto MAX_GAMEPADS = 1; + + static constexpr auto GPAD_VALUE_MASK = 0xFFFFFFFu; + static constexpr auto GPAD_DPAD_MASK = XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_RIGHT; + static constexpr auto GPAD_DIGITAL_MASK = 1u << 28; + static constexpr auto GPAD_ANALOG_MASK = 1u << 29; + static constexpr auto GPAD_STICK_MASK = 1u << 30; + + enum GamePadButton + { + GPAD_NONE = 0, + GPAD_UP = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_UP & GPAD_VALUE_MASK), + GPAD_DOWN = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_DOWN & GPAD_VALUE_MASK), + GPAD_LEFT = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_LEFT & GPAD_VALUE_MASK), + GPAD_RIGHT = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_RIGHT & GPAD_VALUE_MASK), + GPAD_START = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_START & GPAD_VALUE_MASK), + GPAD_BACK = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_BACK & GPAD_VALUE_MASK), + GPAD_L3 = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_LEFT_THUMB & GPAD_VALUE_MASK), + GPAD_R3 = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_RIGHT_THUMB & GPAD_VALUE_MASK), + GPAD_L_SHLDR = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_LEFT_SHOULDER & GPAD_VALUE_MASK), + GPAD_R_SHLDR = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_RIGHT_SHOULDER & GPAD_VALUE_MASK), + GPAD_A = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_A & GPAD_VALUE_MASK), + GPAD_B = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_B & GPAD_VALUE_MASK), + GPAD_X = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_X & GPAD_VALUE_MASK), + GPAD_Y = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_Y & GPAD_VALUE_MASK), + GPAD_L_TRIG = GPAD_ANALOG_MASK | (0 & GPAD_VALUE_MASK), + GPAD_R_TRIG = GPAD_ANALOG_MASK | (1 & GPAD_VALUE_MASK), + }; + + enum GamePadStick + { + GPAD_INVALID = 0x0, + GPAD_LX = GPAD_STICK_MASK | (0 & GPAD_VALUE_MASK), + GPAD_LY = GPAD_STICK_MASK | (1 & GPAD_VALUE_MASK), + GPAD_RX = GPAD_STICK_MASK | (2 & GPAD_VALUE_MASK), + GPAD_RY = GPAD_STICK_MASK | (3 & GPAD_VALUE_MASK), + }; + + enum GamePadButtonEvent + { + GPAD_BUTTON_RELEASED = 0x0, + GPAD_BUTTON_PRESSED = 0x1, + GPAD_BUTTON_UPDATE = 0x2, + }; + + enum GamepadPhysicalAxis + { + GPAD_PHYSAXIS_NONE = -1, + GPAD_PHYSAXIS_RSTICK_X = 0x0, + GPAD_PHYSAXIS_RSTICK_Y = 0x1, + GPAD_PHYSAXIS_LSTICK_X = 0x2, + GPAD_PHYSAXIS_LSTICK_Y = 0x3, + GPAD_PHYSAXIS_RTRIGGER = 0x4, + GPAD_PHYSAXIS_LTRIGGER = 0x5, + + GPAD_PHYSAXIS_COUNT, + }; + + enum GamepadVirtualAxis + { + GPAD_VIRTAXIS_NONE = -1, + GPAD_VIRTAXIS_SIDE = 0x0, + GPAD_VIRTAXIS_FORWARD = 0x1, + GPAD_VIRTAXIS_UP = 0x2, + GPAD_VIRTAXIS_YAW = 0x3, + GPAD_VIRTAXIS_PITCH = 0x4, + GPAD_VIRTAXIS_ATTACK = 0x5, + + GPAD_VIRTAXIS_COUNT + }; + + enum GamePadStickDir + { + GPAD_STICK_POS = 0x0, + GPAD_STICK_NEG = 0x1, + + GPAD_STICK_DIR_COUNT + }; + + enum GamepadMapping + { + GPAD_MAP_NONE = -1, + GPAD_MAP_LINEAR = 0x0, + GPAD_MAP_SQUARED = 0x1, + + GPAD_MAP_COUNT + }; + + struct ButtonToCodeMap_t + { + GamePadButton padButton; + int code; + }; + + struct StickToCodeMap_t + { + GamePadStick padStick; + int posCode; + int negCode; + }; + + struct GamepadVirtualAxisMapping + { + GamepadPhysicalAxis physicalAxis; + GamepadMapping mapType; + }; + + struct GpadAxesGlob + { + float axesValues[GPAD_PHYSAXIS_COUNT]; + GamepadVirtualAxisMapping virtualAxes[GPAD_VIRTAXIS_COUNT]; + }; + + enum weaponstate_t + { + WEAPON_READY = 0x0, + WEAPON_RAISING = 0x1, + WEAPON_RAISING_ALTSWITCH = 0x2, + WEAPON_DROPPING = 0x3, + WEAPON_DROPPING_QUICK = 0x4, + WEAPON_DROPPING_ALT = 0x5, + WEAPON_FIRING = 0x6, + WEAPON_RECHAMBERING = 0x7, + WEAPON_RELOADING = 0x8, + WEAPON_RELOADING_INTERUPT = 0x9, + WEAPON_RELOAD_START = 0xA, + WEAPON_RELOAD_START_INTERUPT = 0xB, + WEAPON_RELOAD_END = 0xC, + WEAPON_MELEE_INIT = 0xD, + WEAPON_MELEE_FIRE = 0xE, + WEAPON_MELEE_END = 0xF, + WEAPON_OFFHAND_INIT = 0x10, + WEAPON_OFFHAND_PREPARE = 0x11, + WEAPON_OFFHAND_HOLD = 0x12, + WEAPON_OFFHAND_FIRE = 0x13, + WEAPON_OFFHAND_DETONATE = 0x14, + WEAPON_OFFHAND_END = 0x15, + WEAPON_DETONATING = 0x16, + WEAPON_SPRINT_RAISE = 0x17, + WEAPON_SPRINT_LOOP = 0x18, + WEAPON_SPRINT_DROP = 0x19, + WEAPON_STUNNED_START = 0x1A, + WEAPON_STUNNED_LOOP = 0x1B, + WEAPON_STUNNED_END = 0x1C, + WEAPON_NIGHTVISION_WEAR = 0x1D, + WEAPON_NIGHTVISION_REMOVE = 0x1E, + + WEAPONSTATES_NUM + }; + + struct AimAssistPlayerState + { + float velocity[3]; + int eFlags; + int linkFlags; + int pm_flags; + int weapFlags; + int weaponState; + float fWeaponPosFrac; + int weapIndex; + bool hasAmmo; + bool isDualWielding; + bool isThirdPerson; + bool isExtendedMelee; + }; + + struct AimTweakables + { + float slowdownRegionWidth; + float slowdownRegionHeight; + float autoAimRegionWidth; + float autoAimRegionHeight; + float autoMeleeRegionWidth; + float autoMeleeRegionHeight; + float lockOnRegionWidth; + float lockOnRegionHeight; + }; + + constexpr auto AIM_TARGET_INVALID = 0x3FF; + struct AimScreenTarget + { + int entIndex; + float clipMins[2]; + float clipMaxs[2]; + float aimPos[3]; + float velocity[3]; + float distSqr; + float crosshairDistSqr; + }; + + enum AutoMeleeState + { + AIM_MELEE_STATE_OFF = 0x0, + AIM_MELEE_STATE_TARGETED = 0x1, + AIM_MELEE_STATE_UPDATING = 0x2, + }; + +#pragma warning(push) +#pragma warning(disable: 4324) + struct __declspec(align(16)) AimAssistGlobals + { + AimAssistPlayerState ps; + char _pad1[4]; + float screenMtx[4][4]; + float invScreenMtx[4][4]; + bool initialized; + int prevButtons; + AimTweakables tweakables; + float eyeOrigin[3]; + float viewOrigin[3]; + float viewAngles[3]; + float viewAxis[3][3]; + float fovTurnRateScale; + float fovScaleInv; + float adsLerp; + float pitchDelta; + float yawDelta; + float screenWidth; + float screenHeight; + AimScreenTarget screenTargets[64]; + int screenTargetCount; + int autoAimTargetEnt; + bool autoAimPressed; + bool autoAimActive; + float autoAimPitch; + float autoAimPitchTarget; + float autoAimYaw; + float autoAimYawTarget; + AutoMeleeState autoMeleeState; + int autoMeleeTargetEnt; + float autoMeleePitch; + float autoMeleePitchTarget; + float autoMeleeYaw; + float autoMeleeYawTarget; + int lockOnTargetEnt; + }; +#pragma warning(pop) + + enum ShockViewTypes + { + SHELLSHOCK_VIEWTYPE_BLURRED = 0x0, + SHELLSHOCK_VIEWTYPE_FLASHED = 0x1, + SHELLSHOCK_VIEWTYPE_NONE = 0x2, + }; + + struct shellshock_parms_t + { + struct + { + int blurredFadeTime; + int blurredEffectTime; + int flashWhiteFadeTime; + int flashShotFadeTime; + ShockViewTypes type; + } screenBlend; + + struct + { + int fadeTime; + float kickRate; + float kickRadius; + } view; + + struct + { + bool affect; + char loop[64]; + char loopSilent[64]; + char end[64]; + char endAbort[64]; + int fadeInTime; + int fadeOutTime; + float drylevel; + float wetlevel; + char roomtype[16]; + float channelvolume[64]; + int modEndDelay; + int loopFadeTime; + int loopEndDelay; + } sound; + + struct + { + bool affect; + int fadeTime; + float mouseSensitivity; + float maxPitchSpeed; + float maxYawSpeed; + } lookControl; + + struct + { + bool affect; + } movement; + }; + + struct XAnimParent + { + unsigned short flags; + unsigned short children; + }; + + struct XAnimEntry + { + unsigned short numAnims; + unsigned short parent; + + union + { + XAnimParts* parts; + XAnimParent animParent; + }; + }; + + struct XAnim_s + { + unsigned int size; + const char* debugName; + const char** debugAnimNames; + XAnimEntry entries[1]; + }; + + struct animation_s + { + char name[64]; + int initialLerp; + float moveSpeed; + int duration; + int nameHash; + int flags; + int64_t movetype; + int noteType; + }; + + struct lerpFrame_t + { + float yawAngle; + int yawing; + float pitchAngle; + int pitching; + int animationNumber; + animation_s* animation; + int animationTime; + float oldFramePos[3]; + float animSpeedScale; + int oldFrameSnapshotTime; + }; + + struct clientControllers_t + { + float angles[4][3]; + float tag_origin_angles[3]; + float tag_origin_offset[3]; + }; + + struct __declspec(align(4)) XAnimTree_s + { + XAnim_s* anims; + int info_usage; + volatile int calcRefCount; + volatile int modifyRefCount; + unsigned __int16 children; + }; + + enum PlayerDiveState + { + DIVE_NONE = 0x0, + DIVE_FORWARD = 0x1, + DIVE_FORWARDLEFT = 0x2, + DIVE_LEFT = 0x3, + DIVE_BACKLEFT = 0x4, + DIVE_BACK = 0x5, + DIVE_BACKRIGHT = 0x6, + DIVE_RIGHT = 0x7, + DIVE_FORWARDRIGHT = 0x8, + }; + + struct clientInfo_t + { + int infoValid; + int nextValid; + int clientNum; + char name[16]; + team_t team; + team_t oldteam; + int rank; + int prestige; + unsigned int perks[2]; + int score; + int location; + int health; + char model[64]; + char attachModelNames[6][64]; + char attachTagNames[6][64]; + unsigned int partBits[6]; + lerpFrame_t legs; + lerpFrame_t torso; + float lerpMoveDir; + float lerpLean; + float playerAngles[3]; + int legsAnim; + int torsoAnim; + float fTorsoPitch; + float fWaistPitch; + int leftHandGun; + int dobjDirty; + clientControllers_t control; + unsigned int clientConditions[18][2]; + XAnimTree_s* pXAnimTree; + int iDObjWeapon; + char weaponModel; + int stanceTransitionTime; + int turnAnimEndTime; + char turnAnimType; + bool hideWeapon; + bool usingKnife; + int dualWielding; + PlayerDiveState diveState; + int riotShieldNext; + unsigned int playerCardIcon; + unsigned int playerCardTitle; + unsigned int playerCardNameplate; + }; + + struct cgs_t + { + int viewX; + int viewY; + int viewWidth; + int viewHeight; + float viewAspect; + int serverCommandSequence; + int processedSnapshotNum; + int localServer; + char gametype[32]; + char szHostName[256]; + bool hardcore; + int maxclients; + int privateClients; + char mapname[64]; + int gameEndTime; + int voteTime; + int voteYes; + int voteNo; + char voteString[256]; + XModel* gameModels[512]; + FxEffectDef* smokeGrenadeFx; + shellshock_parms_t holdBreathParams; + char teamChatMsgs[8][160]; + int teamChatMsgTimes[8]; + int teamChatPos; + int teamLastChatPos; + float compassWidth; + float compassHeight; + float compassY; + clientInfo_t corpseinfo[8]; + bool entUpdateToggleContextKey; + XAnim_s* helicopterAnims; + }; + + static_assert(sizeof(cgs_t) == 0x3BA4); + + struct ConversionArguments + { + int argCount; + const char* args[9]; + }; + + enum TraceHitType + { + TRACE_HITTYPE_NONE = 0, + TRACE_HITTYPE_ENTITY = 1, + TRACE_HITTYPE_DYNENT_MODEL = 2, + TRACE_HITTYPE_DYNENT_BRUSH = 3, + TRACE_HITTYPE_GLASS = 4 + }; + + struct trace_t + { + float fraction; + float normal[3]; + int surfaceFlags; + int contents; + const char* material; + TraceHitType hitType; + unsigned __int16 hitId; + unsigned __int16 modelIndex; + unsigned __int16 partName; + unsigned __int16 partGroup; + bool allsolid; + bool startsolid; + bool walkable; + }; + + static_assert(sizeof(trace_t) == 0x2C); + + struct pmove_s + { + playerState_s* ps; + usercmd_s cmd; + usercmd_s oldcmd; + int tracemask; // 84 + int numtouch; + int touchents[32]; + Bounds bounds; // 220 + float xyspeed; + int proneChange; + float maxSprintTimeMultiplier; + bool mantleStarted; + float mantleEndPos[3]; + int mantleDuration; + int viewChangeTime; + float viewChange; + float fTorsoPitch; + float fWaistPitch; + unsigned char handler; + }; + + static_assert(sizeof(pmove_s) == 296); + + struct pml_t + { + float forward[3]; + float right[3]; + float up[3]; + float frametime; + int msec; + int walking; + int groundPlane; + int almostGroundPlane; + trace_t groundTrace; + float impactSpeed; + float previous_origin[3]; + float previous_velocity[3]; + int holdrand; + }; + + static_assert(sizeof(pml_t) == 0x84); + + 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 + }; + + struct TempPriority + { + void* threadHandle; + int oldPriority; + }; + + struct FastCriticalSection + { + volatile long readCount; + volatile long writeCount; + TempPriority tempPriority; + }; + #pragma endregion #ifndef IDA diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 872e40c3..bdec22d1 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -5,11 +5,12 @@ #ifndef RC_INVOKED -#define _HAS_CXX17 1 -#define _HAS_CXX20 1 +//#define _HAS_CXX17 1 +//#define _HAS_CXX20 1 #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN #define _CRT_SECURE_NO_WARNINGS +#define _USE_MATH_DEFINES // Requires Visual Leak Detector plugin: http://vld.codeplex.com/ #define VLD_FORCE_ENABLE @@ -38,24 +39,22 @@ #include #include #include - -// Experimental C++17 features +#include +#include +#include #include #include +#include #pragma warning(pop) #include #pragma comment(lib, "D3dx9.lib") -// Usefull for debugging -template class Sizer { }; -#define BindNum(x, y) Sizer y; -#define Size_Of(x, y) BindNum(sizeof(x), y) -#define Offset_Of(x, y, z) BindNum(offsetof(x, y), z) +#include +#pragma comment (lib, "xinput.lib") -// Submodules -// Ignore the warnings, it's no our code! +// Ignore the warnings #pragma warning(push) #pragma warning(disable: 4005) #pragma warning(disable: 4091) @@ -73,6 +72,7 @@ template class Sizer { }; #pragma warning(disable: 6258) #pragma warning(disable: 6386) #pragma warning(disable: 6387) +#pragma warning(disable: 26812) #include @@ -148,6 +148,7 @@ template class Sizer { }; #pragma comment(lib, "Advapi32.lib") #pragma comment(lib, "rpcrt4.lib") #pragma comment(lib, "dbghelp.lib") +#pragma comment(lib, "ntdll.lib") // Enable additional literals using namespace std::literals; diff --git a/src/Steam/Interfaces/SteamFriends.cpp b/src/Steam/Interfaces/SteamFriends.cpp index 6f1d1c06..6b5fbe27 100644 --- a/src/Steam/Interfaces/SteamFriends.cpp +++ b/src/Steam/Interfaces/SteamFriends.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include using namespace Components; diff --git a/src/Steam/Interfaces/SteamGameServer.cpp b/src/Steam/Interfaces/SteamGameServer.cpp index 26261093..e393d42c 100644 --- a/src/Steam/Interfaces/SteamGameServer.cpp +++ b/src/Steam/Interfaces/SteamGameServer.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamMasterServerUpdater.cpp b/src/Steam/Interfaces/SteamMasterServerUpdater.cpp index 431fd46a..d256ed2a 100644 --- a/src/Steam/Interfaces/SteamMasterServerUpdater.cpp +++ b/src/Steam/Interfaces/SteamMasterServerUpdater.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamMatchmaking.cpp b/src/Steam/Interfaces/SteamMatchmaking.cpp index af228ce2..72e56f94 100644 --- a/src/Steam/Interfaces/SteamMatchmaking.cpp +++ b/src/Steam/Interfaces/SteamMatchmaking.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamNetworking.cpp b/src/Steam/Interfaces/SteamNetworking.cpp index fb98f2d1..b782b5c6 100644 --- a/src/Steam/Interfaces/SteamNetworking.cpp +++ b/src/Steam/Interfaces/SteamNetworking.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamRemoteStorage.cpp b/src/Steam/Interfaces/SteamRemoteStorage.cpp index 0e7192c1..2cbe6620 100644 --- a/src/Steam/Interfaces/SteamRemoteStorage.cpp +++ b/src/Steam/Interfaces/SteamRemoteStorage.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamUser.cpp b/src/Steam/Interfaces/SteamUser.cpp index 81940388..3e95be40 100644 --- a/src/Steam/Interfaces/SteamUser.cpp +++ b/src/Steam/Interfaces/SteamUser.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Interfaces/SteamUtils.cpp b/src/Steam/Interfaces/SteamUtils.cpp index 56bfee0d..93d816d2 100644 --- a/src/Steam/Interfaces/SteamUtils.cpp +++ b/src/Steam/Interfaces/SteamUtils.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include STEAM_IGNORE_WARNINGS_START diff --git a/src/Steam/Proxy.cpp b/src/Steam/Proxy.cpp index 1458972f..f3a7e72f 100644 --- a/src/Steam/Proxy.cpp +++ b/src/Steam/Proxy.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Steam { @@ -156,13 +156,13 @@ namespace Steam gameID.type = 1; // k_EGameIDTypeGameMod gameID.appID = Proxy::AppId & 0xFFFFFF; - char* modId = const_cast("IW4x"); - gameID.modID = *reinterpret_cast(modId) | 0x80000000; + const char* modId = "IW4x"; + gameID.modID = *reinterpret_cast(modId) | 0x80000000; Interface clientUtils(Proxy::ClientEngine->GetIClientUtils(Proxy::SteamPipe)); clientUtils.invoke("SetAppIDForCurrentPipe", Proxy::AppId, false); - char ourPath[MAX_PATH] = { 0 }; + char ourPath[MAX_PATH] = {0}; GetModuleFileNameA(GetModuleHandle(nullptr), ourPath, sizeof(ourPath)); char ourDirectory[MAX_PATH] = { 0 }; @@ -384,11 +384,11 @@ namespace Steam Proxy::LaunchWatchGuard(); Proxy::Overlay = ::Utils::Library(GAMEOVERLAY_LIB, false); - if (!Proxy::Overlay.valid()) return false; + if (!Proxy::Overlay.isValid()) return false; } Proxy::Client = ::Utils::Library(STEAMCLIENT_LIB, false); - if (!Proxy::Client.valid()) return false; + if (!Proxy::Client.isValid()) return false; Proxy::SteamClient = Proxy::Client.get("CreateInterface")("SteamClient008", nullptr); if(!Proxy::SteamClient) return false; @@ -526,7 +526,7 @@ namespace Steam void Proxy::SetOverlayNotificationPosition(uint32_t eNotificationPosition) { - if (Proxy::Overlay.valid()) + if (Proxy::Overlay.isValid()) { Proxy::Overlay.get("SetNotificationPosition")(eNotificationPosition); } @@ -534,7 +534,7 @@ namespace Steam bool Proxy::IsOverlayEnabled() { - if (Proxy::Overlay.valid()) + if (Proxy::Overlay.isValid()) { return Proxy::Overlay.get("IsOverlayEnabled")(); } @@ -544,7 +544,7 @@ namespace Steam bool Proxy::BOverlayNeedsPresent() { - if (Proxy::Overlay.valid()) + if (Proxy::Overlay.isValid()) { return Proxy::Overlay.get("BOverlayNeedsPresent")(); } diff --git a/src/Steam/Proxy.hpp b/src/Steam/Proxy.hpp index 9a551a74..3e0c84a3 100644 --- a/src/Steam/Proxy.hpp +++ b/src/Steam/Proxy.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #ifdef _WIN64 #define GAMEOVERLAY_LIB "gameoverlayrenderer64.dll" #define STEAMCLIENT_LIB "steamclient64.dll" @@ -10,7 +12,6 @@ #define STEAM_REGISTRY_PATH "Software\\Valve\\Steam" #define STEAM_REGISTRY_PROCESS_PATH "Software\\Valve\\Steam\\ActiveProcess" #endif -#include "STDInclude.hpp" namespace Steam { diff --git a/src/Steam/Steam.cpp b/src/Steam/Steam.cpp index 059c4d9e..23501def 100644 --- a/src/Steam/Steam.cpp +++ b/src/Steam/Steam.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Steam { diff --git a/src/Utils/CSV.cpp b/src/Utils/CSV.cpp index 0b0a02d1..94370ba9 100644 --- a/src/Utils/CSV.cpp +++ b/src/Utils/CSV.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { @@ -71,7 +71,7 @@ namespace Utils if (!buffer.empty()) { - auto rows = Utils::String::Explode(buffer, '\n'); + auto rows = Utils::String::Split(buffer, '\n'); for (auto& row : rows) { diff --git a/src/Utils/Cache.cpp b/src/Utils/Cache.cpp index 774d938a..cd761d76 100644 --- a/src/Utils/Cache.cpp +++ b/src/Utils/Cache.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/Compression.cpp b/src/Utils/Compression.cpp index 34e515eb..581be7e8 100644 --- a/src/Utils/Compression.cpp +++ b/src/Utils/Compression.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/Cryptography.cpp b/src/Utils/Cryptography.cpp index 31e0558d..958fe274 100644 --- a/src/Utils/Cryptography.cpp +++ b/src/Utils/Cryptography.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include /// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf diff --git a/src/Utils/Entities.cpp b/src/Utils/Entities.cpp index c63e891f..04e24761 100644 --- a/src/Utils/Entities.cpp +++ b/src/Utils/Entities.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/Hooking.cpp b/src/Utils/Hooking.cpp index bf529da7..c814daa1 100644 --- a/src/Utils/Hooking.cpp +++ b/src/Utils/Hooking.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/IO.cpp b/src/Utils/IO.cpp index 7310b529..78f8d042 100644 --- a/src/Utils/IO.cpp +++ b/src/Utils/IO.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { @@ -63,6 +63,11 @@ namespace Utils return false; } + bool RemoveFile(const std::string& file) + { + return DeleteFileA(file.data()) == TRUE; + } + size_t FileSize(const std::string& file) { if (FileExists(file)) diff --git a/src/Utils/IO.hpp b/src/Utils/IO.hpp index a4e92afd..16c0198c 100644 --- a/src/Utils/IO.hpp +++ b/src/Utils/IO.hpp @@ -8,6 +8,7 @@ namespace Utils bool WriteFile(const std::string& file, const std::string& data, bool append = false); bool ReadFile(const std::string& file, std::string* data); std::string ReadFile(const std::string& file); + bool RemoveFile(const std::string& file); size_t FileSize(const std::string& file); bool CreateDir(const std::string& dir); bool DirectoryExists(const std::string& file); diff --git a/src/Utils/InfoString.cpp b/src/Utils/InfoString.cpp index 09126843..8faa26fa 100644 --- a/src/Utils/InfoString.cpp +++ b/src/Utils/InfoString.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { @@ -9,9 +9,10 @@ namespace Utils std::string InfoString::get(const std::string& key) { - if (this->keyValuePairs.find(key) != this->keyValuePairs.end()) + const auto value = this->keyValuePairs.find(key); + if (value != this->keyValuePairs.end()) { - return this->keyValuePairs[key]; + return value->second; } return ""; @@ -24,11 +25,13 @@ namespace Utils buffer = buffer.substr(1); } - std::vector KeyValues = Utils::String::Explode(buffer, '\\'); + auto KeyValues = Utils::String::Split(buffer, '\\'); - for (unsigned int i = 0; i < (KeyValues.size() - 1); i += 2) + for (size_t i = 0; !KeyValues.empty() && i < (KeyValues.size() - 1); i += 2) { - this->keyValuePairs[KeyValues[i]] = KeyValues[i + 1]; + const auto& key = KeyValues[i]; + const auto& value = KeyValues[i + 1]; + this->keyValuePairs[key] = value; } } @@ -36,16 +39,16 @@ namespace Utils { std::string infoString; - bool first = true; + auto first = true; - for (auto i = this->keyValuePairs.begin(); i != this->keyValuePairs.end(); ++i) + for (const auto& [key, value] : this->keyValuePairs) { if (first) first = false; else infoString.append("\\"); - infoString.append(i->first); // Key + infoString.append(key); infoString.append("\\"); - infoString.append(i->second); // Value + infoString.append(value); } return infoString; @@ -53,9 +56,9 @@ namespace Utils void InfoString::dump() { - for (auto i = this->keyValuePairs.begin(); i != this->keyValuePairs.end(); ++i) + for (const auto& [key, value] : this->keyValuePairs) { - OutputDebugStringA(Utils::String::VA("%s: %s", i->first.data(), i->second.data())); + OutputDebugStringA(Utils::String::VA("%s: %s\n", key.data(), value.data())); } } diff --git a/src/Utils/InfoString.hpp b/src/Utils/InfoString.hpp index d83edde8..f63c6714 100644 --- a/src/Utils/InfoString.hpp +++ b/src/Utils/InfoString.hpp @@ -7,11 +7,9 @@ namespace Utils public: InfoString() {}; InfoString(const std::string& buffer) : InfoString() { this->parse(buffer); }; - InfoString(const InfoString &obj) : keyValuePairs(obj.keyValuePairs) {}; void set(const std::string& key, const std::string& value); std::string get(const std::string& key); - std::string build(); void dump(); diff --git a/src/Utils/Library.cpp b/src/Utils/Library.cpp index 7c12fe71..1b10bf40 100644 --- a/src/Utils/Library.cpp +++ b/src/Utils/Library.cpp @@ -1,10 +1,33 @@ -#include "STDInclude.hpp" +#include namespace Utils { - Library::Library(const std::string& buffer, bool _freeOnDestroy) : _module(nullptr), freeOnDestroy(_freeOnDestroy) + Library Library::Load(const std::string& name) { - this->_module = LoadLibraryExA(buffer.data(), nullptr, 0); + return Library(LoadLibraryA(name.data())); + } + + Library Library::Load(const std::filesystem::path& path) + { + return Library::Load(path.generic_string()); + } + + Library Library::GetByAddress(void* address) + { + HMODULE handle = nullptr; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast(address), &handle); + return Library(handle); + } + + Library::Library(const std::string& name, bool _freeOnDestroy) : _module(nullptr), freeOnDestroy(_freeOnDestroy) + { + this->_module = LoadLibraryExA(name.data(), nullptr, 0); + } + + Library::Library(const HMODULE handle) + { + this->_module = handle; + this->freeOnDestroy = true; } Library::~Library() @@ -15,21 +38,21 @@ namespace Utils } } - bool Library::valid() + bool Library::isValid() const { - return (this->getModule() != nullptr); + return this->_module != nullptr; } - HMODULE Library::getModule() + HMODULE Library::getModule() const { return this->_module; } void Library::free() { - if (this->valid()) + if (this->isValid()) { - FreeLibrary(this->getModule()); + FreeLibrary(this->_module); } this->_module = nullptr; diff --git a/src/Utils/Library.hpp b/src/Utils/Library.hpp index c5b3815b..3ef0db41 100644 --- a/src/Utils/Library.hpp +++ b/src/Utils/Library.hpp @@ -5,22 +5,55 @@ namespace Utils class Library { public: + static Library Load(const std::string& name); + static Library Load(const std::filesystem::path& path); + static Library GetByAddress(void* address); + Library() : _module(nullptr), freeOnDestroy(false) {}; - Library(const std::string& buffer, bool freeOnDestroy = true); + Library(const std::string& name, bool freeOnDestroy); + explicit Library(const std::string& name) : _module(GetModuleHandleA(name.data())), freeOnDestroy(true) {}; + explicit Library(HMODULE handle); ~Library(); - bool valid(); - HMODULE getModule(); + bool isValid() const; + HMODULE getModule() const; template - std::function get(const std::string& process) + T getProc(const std::string& process) const { - if (!this->valid()) - { - throw std::runtime_error("Library not loaded!"); - } + if (!this->isValid()) T{}; + return reinterpret_cast(GetProcAddress(this->_module, process.data())); + } - return reinterpret_cast(GetProcAddress(this->getModule(), process.data())); + template + std::function get(const std::string& process) const + { + if (!this->isValid()) return std::function(); + return static_cast(this->getProc(process)); + } + + template + T invoke(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invokePascal(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invokeThis(const std::string& process, void* this_ptr, Args ... args) const + { + auto method = this->get(this_ptr, process); + if (method) return method(args...); + return T(); } void free(); diff --git a/src/Utils/Memory.cpp b/src/Utils/Memory.cpp index a69c1173..bba45884 100644 --- a/src/Utils/Memory.cpp +++ b/src/Utils/Memory.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/Memory.hpp b/src/Utils/Memory.hpp index 015e7f8b..88bb7007 100644 --- a/src/Utils/Memory.hpp +++ b/src/Utils/Memory.hpp @@ -81,10 +81,12 @@ namespace Utils this->pool.push_back(data); return data; } + template inline T* allocate() { return this->allocateArray(1); } + template inline T* allocateArray(size_t count = 1) { return static_cast(this->allocate(count * sizeof(T))); @@ -142,6 +144,13 @@ namespace Utils return static_cast(Allocate(count * sizeof(T))); } + template static inline T* Duplicate(T* original) + { + T* data = Memory::Allocate(); + std::memcpy(data, original, sizeof(T)); + return data; + } + static char* DuplicateString(const std::string& string); static void Free(void* data); diff --git a/src/Utils/Stream.cpp b/src/Utils/Stream.cpp index 92049f59..e3ef1b14 100644 --- a/src/Utils/Stream.cpp +++ b/src/Utils/Stream.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index ca2d0664..422a9158 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #ifdef ENABLE_BASE128 #include "base128.h" #endif @@ -62,25 +62,18 @@ namespace Utils return str; } - std::vector Explode(const std::string& str, char delim) + std::vector Split(const std::string& str, const char delim) { - std::vector result; - std::istringstream iss(str); + std::stringstream ss(str); + std::string item; + std::vector elems; - for (std::string token; std::getline(iss, token, delim);) + while (std::getline(ss, item, delim)) { - std::string _entry = std::move(token); - - // Remove trailing 0x0 bytes - while (_entry.size() && !_entry.back()) - { - _entry = _entry.substr(0, _entry.size() - 1); - } - - result.push_back(_entry); + elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from S1x) } - return result; + return elems; } void Replace(std::string &string, const std::string& find, const std::string& replace) @@ -110,30 +103,58 @@ namespace Utils return _isspace_l(c, nullptr); } - // trim from start - std::string <rim(std::string &s) + // Trim from start + std::string& LTrim(std::string& str) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int val) + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](int val) { return !IsSpace(val); })); - return s; + + return str; } - // trim from end - std::string &RTrim(std::string &s) + // Trim from end + std::string& RTrim(std::string& str) { - s.erase(std::find_if(s.rbegin(), s.rend(), [](int val) + str.erase(std::find_if(str.rbegin(), str.rend(), [](int val) { return !IsSpace(val); - }).base(), s.end()); - return s; + }).base(), str.end()); + + return str; } - // trim from both ends - std::string &Trim(std::string &s) + // Trim from both ends + std::string& Trim(std::string& str) { - return LTrim(RTrim(s)); + return LTrim(RTrim(str)); + } + + std::string Convert(const std::wstring& wstr) + { + std::string result; + result.reserve(wstr.size()); + + for (const auto& chr : wstr) + { + result.push_back(static_cast(chr)); + } + + return result; + } + + std::wstring Convert(const std::string& str) + { + std::wstring result; + result.reserve(str.size()); + + for (const auto& chr : str) + { + result.push_back(static_cast(chr)); + } + + return result; } std::string FormatTimeSpan(int milliseconds) diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 2b0ed6d1..0da5f00b 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -78,12 +78,15 @@ namespace Utils std::string ToLower(std::string input); std::string ToUpper(std::string input); bool EndsWith(const std::string& haystack, const std::string& needle); - std::vector Explode(const std::string& str, char delim); - void Replace(std::string &string, const std::string& find, const std::string& replace); + std::vector Split(const std::string& str, const char delim); + void Replace(std::string& string, const std::string& find, const std::string& replace); bool StartsWith(const std::string& haystack, const std::string& needle); - std::string <rim(std::string &s); - std::string &RTrim(std::string &s); - std::string &Trim(std::string &s); + std::string& LTrim(std::string& str); + std::string& RTrim(std::string& str); + std::string& Trim(std::string& str); + + std::string Convert(const std::wstring& wstr); + std::wstring Convert(const std::string& str); std::string FormatTimeSpan(int milliseconds); std::string FormatBandwidth(size_t bytes, int milliseconds); diff --git a/src/Utils/Time.cpp b/src/Utils/Time.cpp index 4761f43e..8e257e7d 100644 --- a/src/Utils/Time.cpp +++ b/src/Utils/Time.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index 3a16ee58..e17480bf 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Utils { @@ -10,7 +10,7 @@ namespace Utils if (mimeType) { std::wstring wMimeType(mimeType); - return std::string(wMimeType.begin(), wMimeType.end()); + return String::Convert(wMimeType); } return "application/octet-stream"; @@ -107,12 +107,12 @@ namespace Utils void SetEnvironment() { wchar_t exeName[512]; - GetModuleFileName(GetModuleHandle(nullptr), exeName, sizeof(exeName) / 2); + GetModuleFileNameW(GetModuleHandle(nullptr), exeName, sizeof(exeName) / sizeof(wchar_t)); - wchar_t* exeBaseName = wcsrchr(exeName, L'\\'); + auto* exeBaseName = wcsrchr(exeName, L'\\'); exeBaseName[0] = L'\0'; - SetCurrentDirectory(exeName); + SetCurrentDirectoryW(exeName); } HMODULE GetNTDLL() @@ -149,4 +149,15 @@ namespace Utils { return !(base1 + len1 <= base2 || base2 + len2 <= base1); } + + float Vec3SqrDistance(const float v1[3], const float v2[3]) + { + auto x = v2[0] - v1[0]; + auto y = v2[1] - v1[1]; + auto z = v2[2] - v1[2]; + + return x * x + y * y + z * z; + } + + } diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index f296f0e1..c33bbda3 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -24,6 +24,7 @@ namespace Utils void OpenUrl(const std::string& url); bool HasIntercection(unsigned int base1, unsigned int len1, unsigned int base2, unsigned int len2); + float Vec3SqrDistance(const float v1[3], const float v2[3]); template inline void RotLeft(T& object, size_t bits) { diff --git a/src/Utils/WebIO.cpp b/src/Utils/WebIO.cpp index 7c25ebf0..b9189215 100644 --- a/src/Utils/WebIO.cpp +++ b/src/Utils/WebIO.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include #include namespace Utils @@ -396,7 +396,7 @@ namespace Utils return false; } - void WebIO::formatPath(std::string &path, bool win) + void WebIO::formatPath(std::string& path, bool win) { size_t nPos; std::string find = "\\"; @@ -408,7 +408,7 @@ namespace Utils replace = "\\"; } - while ((nPos = path.find(find)) != std::wstring::npos) + while ((nPos = path.find(find)) != std::string::npos) { path = path.replace(nPos, find.length(), replace); } @@ -445,7 +445,7 @@ namespace Utils return (FtpRenameFileA(this->hConnect, directory.data(), newDir.data()) == TRUE); // According to the internetz, this should work } - bool WebIO::listElements(const std::string& directory, std::vector &list, bool files) + bool WebIO::listElements(const std::string& directory, std::vector& list, bool files) { list.clear(); @@ -483,12 +483,12 @@ namespace Utils return result; } - bool WebIO::listDirectories(const std::string& directory, std::vector &list) + bool WebIO::listDirectories(const std::string& directory, std::vector& list) { return this->listElements(directory, list, false); } - bool WebIO::listFiles(const std::string& directory, std::vector &list) + bool WebIO::listFiles(const std::string& directory, std::vector& list) { return this->listElements(directory, list, true); } @@ -532,7 +532,7 @@ namespace Utils return result; } - bool WebIO::downloadFileData(const std::string& file, std::string &data) + bool WebIO::downloadFileData(const std::string& file, std::string& data) { data.clear(); diff --git a/src/Utils/WebIO.hpp b/src/Utils/WebIO.hpp index b272fdbe..07af0c0f 100644 --- a/src/Utils/WebIO.hpp +++ b/src/Utils/WebIO.hpp @@ -51,8 +51,8 @@ namespace Utils bool deleteDirectory(const std::string& directory); bool renameDirectory(const std::string& directory, const std::string& newDir); - bool listDirectories(const std::string& directory, std::vector &list); - bool listFiles(const std::string& directory, std::vector &list); + bool listDirectories(const std::string& directory, std::vector& list); + bool listFiles(const std::string& directory, std::vector& list); bool deleteFile(const std::string& file); bool renameFile(const std::string& file, const std::string& newFile); @@ -60,7 +60,7 @@ namespace Utils bool downloadFile(const std::string& file, const std::string& localfile); bool uploadFileData(const std::string& file,const std::string& data); - bool downloadFileData(const std::string& file, std::string &data); + bool downloadFileData(const std::string& file, std::string& data); void setProgressCallback(Utils::Slot callback); void cancelDownload() { this->cancel = true; } @@ -104,7 +104,7 @@ namespace Utils std::string execute(const char* command, const std::string& body, WebIO::Params headers = WebIO::Params(), bool* success = nullptr); - bool listElements(const std::string& directory, std::vector &list, bool files); + bool listElements(const std::string& directory, std::vector& list, bool files); void openSession(const std::string& useragent); void closeSession(); @@ -112,6 +112,6 @@ namespace Utils bool openConnection(); void closeConnection(); - void formatPath(std::string &path, bool win); /* if (win == true): / -> \\ */ + void formatPath(std::string& path, bool win); /* if (win == true): / -> \\ */ }; } diff --git a/tools/premake5.exe b/tools/premake5.exe index 9048d51e..c73da1fb 100644 Binary files a/tools/premake5.exe and b/tools/premake5.exe differ diff --git a/tools/protoc.exe b/tools/protoc.exe index 07452286..87bf60b6 100644 Binary files a/tools/protoc.exe and b/tools/protoc.exe differ