Merge tag 'v0.7.0-RC10'

This commit is contained in:
FutureRave 2022-05-01 18:23:30 +01:00
commit 91e579bdd9
237 changed files with 17292 additions and 3497 deletions

7
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: gitsubmodule
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

36
.github/labeler.yml vendored
View File

@ -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":
- "**"

117
.github/workflows/build.yml vendored Normal file
View File

@ -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 }}

17
.github/workflows/discord-notify.yml vendored Normal file
View File

@ -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 }}

48
.github/workflows/draft-new-release.yml vendored Normal file
View File

@ -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

View File

@ -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 }}"

83
.github/workflows/release.yml vendored Normal file
View File

@ -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"

2
.gitmodules vendored
View File

@ -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

317
Jenkinsfile vendored
View File

@ -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()
}
}
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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%

2
deps/json11 vendored

@ -1 +1 @@
Subproject commit e2e3a11e99672b018e0e0657867e6a3439e180cf
Subproject commit 2df9473fb3605980db55ecddf34392a2e832ad35

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 1937f412605e1b04ddb41ef9c2f2f0aab7e61548
Subproject commit 06a81aeb227424182125363f7554fad5146d6d2a

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 6ac0b0c1b69b9a88e1b3b3002c2e3a9062ae99b4
Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a

2
deps/pdcurses vendored

@ -1 +1 @@
Subproject commit 618e0aaa31b4728eb4df78ec4de6c2b873908eda
Subproject commit 2fa0f10dd844da47ee83c05a40a1ec541ceb95e1

19
deps/premake/dxsdk.lua vendored Normal file
View File

@ -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)

32
deps/premake/json11.lua vendored Normal file
View File

@ -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)

59
deps/premake/libtomcrypt.lua vendored Normal file
View File

@ -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)

50
deps/premake/libtommath.lua vendored Normal file
View File

@ -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)

43
deps/premake/minizip.lua vendored Normal file
View File

@ -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)

32
deps/premake/mongoose.lua vendored Normal file
View File

@ -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)

34
deps/premake/pdcurses.lua vendored Normal file
View File

@ -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)

50
deps/premake/protobuf.lua vendored Normal file
View File

@ -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)

35
deps/premake/udis86.lua vendored Normal file
View File

@ -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)

39
deps/premake/zlib.lua vendored Normal file
View File

@ -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)

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 63cfdafacba6141717a2df97fc123dc0c14ba7c4
Subproject commit 5500c72c5b616da9f0125bcfab513987a1226e2b

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit d71dc66fa8a153fb6e7c626847095d9697a6cf42
Subproject commit ec3df00224d4b396e2ac6586ab5d25f673caa4c2

View File

@ -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

View File

@ -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

5011
lib/include/stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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());

View File

@ -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"
#include "Modules/Gamepad.hpp"
#include "Modules/Client.hpp"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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();

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Components
{

View File

@ -1,9 +1,9 @@
#include "STDInclude.hpp"
#include <StdInclude.hpp>
namespace Components
{
thread_local int AssetHandler::BypassState = 0;
bool AssetHandler::ShouldSearchTempAssets = false;
bool AssetHandler::ShouldSearchTempAssets = false;
std::map<Game::XAssetType, AssetHandler::IAsset*> AssetHandler::AssetInterfaces;
std::map<Game::XAssetType, Utils::Slot<AssetHandler::Callback>> AssetHandler::TypeCallbacks;
Utils::Signal<AssetHandler::RestrictCallback> 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<bool>("r_noVoid", false, Game::DVAR_FLAG_SAVED, "Disable void model (red fx)");
Dvar::Register<bool>("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<json11::Json> 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());

View File

@ -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<std::string, Game::XAssetHeader> 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();

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_COMMAP_VERSION 0

View File

@ -1,7 +1,82 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_truetype.h>
namespace Assets
{
namespace
{
int PackFonts(const uint8_t* data, std::vector<uint16_t>& 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<float>(pw);
glyph.s1 = (x + gw) / static_cast<float>(pw);
glyph.t0 = y / static_cast<float>(ph);
glyph.t1 = (y + gh) / static_cast<float>(ph);
glyph.pixelWidth = static_cast<char>(gw);
glyph.pixelHeight = static_cast<char>(gh);
glyph.x0 = static_cast<char>(x0);
glyph.y0 = static_cast<char>(y0 + yOffset);
glyph.dx = static_cast<char>(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<uint8_t>(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<Game::GfxImage>();
std::memcpy(image, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_IMAGE, "gamefonts_pc").image, sizeof(Game::GfxImage));
image->name = texName;
auto* material = builder->getAllocator()->allocate<Game::Material>();
std::memcpy(material, Game::DB_FindXAssetHeader(Game::ASSET_TYPE_MATERIAL, "fonts/gamefonts_pc").material, sizeof(Game::Material));
material->textureTable = builder->getAllocator()->allocate<Game::MaterialTextureDef>();
material->textureTable->u.image = image;
material->info.name = fontName;
auto* glowMaterial = builder->getAllocator()->allocate<Game::Material>();
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<uint16_t> charset;
if (fontDef["charset"].is_array())
{
for (auto& ch : fontDef["charset"].array_items())
charset.push_back(static_cast<uint16_t>(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<Game::Font_s>();
font->fontName = fontName;
font->pixelHeight = size;
font->material = material;
font->glowMaterial = glowMaterial;
font->glyphCount = charset.size();
font->glyphs = builder->getAllocator()->allocateArray<Game::Glyph>(charset.size());
// Generate glyph data
int result = PackFonts(reinterpret_cast<const uint8_t*>(fontFile.getBuffer().data()), charset, font->glyphs, static_cast<float>(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<int>(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<short>(w), static_cast<short>(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<char>(255);
rgbaPixels[i + 1] = static_cast<char>(255);
rgbaPixels[i + 2] = static_cast<char>(255);
rgbaPixels[i + 3] = static_cast<char>(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);

View File

@ -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;
};
}

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#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;
}

View File

@ -1,4 +1,4 @@
#include "StdInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_IMG_VERSION "0"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_LIGHT_VERSION "0"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_GFXMAP_VERSION 1

View File

@ -1,10 +1,10 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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<short>();
sound->sound.info.samples = reader.read<int>();
sound->sound.info.rate = reader.read<int>();
sound->sound.info.samples = reader.read<int>();
sound->sound.info.block_size = reader.read<short>();
sound->sound.info.bits = reader.read<short>();

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_MAT_VERSION "1"
@ -29,6 +29,33 @@ namespace Assets
"_add_lin_nofog",
};
std::map<std::string, std::string> 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())

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_TECHSET_VERSION "0"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_TECHSET_VERSION "0"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_TECHSET_VERSION "0"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <StdInclude.hpp>
#define IW4X_TECHSET_VERSION "0"

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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<int>();
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);
}

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define IW4X_ANIM_VERSION 1

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#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<bool>() || !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())
{

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Assets
{

View File

@ -1,6 +1,6 @@
#include "StdInclude.hpp"
#include <STDInclude.hpp>
#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<void**>(clipMap))
{
*reinterpret_cast<Game::clipMap_t**>(clipMap) = header.clipMap;
}
}, &orgClipMap, false);
if (!*reinterpret_cast<void**>(clipMap))
{
*reinterpret_cast<Game::clipMap_t**>(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<int>();
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<unsigned short>();
clipMap->smodelNodes = reader.readArray<Game::SModelAabbNode>(clipMap->smodelNodeCount);
clipMap->checksum = reader.read<int>();
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<Game::TriggerSlab> slabs;
std::list<Game::TriggerHull> hulls;
std::list<Game::TriggerModel> 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<Game::TriggerHull>(clipMap->mapEnts->trigger.hullCount);
Game::TriggerModel* models = builder->getAllocator()->allocateArray<Game::TriggerModel>(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<Game::TriggerModel>();
hulls[i] = reader.read<Game::TriggerHull>();
}
size_t slabCount = reader.read<size_t>();
clipMap->mapEnts->trigger.slabCount = slabCount;
Game::TriggerSlab* slabs = builder->getAllocator()->allocateArray<Game::TriggerSlab>(clipMap->mapEnts->trigger.slabCount);
for (unsigned int i = 0; i < clipMap->mapEnts->trigger.slabCount; i++) {
slabs[i] = reader.read<Game::TriggerSlab>();
}
clipMap->mapEnts->trigger.models = &models[0];
clipMap->mapEnts->trigger.hulls = &hulls[0];
clipMap->mapEnts->trigger.slabs = &slabs[0];
}
}
*/
clipMap->checksum = reader.read<int>();
// This mustn't be null and has to have at least 1 'valid' entry.
if (!clipMap->smodelNodeCount || !clipMap->smodelNodes)

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <StdInclude.hpp>
namespace Assets
{

View File

@ -1,13 +1,13 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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<Game::snd_alias_t>();
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<Game::snd_alias_t>(aliasList->count);
if (!aliasList->head)
{
Components::Logger::Print("Error allocating memory for sound alias structure!\n");
return;
}
aliasList->head->soundFile = builder->getAllocator()->allocate<Game::SoundFile>();
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<Game::SoundFile>();
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<Game::SpeakerMap>();
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<float>(speaker["levels0"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].levels[1] = static_cast<float>(speaker["levels1"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].numLevels = static_cast<int>(speaker["numLevels"].number_value());
alias->speakerMap->channelMaps[channelMapIndex][subChannelIndex].speakers[speakerIndex].speaker = static_cast<int>(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<Game::snd_alias_type_t>(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<Game::snd_alias_type_t>(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<Game::snd_alias_t>();
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];

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Components
{
@ -8,7 +8,8 @@ namespace Components
Utils::Cryptography::Token Auth::ComputeToken;
Utils::Cryptography::ECC::Key Auth::GuidKey;
std::vector<std::uint64_t> Auth::BannedUids = {
std::vector<std::uint64_t> 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<uint32_t>(Dvar::Var("sv_securityLevel").get<int>());
uint32_t userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey());
auto ourLevel = Dvar::Var("sv_securityLevel").get<unsigned int>();
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<int>("sv_securityLevel", 23, 0, 512, Game::dvar_flag::DVAR_FLAG_SERVERINFO, "Security level for GUID certificates (POW)");
Dvar::Register<int>("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);

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Components
{
@ -6,6 +6,8 @@ namespace Components
bool Bans::IsBanned(Bans::Entry entry)
{
std::lock_guard<std::recursive_mutex> _(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()
{
}
}

View File

@ -8,7 +8,6 @@ namespace Components
typedef std::pair<SteamID, Game::netIP_t> Entry;
Bans();
~Bans();
static void BanClientNum(int num, const std::string& reason);
static void UnbanClient(SteamID id);

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
#define KEY_MASK_FIRE 1
#define KEY_MASK_SPRINT 2
@ -96,7 +96,7 @@ namespace Components
if (bots.exists())
{
std::vector<std::string> names = Utils::String::Explode(bots.getBuffer(), '\n');
std::vector<std::string> 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(<int>)
{
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: <bot> botWeapon(<str>);
{
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: <bot> botAction(<str action>);
{
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: <bot> botMovement(<int>, <int>);
{
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<unsigned int>(-1);
else count = atoi(params->get(1));

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Components
{
@ -192,14 +192,14 @@ namespace Components
{
Dvar::OnInit([]()
{
CardTitles::CustomTitleDvar = Dvar::Register<const char*>("customtitle", "", Game::dvar_flag::DVAR_FLAG_USERINFO | Game::dvar_flag::DVAR_FLAG_SAVED, "Custom card title");
CardTitles::CustomTitleDvar = Dvar::Register<const char*>("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();
}
}
}

View File

@ -60,7 +60,6 @@ namespace Components
static void ParseCustomTitles(const char * msg);
CardTitles();
~CardTitles();
private:
static CClient * GetClientByIndex(std::uint32_t index);

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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<std::mutex> _(Changelog::Mutex);
Changelog::Lines.clear();
}
}
}

View File

@ -6,7 +6,6 @@ namespace Components
{
public:
Changelog();
~Changelog();
static void LoadChangelog();

View File

@ -0,0 +1,331 @@
#include <STDInclude.hpp>
namespace Components
{
Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast<Game::dvar_t**>(0x7ED398);
Dvar::Var Chat::cg_chatWidth;
Game::dvar_t** Chat::cg_chatTime = reinterpret_cast<Game::dvar_t**>(0x9F5DE8);
bool Chat::SendChat;
std::mutex Chat::AccessMutex;
std::unordered_set<std::uint64_t> 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<std::mutex> 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<float>(cg_chatWidth.get<int>());
const auto chatTime = (*cg_chatTime)->current.integer;
if (chatHeight < 0 || static_cast<unsigned>(chatHeight) > std::extent_v<decltype(Game::cgs_t::teamChatMsgs)> || 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<int>(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<std::mutex> 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<std::mutex> 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<bool>())
{
Logger::Print("Server is not running.\n");
return;
}
const auto* cmd = params->get(0);
if (params->size() < 2)
{
Logger::Print("Usage: %s <client number> : 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<bool>())
{
Logger::Print("Server is not running.\n");
return;
}
const auto* cmd = params->get(0);
if (params->size() < 2)
{
Logger::Print("Usage: %s <client number or guid>\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<int>("cg_chatWidth", 52, 1, std::numeric_limits<int>::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();
}
}

View File

@ -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<std::uint64_t> 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();
};
}

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Components
{
@ -75,7 +75,7 @@ namespace Components
// Create clantag dvar
Dvar::OnInit([]()
{
Dvar::Register<const char*>("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<const char*>("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();
}
}
}

View File

@ -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];

View File

@ -1,4 +1,4 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
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());

View File

@ -0,0 +1,312 @@
#include <STDInclude.hpp>
namespace Components
{
std::unordered_map<std::string, Utils::Slot<ClientCommand::Callback>> ClientCommand::FunctionMap;
bool ClientCommand::CheatsOk(const Game::gentity_s* ent)
{
const auto entNum = ent->s.number;
if (!Dvar::Var("sv_cheats").get<bool>())
{
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> 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<void(const int)>(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(<optional int toggle>);
{
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(<optional int toggle>);
{
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(<optional int toggle>);
{
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(<optional int toggle>);
{
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(<optional int toggle>);
{
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();
}
}

View File

@ -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> callback);
static bool CheatsOk(const Game::gentity_s* ent);
private:
static std::unordered_map<std::string, Utils::Slot<Callback>> FunctionMap;
static bool CallbackHandler(Game::gentity_s* ent, const char* cmd);
static void ClientCommandStub(const int clientNum);
static void AddCheatCommands();
static void AddScriptFunctions();
};
}

View File

@ -1,269 +0,0 @@
#include "STDInclude.hpp"
namespace Components
{
Dvar::Var Colors::NewColors;
std::vector<DWORD> 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<uint8_t>(h / 43);
remainder = (h - (region * 43)) * 6;
p = static_cast<uint8_t>((v * (255 - s)) >> 8);
q = static_cast<uint8_t>((v * (255 - ((s * remainder) >> 8))) >> 8);
t = static_cast<uint8_t>((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<bool>())
{
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<char>(0x535629, limit); // DrawText2d
Utils::Hook::Set<char>(0x4C1BE4, limit); // No idea :P
Utils::Hook::Set<char>(0x4863DD, limit); // No idea :P
Utils::Hook::Set<char>(0x486429, limit); // No idea :P
Utils::Hook::Set<char>(0x49A5A8, limit); // No idea :P
Utils::Hook::Set<char>(0x505721, limit); // R_TextWidth
Utils::Hook::Set<char>(0x505801, limit); // No idea :P
Utils::Hook::Set<char>(0x50597F, limit); // No idea :P
Utils::Hook::Set<char>(0x5815DB, limit); // No idea :P
Utils::Hook::Set<char>(0x592ED0, limit); // No idea :P
Utils::Hook::Set<char>(0x5A2E2E, limit); // No idea :P
Utils::Hook::Set<char>(0x5A2733, limit - '0'); // No idea :P
}
char Colors::Add(uint8_t r, uint8_t g, uint8_t b)
{
char index = '0' + static_cast<char>(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<unsigned int>(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<DWORD*>(0x66E5F70);
}
else if (index == '9') // Color 9
{
*color = *reinterpret_cast<DWORD*>(0x66E5F74);
}
else if (index == ':')
{
*color = Colors::HsvToRgb({ static_cast<uint8_t>((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<bool>())
{
*color = reinterpret_cast<DWORD*>(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<BYTE>(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<bool>("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<bool>("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();
}
}

View File

@ -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<DWORD> ColorTable;
};
}

View File

@ -1,60 +1,75 @@
#include "STDInclude.hpp"
#include <STDInclude.hpp>
namespace Components
{
std::unordered_map<std::string, Utils::Slot<Command::Callback>> Command::FunctionMap;
std::unordered_map<std::string, Utils::Slot<Command::Callback>> 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<char*>("");
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<char*>("");
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<Command::Callback> 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<Command::Callback> 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](&params);
got->second(&params);
}
}
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](&params);
got->second(&params);
}
}
@ -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<bool>())
{
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<bool>())
{
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<bool>())
{
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();
}
}

View File

@ -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();

Some files were not shown because too many files have changed in this diff Show More