216
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types: [closed, opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
modify:
|
||||
runs-on: ubuntu-latest
|
||||
name: Apply Git modifications if any are necessary
|
||||
steps:
|
||||
- name: Check out files
|
||||
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 changelog
|
||||
if: github.event.pull_request.merged
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@v2
|
||||
with:
|
||||
validation_depth: 10
|
||||
path: ./CHANGELOG.md
|
||||
|
||||
- name: Create annotated tag
|
||||
if: github.event.pull_request.merged
|
||||
run: |
|
||||
git tag -a -m "${{ steps.changelog_reader.outputs.changes }}" \
|
||||
"${{ steps.changelog_reader.outputs.version }}" \
|
||||
"${{ github.event.pull_request.merge_commit_sha }}"
|
||||
git push origin --tags
|
||||
|
||||
build:
|
||||
name: Build binaries
|
||||
runs-on: windows-latest
|
||||
needs:
|
||||
- modify
|
||||
strategy:
|
||||
matrix:
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
steps:
|
||||
- name: Check out files
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
# NOTE - if LFS ever starts getting used during builds, switch this to true!
|
||||
lfs: false
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
|
||||
- name: Generate project files
|
||||
run: tools/premake5 vs2019
|
||||
|
||||
- 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=x64 build/s1x.sln
|
||||
|
||||
- name: Upload ${{matrix.configuration}} binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{matrix.configuration}} binaries
|
||||
path: |
|
||||
build/bin/x64/${{matrix.configuration}}/*
|
||||
|
||||
- name: Upload ${{matrix.configuration}} debug symbols
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{matrix.configuration}} debug symbols
|
||||
path: |
|
||||
build/bin/**/*.pdb
|
||||
|
||||
release:
|
||||
name: Create new GitHub Release
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged
|
||||
steps:
|
||||
- name: Check out files
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: false
|
||||
lfs: false
|
||||
|
||||
- name: Download Release binaries
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: Release binaries
|
||||
|
||||
# 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: 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 changelog
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@v2
|
||||
with:
|
||||
validation_depth: 2
|
||||
path: ./CHANGELOG.md
|
||||
|
||||
- uses: papeloto/action-zip@v1
|
||||
with:
|
||||
recursive: false
|
||||
files: s1x.exe
|
||||
dest: s1x-${{ steps.changelog_reader.outputs.version }}.zip
|
||||
- name: Sign ZIP file
|
||||
run: gpg --output "s1x-${{ steps.changelog_reader.outputs.version }}.zip.sig" --detach-sig "s1x-${{ steps.changelog_reader.outputs.version }}.zip"
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.changelog_reader.outputs.version }}
|
||||
release_name: ${{ steps.changelog_reader.outputs.version }}
|
||||
body: ${{ steps.changelog_reader.outputs.changes }}
|
||||
draft: ${{ steps.changelog_reader.outputs.status == 'unreleased' }}
|
||||
prerelease: ${{ steps.changelog_reader.outputs.status == 'prereleased' }}
|
||||
|
||||
- name: Upload Release ZIP
|
||||
id: upload-release-zip
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/s1x-${{ steps.changelog_reader.outputs.version }}.zip
|
||||
asset_name: s1x-${{ steps.changelog_reader.outputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Release ZIP signature
|
||||
id: upload-release-zip-signature
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/s1x-${{ steps.changelog_reader.outputs.version }}.zip.sig
|
||||
asset_name: s1x-${{ steps.changelog_reader.outputs.version }}.zip.sig
|
||||
asset_content_type: text/plain
|
||||
|
||||
- name: Remove extra files
|
||||
run: git clean -ffdx && git reset --hard
|
||||
|
||||
- name: Create Pull Request to merge master back into develop
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
|
||||
with:
|
||||
delete-branch: false
|
||||
author: "${{ secrets.XLABS_CI_NAME }} <${{ secrets.XLABS_CI_EMAIL }}>"
|
||||
committer: "${{ secrets.XLABS_CI_NAME }} <${{ secrets.XLABS_CI_EMAIL }}>"
|
||||
branch: release/${{ steps.changelog_reader.outputs.version }}
|
||||
base: develop
|
||||
body: |
|
||||
This Pull Request contains all changes done for the release of ${{ steps.changelog_reader.outputs.version }}, ready to be merged back into `master`.
|
||||
|
||||
This release should be merged in due time to make sure that changes done to files such as the changelog as part of the release are also contained on the `develop` branch.
|
||||
title: Merge ${{ steps.changelog_reader.outputs.version }} into develop
|
||||
|
||||
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"
|
17
.github/workflows/discord-notify.yml
vendored
Normal 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 }}
|
82
.github/workflows/draft-new-release.yml
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
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: Rename Unreleased section in changelog to ${{ steps.normalize_version.outputs.version }}
|
||||
uses: thomaseizinger/keep-a-changelog-new-release@1.1.0
|
||||
with:
|
||||
version: ${{ steps.normalize_version.outputs.version }}
|
||||
|
||||
- name: Commit changelog
|
||||
id: make-commit
|
||||
run: |
|
||||
git checkout -b "release/${{ steps.normalize_version.outputs.version }}"
|
||||
git add CHANGELOG.md
|
||||
git commit -S -m "Prepare release ${{ steps.normalize_version.outputs.version }}"
|
||||
git push -u origin "release/${{ steps.normalize_version.outputs.version }}"
|
||||
|
||||
echo "::set-output name=commit::$(git rev-parse HEAD)"
|
||||
|
||||
- name: Extract changelog for Pull Request
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@v2
|
||||
with:
|
||||
validation_depth: 10
|
||||
version: ${{ steps.normalize_version.outputs.version }}
|
||||
path: ./CHANGELOG.md
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
github_token: ${{ secrets.XLABS_CI_GITHUB_TOKEN }}
|
||||
source_branch: "release/${{ steps.normalize_version.outputs.version }}"
|
||||
destination_branch: "master"
|
||||
pr_body: |
|
||||
This Pull Request is for the release of S1x ${{ 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 }}.
|
||||
|
||||
Commit [`${{ steps.make-commit.outputs.commit }}`](https://github.com/${{ github.repository }}/commit/${{ steps.make-commit.outputs.commit }}) includes an update to the changelog to list the new version with its changes.
|
||||
|
||||
# What happens when this PR gets merged?
|
||||
|
||||
After merging this PR, another workflow will create a new tag `${{ steps.normalize_version.outputs.version }}` on the `master` branch and the version will officially be ${{ steps.changelog_reader.outputs.status }} via an actual GitHub release. A final build will be triggered and all binaries and assets will be attached to the GitHub release.
|
||||
|
||||
# Changelog for ${{ steps.normalize_version.outputs.version }}
|
||||
|
||||
These changes will be included in the release:
|
||||
|
||||
${{ steps.changelog_reader.outputs.changes }}
|
||||
pr_title: Release ${{ steps.changelog_reader.outputs.version }}
|
||||
pr_label: release
|
6
.gitmodules
vendored
@ -31,5 +31,7 @@
|
||||
url = https://github.com/discord/discord-rpc.git
|
||||
[submodule "deps/asmjit"]
|
||||
path = deps/asmjit
|
||||
url = https://github.com/Joelrau/asmjit.git
|
||||
branch = old-state
|
||||
url = https://github.com/asmjit/asmjit.git
|
||||
[submodule "deps/WinToast"]
|
||||
path = deps/WinToast
|
||||
url = https://github.com/mohabouje/WinToast.git
|
||||
|
46
CHANGELOG.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.0.2] - 2021-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Offline mode [#19](https://github.com/XLabsProject/s1x-client/issues/19)
|
||||
- Rewrite demonware emulator [#20](https://github.com/XLabsProject/s1x-client/issues/20)
|
||||
- Add a way to disable auto restart on crashes. [#58](https://github.com/XLabsProject/s1x-client/issues/58)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Investigate performance issues [#11](https://github.com/XLabsProject/s1x-client/issues/11)
|
||||
- Map command ingame [#13](https://github.com/XLabsProject/s1x-client/issues/13)
|
||||
- Replicated dvars are reset upon map start/restart [#14](https://github.com/XLabsProject/s1x-client/issues/14)
|
||||
- Process sometimes doesn't exit [#17](https://github.com/XLabsProject/s1x-client/issues/17)
|
||||
- Steam Files causing Outdated Game Files error. [#39](https://github.com/XLabsProject/s1x-client/issues/39)
|
||||
- Killcam Lags/Skips [#53](https://github.com/XLabsProject/s1x-client/issues/53)
|
||||
|
||||
### Pull Requests
|
||||
|
||||
- More cleanup [#1](https://github.com/XLabsProject/s1x-client/pull/1) ([@OneFourOne](https://github.com/OneFourOne))
|
||||
- Quick README change [#2](https://github.com/XLabsProject/s1x-client/pull/2) ([@ChxseH](https://github.com/ChxseH))
|
||||
- fix typo [#3](https://github.com/XLabsProject/s1x-client/pull/3) ([@OneFourOne](https://github.com/OneFourOne))
|
||||
- Fix Joelrau/s1-mod#9 [#5](https://github.com/XLabsProject/s1x-client/pull/5) ([@fedddddd](https://github.com/fedddddd))
|
||||
- Unlock everything and re-enable virtuallobby [#24](https://github.com/XLabsProject/s1x-client/pull/24) ([@Joelrau](https://github.com/Joelrau))
|
||||
- Add listassetpool, consoleList, fix various problems [#32](https://github.com/XLabsProject/s1x-client/pull/32) ([@iAmThatMichael](https://github.com/iAmThatMichael))
|
||||
- Allow kbam while gamepad is enabled & Logger stuff [#33](https://github.com/XLabsProject/s1x-client/pull/33) ([@Joelrau](https://github.com/Joelrau))
|
||||
- add discord presence [#36](https://github.com/XLabsProject/s1x-client/pull/36) ([@mjkzy](https://github.com/mjkzy))
|
||||
- Minor fixes [#40](https://github.com/XLabsProject/s1x-client/pull/40) ([@Joelrau](https://github.com/Joelrau))
|
||||
- Uploading new artworks + resources [#41](https://github.com/XLabsProject/s1x-client/pull/41) ([@sortileges](https://github.com/sortileges))
|
||||
- Make it possible to open the client without console [#45](https://github.com/XLabsProject/s1x-client/pull/45) ([@Joelrau](https://github.com/Joelrau))
|
||||
- Fix slowmotion on dedicated [#54](https://github.com/XLabsProject/s1x-client/pull/54) ([@Joelrau](https://github.com/Joelrau))
|
||||
- Discord RPC - party size + party size max [#59](https://github.com/XLabsProject/s1x-client/pull/59) ([@mjkzy](https://github.com/mjkzy))
|
||||
- discord presence - host name address [#64](https://github.com/XLabsProject/s1x-client/pull/64) ([@mjkzy](https://github.com/mjkzy))
|
||||
|
||||
[Unreleased]: https://github.com/XLabsProject/s1x-client/compare/v0.0.2...HEAD
|
||||
|
||||
[v0.0.2]: https://github.com/XLabsProject/s1x-client/compare/75b6d04895a2da346ca9eba5352b300f4926b6c5...v0.0.2
|
@ -1,6 +1,7 @@
|
||||
![license](https://img.shields.io/github/license/XLabsProject/s1x-client.svg)
|
||||
[![open bugs](https://img.shields.io/github/issues/XLabsProject/s1x-client/bug?label=bugs)](https://github.com/XLabsProject/s1x-client/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/p49wb65n4he4m3h9/branch/master?svg=true)](https://ci.appveyor.com/project/XLabsProject/s1x-client/branch/develop)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/p49wb65n4he4m3h9/branch/develop?svg=true)](https://ci.appveyor.com/project/XLabsProject/s1x-client/branch/develop)
|
||||
[![Build](https://github.com/XLabsProject/s1x-client/workflows/Build/badge.svg)](https://github.com/XLabsProject/s1x-client/actions)
|
||||
[![discord](https://img.shields.io/endpoint?url=https://momo5502.com/iw4x/members-badge.php)](https://discord.gg/sKeVmR3)
|
||||
[![patreon](https://img.shields.io/badge/patreon-support-blue.svg?logo=patreon)](https://www.patreon.com/xlabsproject)
|
||||
|
||||
@ -8,7 +9,7 @@
|
||||
# S1x: Client
|
||||
|
||||
<p align="center">
|
||||
<img alig src="assets/github/banner.png?raw=true" />
|
||||
<img src="assets/github/banner.png?raw=true" />
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
|
@ -41,7 +41,7 @@ build:
|
||||
verbosity: minimal
|
||||
|
||||
artifacts:
|
||||
# - path: build/version.txt
|
||||
# - path: build/bin/**/iw6x.exe
|
||||
- path: build/version.txt
|
||||
- path: build/bin/**/s1x.exe
|
||||
# - path: build/bin/**/*.exe
|
||||
# - path: build/bin/**/*.pdb
|
||||
|
BIN
assets/banner/s1x-banner.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
assets/banner/s1x-banner.psd
Normal file
Before Width: | Height: | Size: 677 KiB After Width: | Height: | Size: 1.3 MiB |
BIN
assets/github/banner.psd
Normal file
BIN
assets/icons/s1x-128.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
assets/icons/s1x-16.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/icons/s1x-256.png
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
assets/icons/s1x-32.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
assets/icons/s1x-64.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
assets/icons/s1x-icon-small.psd
Normal file
BIN
assets/icons/s1x-icon.psd
Normal file
BIN
assets/splash/s1x-splash.png
Normal file
After Width: | Height: | Size: 734 KiB |
BIN
assets/splash/s1x-splash.psd
Normal file
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit 25bb4bd9489a28b72a77c016b7ac0c64345a7c88
|
||||
Subproject commit ef0ffefe525a6219ff245d19a832ce06f3fd3504
|
1
deps/WinToast
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit a21decbe81b66890f27f1f697577d0c3a9982e0e
|
2
deps/asmjit
vendored
@ -1 +1 @@
|
||||
Subproject commit 9cb2b298e1cdcc82cd014302cc19891bdbe4dd2b
|
||||
Subproject commit a4dd0b2d8b0fdbcda777e4d6dae0e76636080113
|
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit 954ab9bcfc8129eebb369e82ebdc0367ecec334c
|
||||
Subproject commit 910d6252770f1e517d9ed02dc0549a1d61dfe159
|
32
deps/premake/wintoast.lua
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
wintoast = {
|
||||
source = path.join(dependencies.basePath, "WinToast"),
|
||||
}
|
||||
|
||||
function wintoast.import()
|
||||
links { "WinToast" }
|
||||
wintoast.includes()
|
||||
end
|
||||
|
||||
function wintoast.includes()
|
||||
includedirs {
|
||||
path.join(wintoast.source, "src"),
|
||||
}
|
||||
end
|
||||
|
||||
function wintoast.project()
|
||||
project "WinToast"
|
||||
language "C++"
|
||||
|
||||
wintoast.includes()
|
||||
rapidjson.import();
|
||||
|
||||
files {
|
||||
path.join(wintoast.source, "src/*.h"),
|
||||
path.join(wintoast.source, "src/*.cpp"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, wintoast)
|
2
deps/protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit 4a09d77a85a46c65d8b070e20dc88db904eb83af
|
||||
Subproject commit 19fb89416f3fdc2d6668f3738f444885575285bc
|
2
deps/rapidjson
vendored
@ -1 +1 @@
|
||||
Subproject commit 3cdd3c8370a2cf5eed3fa4afbe4465a525f54e33
|
||||
Subproject commit 49aa0fc15d63a2132ecf3ba0eda1ecf6fef215a1
|
@ -144,6 +144,17 @@ namespace arxan
|
||||
// cba to implement sp, not sure if it's even needed
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
// HW-BP Mapping
|
||||
// 1404D4F40 -> 1404D4F30
|
||||
// 140509500 -> 140509410
|
||||
// 140545F80 -> 140545EE0
|
||||
// 14053CCF0 -> 14053CCC0 // dwGetLogonStatus
|
||||
|
||||
utils::hook::call(0x14053B5FE, 0x140545EE0); // some pump
|
||||
utils::hook::call(0x1404D3D42, 0x140509410); // some other pump
|
||||
utils::hook::jump(0x14053CCB0, 0x14053CCC0); // dwGetLogonStatus
|
||||
utils::hook::call(0x14053CD04, 0x14053CCC0); // dwGetLogonStatus
|
||||
|
||||
//scheduler::on_game_initialized(remove_hardware_breakpoints, scheduler::pipeline::main);
|
||||
}
|
||||
};
|
||||
|
@ -5,14 +5,13 @@
|
||||
#include "game/game.hpp"
|
||||
#include "party.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace bots
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool can_spawn()
|
||||
bool can_add()
|
||||
{
|
||||
if (party::get_client_count() < *game::mp::svs_numclients)
|
||||
{
|
||||
@ -21,9 +20,37 @@ namespace bots
|
||||
return false;
|
||||
}
|
||||
|
||||
void bot_team_join(unsigned int entity_num)
|
||||
{
|
||||
scheduler::once([entity_num]()
|
||||
{
|
||||
// auto-assign
|
||||
game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
|
||||
utils::string::va("lui 125 2 %i",
|
||||
*game::mp::sv_serverId_value), false);
|
||||
|
||||
scheduler::once([entity_num]()
|
||||
{
|
||||
// select class ( they don't select it? )
|
||||
game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
|
||||
utils::string::va("lui 9 %i %i", (rand() % (104 - 100 + 1) + 100),
|
||||
*game::mp::sv_serverId_value), false);
|
||||
}, scheduler::pipeline::server, 1s);
|
||||
}, scheduler::pipeline::server, 1s);
|
||||
}
|
||||
|
||||
void spawn_bot(const int entity_num)
|
||||
{
|
||||
game::SV_SpawnTestClient(&game::mp::g_entities[entity_num]);
|
||||
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_CORE)
|
||||
{
|
||||
//bot_team_join(entity_num); // super bugger rn
|
||||
}
|
||||
}
|
||||
|
||||
void add_bot()
|
||||
{
|
||||
if (!can_spawn())
|
||||
if (!can_add())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -33,9 +60,9 @@ namespace bots
|
||||
auto* bot_ent = game::SV_AddBot(bot_name);
|
||||
if (bot_ent)
|
||||
{
|
||||
game::SV_SpawnTestClient(bot_ent);
|
||||
spawn_bot(bot_ent->s.entityNum);
|
||||
}
|
||||
else if (can_spawn()) // workaround since first bot won't ever spawn
|
||||
else if (can_add()) // workaround since first bot won't ever spawn
|
||||
{
|
||||
add_bot();
|
||||
}
|
||||
@ -54,7 +81,7 @@ namespace bots
|
||||
|
||||
command::add("spawnBot", [](const command::params& params)
|
||||
{
|
||||
if (!game::SV_Loaded()) return;
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded()) return;
|
||||
|
||||
auto num_bots = 1;
|
||||
if (params.size() == 2)
|
||||
|
@ -2,14 +2,13 @@
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace colors
|
||||
{
|
||||
struct HsvColor
|
||||
struct hsv_color
|
||||
{
|
||||
unsigned char h;
|
||||
unsigned char s;
|
||||
@ -20,7 +19,7 @@ namespace colors
|
||||
{
|
||||
std::vector<DWORD> color_table;
|
||||
|
||||
DWORD hsv_to_rgb(const HsvColor hsv)
|
||||
DWORD hsv_to_rgb(const hsv_color hsv)
|
||||
{
|
||||
DWORD rgb;
|
||||
|
||||
@ -97,7 +96,7 @@ namespace colors
|
||||
const size_t unk, const size_t unk2)
|
||||
{
|
||||
// CL_GetClientName (CL_GetClientNameAndClantag?)
|
||||
const auto result = reinterpret_cast<size_t(*)(const int, int, char*, int, size_t, size_t)>(0x140213E60)(
|
||||
const auto result = reinterpret_cast<size_t(*)(int, int, char*, int, size_t, size_t)>(0x140213E60)(
|
||||
local_client_num, index, buf, size, unk, unk2);
|
||||
|
||||
utils::string::strip(buf, buf, size);
|
||||
|
@ -226,6 +226,15 @@ namespace command
|
||||
}
|
||||
}
|
||||
|
||||
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
|
||||
{
|
||||
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
|
||||
{
|
||||
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
|
||||
cb(header);
|
||||
}), &callback, includeOverride);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -254,6 +263,29 @@ namespace command
|
||||
*reinterpret_cast<int*>(1) = 0;
|
||||
});
|
||||
|
||||
add("consoleList", [](const params& params)
|
||||
{
|
||||
const std::string input = params.get(1);
|
||||
|
||||
std::vector<std::string> matches;
|
||||
game_console::find_matches(input, matches, false);
|
||||
|
||||
for(auto& match : matches)
|
||||
{
|
||||
auto* dvar = game::Dvar_FindVar(match.c_str());
|
||||
if (!dvar)
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "[CMD]\t %s", match.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "[DVAR]\t%s \"%s\"", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current));
|
||||
}
|
||||
}
|
||||
|
||||
game_console::print(game_console::con_type_info, "Total %i matches", matches.size());
|
||||
});
|
||||
|
||||
add("dvarDump", []()
|
||||
{
|
||||
game_console::print(game_console::con_type_info,
|
||||
@ -291,6 +323,44 @@ namespace command
|
||||
game_console::print(game_console::con_type_info,
|
||||
"================================ END COMMAND DUMP =================================\n");
|
||||
});
|
||||
|
||||
add("listassetpool", [](const params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game_console::print(game_console::con_type_info,
|
||||
"listassetpool <poolnumber>: list all the assets in the specified pool\n");
|
||||
|
||||
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "%d %s\n", i, game::g_assetNames[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
|
||||
|
||||
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
|
||||
{
|
||||
game_console::print(game_console::con_type_error,
|
||||
"Invalid pool passed must be between [%d, %d]", 0,
|
||||
game::XAssetType::ASSET_TYPE_COUNT - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
game_console::print(game_console::con_type_info, "Listing assets in pool %s",
|
||||
game::g_assetNames[type]);
|
||||
|
||||
enum_assets(type, [type](game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = game::XAsset{ type, header };
|
||||
const auto* const asset_name = game::DB_GetXAssetName(&asset);
|
||||
//const auto entry = game::DB_FindXAssetEntry(type, asset_name);
|
||||
//TODO: display which zone the asset is from
|
||||
game_console::print(game_console::con_type_info, "%s", asset_name);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void add_commands_sp()
|
||||
@ -363,7 +433,7 @@ namespace command
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(0);
|
||||
auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
|
||||
@ -388,7 +458,7 @@ namespace command
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(0);
|
||||
auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
game::G_TakePlayerWeapon(ps, wp);
|
||||
@ -485,7 +555,7 @@ namespace command
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
|
||||
auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
|
||||
@ -513,7 +583,7 @@ namespace command
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
|
||||
auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
game::G_TakePlayerWeapon(ps, wp);
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "game_console.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
@ -33,9 +33,9 @@ namespace console
|
||||
{
|
||||
hide_console();
|
||||
|
||||
_pipe(this->handles_, 1024, _O_TEXT);
|
||||
_dup2(this->handles_[1], 1);
|
||||
_dup2(this->handles_[1], 2);
|
||||
(void)_pipe(this->handles_, 1024, _O_TEXT);
|
||||
(void)_dup2(this->handles_[1], 1);
|
||||
(void)_dup2(this->handles_[1], 2);
|
||||
|
||||
//setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
//setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
@ -46,6 +46,7 @@ namespace console
|
||||
scheduler::loop([this]()
|
||||
{
|
||||
this->log_messages();
|
||||
this->event_frame();
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
this->console_runner_ = utils::thread::create_named_thread("Console IO", [this]
|
||||
@ -56,11 +57,11 @@ namespace console
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
this->terminate_runner_ = true;
|
||||
|
||||
printf("\r\n");
|
||||
_flushall();
|
||||
|
||||
this->terminate_runner_ = true;
|
||||
|
||||
if (this->console_runner_.joinable())
|
||||
{
|
||||
this->console_runner_.join();
|
||||
@ -72,7 +73,22 @@ namespace console
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
this->initialize();
|
||||
if (game::environment::is_dedi() || !utils::flags::has_flag("noconsole"))
|
||||
{
|
||||
game::Sys_ShowConsole();
|
||||
}
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
// Hide that shit
|
||||
ShowWindow(console::get_window(), SW_MINIMIZE);
|
||||
}
|
||||
|
||||
// Async console is not ready yet :/
|
||||
//this->initialize();
|
||||
|
||||
std::lock_guard<std::mutex> _(this->mutex_);
|
||||
this->console_initialized_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -85,15 +101,27 @@ namespace console
|
||||
|
||||
int handles_[2]{};
|
||||
|
||||
void event_frame()
|
||||
{
|
||||
MSG msg;
|
||||
while (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
if (msg.message == WM_QUIT)
|
||||
{
|
||||
command::execute("quit", false);
|
||||
break;
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
void initialize()
|
||||
{
|
||||
utils::thread::create_named_thread("Console", [this]()
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
game::Sys_ShowConsole();
|
||||
}
|
||||
else if (!utils::flags::has_flag("noconsole"))
|
||||
if (game::environment::is_dedi() || !utils::flags::has_flag("noconsole"))
|
||||
{
|
||||
game::Sys_ShowConsole();
|
||||
}
|
||||
@ -196,8 +224,8 @@ namespace console
|
||||
|
||||
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
|
||||
|
||||
auto logoWindow = *reinterpret_cast<HWND*>(SELECT_VALUE(0x14A9F6080, 0x14B5B94D0));
|
||||
SetWindowPos(logoWindow, 0, 5, 5, width - 25, 60, 0);
|
||||
auto* const logo_window = *reinterpret_cast<HWND*>(SELECT_VALUE(0x14A9F6080, 0x14B5B94D0));
|
||||
SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "network.hpp"
|
||||
#include "command.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
@ -43,7 +42,7 @@ namespace dedicated
|
||||
return startup_command_queue;
|
||||
}
|
||||
|
||||
void execute_startup_command(int client, int controllerIndex, const char* command)
|
||||
void execute_startup_command(int client, int /*controllerIndex*/, const char* command)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||
{
|
||||
@ -151,6 +150,12 @@ namespace dedicated
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable VirtualLobby
|
||||
dvars::override::Dvar_RegisterBool("virtualLobbyEnabled", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ);
|
||||
|
||||
// Disable r_preloadShaders
|
||||
dvars::override::Dvar_RegisterBool("r_preloadShaders", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ);
|
||||
|
||||
// Don't allow sv_hostname to be changed by the game
|
||||
dvars::disable::Dvar_SetString("sv_hostname");
|
||||
|
||||
@ -263,14 +268,14 @@ namespace dedicated
|
||||
printf("==================================\n");
|
||||
|
||||
// remove disconnect command
|
||||
game::Cmd_RemoveCommand((const char*)751);
|
||||
game::Cmd_RemoveCommand(reinterpret_cast<const char*>(751));
|
||||
|
||||
execute_startup_command_queue();
|
||||
execute_console_command_queue();
|
||||
|
||||
// Send heartbeat to dpmaster
|
||||
scheduler::once(send_heartbeat, scheduler::pipeline::server);
|
||||
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 2min);
|
||||
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min);
|
||||
command::add("heartbeat", send_heartbeat);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "demonware.hpp"
|
||||
#include "game_module.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game/demonware/demonware.hpp"
|
||||
#include "game/demonware/servers/lobby_server.hpp"
|
||||
#include "game/demonware/servers/auth3_server.hpp"
|
||||
#include "game/demonware/servers/stun_server.hpp"
|
||||
#include "game/demonware/servers/umbrella_server.hpp"
|
||||
#include "game/demonware/server_registry.hpp"
|
||||
|
||||
#define TCP_BLOCKING true
|
||||
#define UDP_BLOCKING false
|
||||
@ -17,6 +18,328 @@ namespace demonware
|
||||
{
|
||||
namespace
|
||||
{
|
||||
volatile bool exit_server;
|
||||
std::thread server_thread;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, bool>> blocking_sockets;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, tcp_server*>> socket_map;
|
||||
server_registry<tcp_server> tcp_servers;
|
||||
server_registry<udp_server> udp_servers;
|
||||
|
||||
tcp_server* find_server(const SOCKET socket)
|
||||
{
|
||||
return socket_map.access<tcp_server*>([&](const std::unordered_map<SOCKET, tcp_server*>& map) -> tcp_server*
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
bool socket_link(const SOCKET socket, const uint32_t address)
|
||||
{
|
||||
auto* server = tcp_servers.find(address);
|
||||
if (!server)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
map[socket] = server;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void socket_unlink(const SOCKET socket)
|
||||
{
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool is_socket_blocking(const SOCKET socket, const bool def)
|
||||
{
|
||||
return blocking_sockets.access<bool>([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
void remove_blocking_socket(const SOCKET socket)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void add_blocking_socket(const SOCKET socket, const bool block)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
map[socket] = block;
|
||||
});
|
||||
}
|
||||
|
||||
void server_main()
|
||||
{
|
||||
exit_server = false;
|
||||
|
||||
while (!exit_server)
|
||||
{
|
||||
tcp_servers.frame();
|
||||
udp_servers.frame();
|
||||
std::this_thread::sleep_for(50ms);
|
||||
}
|
||||
}
|
||||
|
||||
namespace io
|
||||
{
|
||||
hostent* gethostbyname_stub(const char* name)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[ network ]: [gethostbyname]: \"%s\"\n", name);
|
||||
#endif
|
||||
|
||||
base_server* server = tcp_servers.find(name);
|
||||
if (!server)
|
||||
{
|
||||
server = udp_servers.find(name);
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
return gethostbyname(name);
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
static thread_local in_addr address{};
|
||||
address.s_addr = server->get_address();
|
||||
|
||||
static thread_local in_addr* addr_list[2]{};
|
||||
addr_list[0] = &address;
|
||||
addr_list[1] = nullptr;
|
||||
|
||||
static thread_local hostent host{};
|
||||
host.h_name = const_cast<char*>(name);
|
||||
host.h_aliases = nullptr;
|
||||
host.h_addrtype = AF_INET;
|
||||
host.h_length = sizeof(in_addr);
|
||||
host.h_addr_list = reinterpret_cast<char**>(addr_list);
|
||||
|
||||
return &host;
|
||||
}
|
||||
|
||||
int connect_stub(const SOCKET s, const struct sockaddr* addr, const int len)
|
||||
{
|
||||
if (len == sizeof(sockaddr_in))
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(addr);
|
||||
if (socket_link(s, in_addr->sin_addr.s_addr)) return 0;
|
||||
}
|
||||
|
||||
return connect(s, addr, len);
|
||||
}
|
||||
|
||||
int closesocket_stub(const SOCKET s)
|
||||
{
|
||||
remove_blocking_socket(s);
|
||||
socket_unlink(s);
|
||||
|
||||
return closesocket(s);
|
||||
}
|
||||
|
||||
int send_stub(const SOCKET s, const char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
return send(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int recv_stub(const SOCKET s, char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (server->pending_data())
|
||||
{
|
||||
return static_cast<int>(server->handle_output(buf, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return recv(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int sendto_stub(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to,
|
||||
const int tolen)
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(to);
|
||||
auto* server = udp_servers.find(in_addr->sin_addr.s_addr);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len, {s, to, tolen});
|
||||
return len;
|
||||
}
|
||||
|
||||
return sendto(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int recvfrom_stub(const SOCKET s, char* buf, const int len, const int flags, struct sockaddr* from,
|
||||
int* fromlen)
|
||||
{
|
||||
// Not supported yet
|
||||
if (is_socket_blocking(s, UDP_BLOCKING))
|
||||
{
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
size_t result = 0;
|
||||
udp_servers.for_each([&](udp_server& server)
|
||||
{
|
||||
if (server.pending_data(s))
|
||||
{
|
||||
result = server.handle_output(
|
||||
s, buf, static_cast<size_t>(len), from, fromlen);
|
||||
}
|
||||
});
|
||||
|
||||
if (result)
|
||||
{
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
int select_stub(const int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
|
||||
struct timeval* timeout)
|
||||
{
|
||||
if (exit_server)
|
||||
{
|
||||
return select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
}
|
||||
|
||||
auto result = 0;
|
||||
std::vector<SOCKET> read_sockets;
|
||||
std::vector<SOCKET> write_sockets;
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& sockets)
|
||||
{
|
||||
for (auto& s : sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, readfds))
|
||||
{
|
||||
if (s.second->pending_data())
|
||||
{
|
||||
read_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, readfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writefds)
|
||||
{
|
||||
if (FD_ISSET(s.first, writefds))
|
||||
{
|
||||
write_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, writefds);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, exceptfds))
|
||||
{
|
||||
FD_CLR(s.first, exceptfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ((!readfds || readfds->fd_count == 0) && (!writefds || writefds->fd_count == 0))
|
||||
{
|
||||
timeout->tv_sec = 0;
|
||||
timeout->tv_usec = 0;
|
||||
}
|
||||
|
||||
result = select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
if (result < 0) result = 0;
|
||||
|
||||
for (const auto& socket : read_sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
FD_SET(socket, readfds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& socket : write_sockets)
|
||||
{
|
||||
if (writefds)
|
||||
{
|
||||
FD_SET(socket, writefds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int ioctlsocket_stub(const SOCKET s, const long cmd, u_long* argp)
|
||||
{
|
||||
if (static_cast<unsigned long>(cmd) == (FIONBIO))
|
||||
{
|
||||
add_blocking_socket(s, *argp == 0);
|
||||
}
|
||||
|
||||
return ioctlsocket(s, cmd, argp);
|
||||
}
|
||||
|
||||
BOOL internet_get_connected_state_stub(LPDWORD, DWORD)
|
||||
{
|
||||
// Allow offline play
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void bd_logger_stub(const char* const function, const char* const msg, ...)
|
||||
{
|
||||
static auto* enabled =
|
||||
@ -38,345 +361,52 @@ namespace demonware
|
||||
}
|
||||
}
|
||||
|
||||
volatile bool exit_server;
|
||||
std::thread server_thread;
|
||||
std::recursive_mutex server_mutex;
|
||||
std::map<SOCKET, bool> sockets_blocking;
|
||||
std::map<SOCKET, server_ptr> sockets;
|
||||
std::map<std::uint32_t, server_ptr> servers;
|
||||
|
||||
void register_server(server_ptr server)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
servers[server->address()] = server;
|
||||
}
|
||||
|
||||
auto find_server_by_address(const std::uint32_t address) -> server_ptr
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
const auto it = servers.find(address);
|
||||
|
||||
if (it != servers.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return server_ptr(nullptr);
|
||||
}
|
||||
|
||||
auto find_server_by_name(const std::string& name) -> server_ptr
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
return find_server_by_address(utils::cryptography::jenkins_one_at_a_time::compute(name));
|
||||
}
|
||||
|
||||
auto find_server_by_socket(const SOCKET socket) -> server_ptr
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
const auto it = sockets.find(socket);
|
||||
|
||||
if (it != sockets.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return server_ptr(nullptr);
|
||||
}
|
||||
|
||||
auto socket_link(const SOCKET socket, std::uint32_t address) -> bool
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
const auto server = find_server_by_address(address);
|
||||
|
||||
if (!server) return false;
|
||||
|
||||
sockets[socket] = server;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void socket_unlink(const SOCKET socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
const auto it = sockets.find(socket);
|
||||
|
||||
if (it != sockets.end())
|
||||
{
|
||||
sockets.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
auto socket_is_blocking(const SOCKET socket, const bool def) -> bool
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
if (sockets_blocking.find(socket) != sockets_blocking.end())
|
||||
{
|
||||
return sockets_blocking[socket];
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
void remove_blocking_socket(const SOCKET socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
const auto it = sockets_blocking.find(socket);
|
||||
|
||||
if (it != sockets_blocking.end())
|
||||
{
|
||||
sockets_blocking.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void add_blocking_socket(const SOCKET socket, const bool block)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
sockets_blocking[socket] = block;
|
||||
}
|
||||
|
||||
void server_main()
|
||||
{
|
||||
exit_server = false;
|
||||
|
||||
while (!exit_server)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> $(server_mutex);
|
||||
|
||||
for (auto& server : servers)
|
||||
{
|
||||
server.second->frame();
|
||||
}
|
||||
|
||||
$.unlock();
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
}
|
||||
}
|
||||
|
||||
// WINSOCK
|
||||
|
||||
namespace io
|
||||
{
|
||||
int getaddrinfo_stub(PCSTR pNodeName, PCSTR pServiceName, const ADDRINFOA* pHints, PADDRINFOA* ppResult)
|
||||
{
|
||||
return getaddrinfo(pNodeName, pServiceName, pHints, ppResult);
|
||||
}
|
||||
|
||||
hostent* gethostbyname_stub(const char* name)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[ network ]: [gethostbyname]: \"%s\"\n", name);
|
||||
#endif
|
||||
|
||||
const auto server = find_server_by_name(name);
|
||||
|
||||
if (server)
|
||||
{
|
||||
static thread_local in_addr address;
|
||||
address.s_addr = server->address();
|
||||
|
||||
static thread_local in_addr* addr_list[2];
|
||||
addr_list[0] = &address;
|
||||
addr_list[1] = nullptr;
|
||||
|
||||
static thread_local hostent host;
|
||||
host.h_name = const_cast<char*>(name);
|
||||
host.h_aliases = nullptr;
|
||||
host.h_addrtype = AF_INET;
|
||||
host.h_length = sizeof(in_addr);
|
||||
host.h_addr_list = reinterpret_cast<char**>(addr_list);
|
||||
|
||||
return &host;
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
return gethostbyname(name);
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
int connect_stub(SOCKET s, const struct sockaddr* addr, int len)
|
||||
{
|
||||
if (len == sizeof(sockaddr_in))
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(addr);
|
||||
if (socket_link(s, in_addr->sin_addr.s_addr)) return 0;
|
||||
}
|
||||
|
||||
return connect(s, addr, len);
|
||||
}
|
||||
|
||||
int closesocket_stub(SOCKET s)
|
||||
{
|
||||
remove_blocking_socket(s);
|
||||
socket_unlink(s);
|
||||
|
||||
return closesocket(s);
|
||||
}
|
||||
|
||||
int send_stub(SOCKET s, const char* buf, int len, int flags)
|
||||
{
|
||||
auto server = find_server_by_socket(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
return server->recv(buf, len);
|
||||
}
|
||||
|
||||
return send(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int recv_stub(SOCKET s, char* buf, int len, int flags)
|
||||
{
|
||||
auto server = find_server_by_socket(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (server->pending_data())
|
||||
{
|
||||
return server->send(buf, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return recv(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int sendto_stub(SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen)
|
||||
{
|
||||
return sendto(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int recvfrom_stub(SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen)
|
||||
{
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
int select_stub(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout)
|
||||
{
|
||||
int result = 0;
|
||||
std::vector<SOCKET> read_sockets;
|
||||
std::vector<SOCKET> write_sockets;
|
||||
|
||||
for (auto& s : sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, readfds))
|
||||
{
|
||||
if (s.second->pending_data())
|
||||
{
|
||||
read_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, readfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writefds)
|
||||
{
|
||||
if (FD_ISSET(s.first, writefds))
|
||||
{
|
||||
write_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, writefds);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, exceptfds))
|
||||
{
|
||||
FD_CLR(s.first, exceptfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((!readfds || readfds->fd_count == 0) && (!writefds || writefds->fd_count == 0))
|
||||
{
|
||||
timeout->tv_sec = 0;
|
||||
timeout->tv_usec = 0;
|
||||
}
|
||||
|
||||
result = select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
if (result < 0) result = 0;
|
||||
|
||||
for (size_t i = 0; i < read_sockets.size(); i++)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
FD_SET(read_sockets.at(i), readfds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < write_sockets.size(); i++)
|
||||
{
|
||||
if (writefds)
|
||||
{
|
||||
FD_SET(write_sockets.at(i), writefds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int ioctlsocket_stub(const SOCKET s, const long cmd, u_long* argp)
|
||||
{
|
||||
if (static_cast<unsigned long>(cmd) == (FIONBIO))
|
||||
{
|
||||
add_blocking_socket(s, *argp == 0);
|
||||
}
|
||||
|
||||
return ioctlsocket(s, cmd, argp);
|
||||
}
|
||||
|
||||
bool register_hook(const std::string& process, void* stub)
|
||||
{
|
||||
const auto game_module = game_module::get_game_module();
|
||||
|
||||
auto result = false;
|
||||
result = result || utils::hook::iat(game_module, "wsock32.dll", process, stub);
|
||||
result = result || utils::hook::iat(game_module, "WS2_32.dll", process, stub);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
register_server(std::make_shared<demonware::server_auth3>("aw-pc-auth3.prod.demonware.net"));
|
||||
register_server(std::make_shared<demonware::server_lobby>("aw-pc-lobby.prod.demonware.net"));
|
||||
udp_servers.create<stun_server>("s1-stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("s1-stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("s1-stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("s1-stun.au.demonware.net");
|
||||
|
||||
udp_servers.create<stun_server>("stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.au.demonware.net");
|
||||
|
||||
tcp_servers.create<auth3_server>("aw-pc-auth3.prod.demonware.net");
|
||||
tcp_servers.create<lobby_server>("aw-pc-lobby.prod.demonware.net");
|
||||
tcp_servers.create<umbrella_server>("prod.umbrella.demonware.net");
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
server_thread = utils::thread::create_named_thread("Demonware", server_main);
|
||||
}
|
||||
|
||||
io::register_hook("send", io::send_stub);
|
||||
io::register_hook("recv", io::recv_stub);
|
||||
io::register_hook("sendto", io::sendto_stub);
|
||||
io::register_hook("recvfrom", io::recvfrom_stub);
|
||||
io::register_hook("select", io::select_stub);
|
||||
io::register_hook("connect", io::connect_stub);
|
||||
io::register_hook("closesocket", io::closesocket_stub);
|
||||
io::register_hook("ioctlsocket", io::ioctlsocket_stub);
|
||||
io::register_hook("gethostbyname", io::gethostbyname_stub);
|
||||
io::register_hook("getaddrinfo", io::getaddrinfo_stub);
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "WS2_32.dll")
|
||||
{
|
||||
if (function == "#3") return io::closesocket_stub;
|
||||
if (function == "#4") return io::connect_stub;
|
||||
if (function == "#10") return io::ioctlsocket_stub;
|
||||
if (function == "#16") return io::recv_stub;
|
||||
if (function == "#17") return io::recvfrom_stub;
|
||||
if (function == "#18") return io::select_stub;
|
||||
if (function == "#19") return io::send_stub;
|
||||
if (function == "#20") return io::sendto_stub;
|
||||
if (function == "#52") return io::gethostbyname_stub;
|
||||
}
|
||||
|
||||
if (function == "InternetGetConnectedState")
|
||||
{
|
||||
return io::internet_get_connected_state_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
@ -394,6 +424,11 @@ namespace demonware
|
||||
utils::hook::set<uint8_t>(0x140698B69, 0xAF); // CURLOPT_SSL_VERIFYHOST
|
||||
utils::hook::set<uint8_t>(0x14088D0E8, 0x0); // HTTPS -> HTTP
|
||||
|
||||
// HTTPS -> HTTP
|
||||
utils::hook::inject(0x14003852E, "http://prod.umbrella.demonware.net/v1.0/");
|
||||
utils::hook::inject(0x14003884F, "http://prod.umbrella.demonware.net/v1.0/");
|
||||
utils::hook::inject(0x140038A07, "http://prod.umbrella.demonware.net/v1.0/");
|
||||
|
||||
utils::hook::set<uint8_t>(0x140437CC0, 0xC3); // SV_SendMatchData
|
||||
}
|
||||
|
||||
@ -404,8 +439,6 @@ namespace demonware
|
||||
{
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
servers.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
}
|
146
src/client/component/discord.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include <discord_rpc.h>
|
||||
#include <component/party.hpp>
|
||||
|
||||
namespace discord
|
||||
{
|
||||
namespace
|
||||
{
|
||||
DiscordRichPresence discord_presence;
|
||||
|
||||
void update_discord()
|
||||
{
|
||||
Discord_RunCallbacks();
|
||||
|
||||
auto* dvar = game::Dvar_FindVar("virtualLobbyActive");
|
||||
if (!game::CL_IsCgameInitialized() || (dvar && dvar->current.enabled == 1))
|
||||
{
|
||||
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer";
|
||||
|
||||
dvar = game::Dvar_FindVar("virtualLobbyInFiringRange");
|
||||
if (dvar && dvar->current.enabled == 1)
|
||||
{
|
||||
discord_presence.state = "Firing Range";
|
||||
}
|
||||
else
|
||||
{
|
||||
discord_presence.state = "Main Menu";
|
||||
}
|
||||
|
||||
discord_presence.partySize = 0;
|
||||
discord_presence.partyMax = 0;
|
||||
discord_presence.startTimestamp = 0;
|
||||
|
||||
discord_presence.largeImageKey = game::environment::is_sp() ? "menu_singleplayer" : "menu_multiplayer";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
const auto* gametype = game::UI_GetGameTypeDisplayName(game::Dvar_FindVar("ui_gametype")->current.string);
|
||||
const auto* map = game::UI_GetMapDisplayName(game::Dvar_FindVar("ui_mapname")->current.string);
|
||||
|
||||
discord_presence.details = utils::string::va("%s on %s", gametype, map);
|
||||
|
||||
auto* const host_name = reinterpret_cast<char*>(0x141646CC4);
|
||||
utils::string::strip(host_name, host_name, static_cast<int>(strlen(host_name)) + 1);
|
||||
|
||||
if (!strcmp(host_name, "key"))
|
||||
{
|
||||
discord_presence.state = game::Dvar_FindVar("sv_hostname")->current.string;
|
||||
}
|
||||
else
|
||||
{
|
||||
discord_presence.state = host_name;
|
||||
}
|
||||
|
||||
dvar = game::Dvar_FindVar("sv_maxclients");
|
||||
if (dvar)
|
||||
{
|
||||
auto clients = party::get_client_count();
|
||||
discord_presence.partySize = clients;
|
||||
discord_presence.partyMax = dvar->current.integer;
|
||||
}
|
||||
|
||||
if (!discord_presence.startTimestamp)
|
||||
{
|
||||
discord_presence.startTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
discord_presence.largeImageKey = game::Dvar_FindVar("ui_mapname")->current.string;
|
||||
discord_presence.largeImageText = game::UI_GetGameTypeDisplayName(game::Dvar_FindVar("ui_mapname")->current.string);
|
||||
}
|
||||
|
||||
Discord_UpdatePresence(&discord_presence);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordEventHandlers handlers;
|
||||
ZeroMemory(&handlers, sizeof(handlers));
|
||||
handlers.ready = ready;
|
||||
handlers.errored = errored;
|
||||
handlers.disconnected = errored;
|
||||
handlers.joinGame = nullptr;
|
||||
handlers.spectateGame = nullptr;
|
||||
handlers.joinRequest = nullptr;
|
||||
|
||||
Discord_Initialize("823223724013912124", &handlers, 1, nullptr);
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
scheduler::once(update_discord, scheduler::pipeline::async);
|
||||
scheduler::loop(update_discord, scheduler::pipeline::async, 20s);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
if (!initialized_ || game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Discord_Shutdown();
|
||||
}
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
static void ready(const DiscordUser* /*request*/)
|
||||
{
|
||||
ZeroMemory(&discord_presence, sizeof(discord_presence));
|
||||
|
||||
discord_presence.instance = 1;
|
||||
|
||||
Discord_UpdatePresence(&discord_presence);
|
||||
}
|
||||
|
||||
static void errored(const int error_code, const char* message)
|
||||
{
|
||||
printf("Discord: (%i) %s", error_code, message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifndef DEV_BUILD
|
||||
REGISTER_COMPONENT(discord::component)
|
||||
#endif
|
@ -78,10 +78,10 @@ namespace dvars
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
template <typename T>
|
||||
T* find_dvar(std::vector<T>* vec, const std::string& name)
|
||||
{
|
||||
for (auto i = 0; i < vec->size(); i++)
|
||||
for (auto i = 0ull; i < vec->size(); i++)
|
||||
{
|
||||
if (name == vec->at(i).name)
|
||||
{
|
||||
@ -150,7 +150,8 @@ namespace dvars
|
||||
register_bool_overrides.push_back(std::move(values));
|
||||
}
|
||||
|
||||
void Dvar_RegisterFloat(const std::string& name, float value, float min, float max, unsigned int flags, const std::string& description)
|
||||
void Dvar_RegisterFloat(const std::string& name, float value, float min, float max, unsigned int flags,
|
||||
const std::string& description)
|
||||
{
|
||||
dvar_float values;
|
||||
values.name = name;
|
||||
@ -162,7 +163,8 @@ namespace dvars
|
||||
register_float_overrides.push_back(std::move(values));
|
||||
}
|
||||
|
||||
void Dvar_RegisterInt(const std::string& name, int value, int min, int max, unsigned int flags, const std::string& description)
|
||||
void Dvar_RegisterInt(const std::string& name, int value, int min, int max, unsigned int flags,
|
||||
const std::string& description)
|
||||
{
|
||||
dvar_int values;
|
||||
values.name = name;
|
||||
@ -174,7 +176,8 @@ namespace dvars
|
||||
register_int_overrides.push_back(std::move(values));
|
||||
}
|
||||
|
||||
void Dvar_RegisterString(const std::string& name, const std::string& value, unsigned int flags, const std::string& description)
|
||||
void Dvar_RegisterString(const std::string& name, const std::string& value, unsigned int flags,
|
||||
const std::string& description)
|
||||
{
|
||||
dvar_string values;
|
||||
values.name = name;
|
||||
@ -240,7 +243,8 @@ namespace dvars
|
||||
return dvar_register_bool_hook.invoke<game::dvar_t*>(name, value, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_float(const char* name, float value, float min, float max, unsigned int flags, const char* description)
|
||||
game::dvar_t* dvar_register_float(const char* name, float value, float min, float max, unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
auto* var = find_dvar(&override::register_float_overrides, name);
|
||||
if (var)
|
||||
@ -255,7 +259,8 @@ namespace dvars
|
||||
return dvar_register_float_hook.invoke<game::dvar_t*>(name, value, min, max, flags, description);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_int(const char* name, int value, int min, int max, unsigned int flags, const char* description)
|
||||
game::dvar_t* dvar_register_int(const char* name, int value, int min, int max, unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
auto* var = find_dvar(&override::register_int_overrides, name);
|
||||
if (var)
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
//#include "system_check.hpp"
|
||||
//#include "scheduler.hpp"
|
||||
#include "system_check.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
@ -11,10 +11,12 @@
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/compression.hpp>
|
||||
|
||||
//#include <exception/minidump.hpp>
|
||||
#include <exception/minidump.hpp>
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
namespace exception
|
||||
{
|
||||
namespace
|
||||
@ -31,6 +33,25 @@ namespace exception
|
||||
std::atomic<int> recovery_counts = {0};
|
||||
} recovery_data;
|
||||
|
||||
bool is_game_thread()
|
||||
{
|
||||
static std::vector<int> allowed_threads =
|
||||
{
|
||||
game::THREAD_CONTEXT_MAIN,
|
||||
};
|
||||
|
||||
const auto self_id = GetCurrentThreadId();
|
||||
for (const auto& index : allowed_threads)
|
||||
{
|
||||
if (game::threadIds[index] == self_id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_exception_interval_too_short()
|
||||
{
|
||||
const auto delta = std::chrono::high_resolution_clock::now() - recovery_data.last_recovery;
|
||||
@ -48,6 +69,14 @@ namespace exception
|
||||
return initialized;
|
||||
}
|
||||
|
||||
bool is_recoverable()
|
||||
{
|
||||
return is_game_thread()
|
||||
&& !is_exception_interval_too_short()
|
||||
&& !too_many_exceptions_occured()
|
||||
&& is_initialized();
|
||||
}
|
||||
|
||||
void show_mouse_cursor()
|
||||
{
|
||||
while (ShowCursor(TRUE) < 0);
|
||||
@ -55,18 +84,67 @@ namespace exception
|
||||
|
||||
void display_error_dialog()
|
||||
{
|
||||
std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n\n",
|
||||
std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n"
|
||||
"A minidump has been written.\n\n",
|
||||
exception_data.code, exception_data.address);
|
||||
|
||||
error_str += "Make sure to update your graphics card drivers and install operating system updates!";
|
||||
if (!system_check::is_valid())
|
||||
{
|
||||
error_str += "Make sure to get supported game files to avoid such crashes!";
|
||||
}
|
||||
else
|
||||
{
|
||||
error_str += "Make sure to update your graphics card drivers and install operating system updates!";
|
||||
}
|
||||
|
||||
utils::thread::suspend_other_threads();
|
||||
show_mouse_cursor();
|
||||
|
||||
MessageBoxA(nullptr, error_str.data(), "ERROR", MB_ICONERROR);
|
||||
MessageBoxA(nullptr, error_str.data(), "S1x ERROR", MB_ICONERROR);
|
||||
TerminateProcess(GetCurrentProcess(), exception_data.code);
|
||||
}
|
||||
|
||||
void reset_state()
|
||||
{
|
||||
if (dvars::cg_legacyCrashHandling && dvars::cg_legacyCrashHandling->current.enabled)
|
||||
{
|
||||
display_error_dialog();
|
||||
}
|
||||
|
||||
// TODO: Add a limit for dedi restarts
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
utils::nt::relaunch_self();
|
||||
utils::nt::terminate(exception_data.code);
|
||||
}
|
||||
|
||||
if (is_recoverable())
|
||||
{
|
||||
recovery_data.last_recovery = std::chrono::high_resolution_clock::now();
|
||||
++recovery_data.recovery_counts;
|
||||
game::Com_Error(game::ERR_DROP, "Fatal error (0x%08X) at 0x%p.\nA minidump has been written.\n\n"
|
||||
"S1x has tried to recover your game, but it might not run stable anymore.\n\n"
|
||||
"Make sure to update your graphics card drivers and install operating system updates!",
|
||||
exception_data.code, exception_data.address);
|
||||
}
|
||||
else
|
||||
{
|
||||
display_error_dialog();
|
||||
}
|
||||
}
|
||||
|
||||
size_t get_reset_state_stub()
|
||||
{
|
||||
static auto* stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.sub(rsp, 0x10);
|
||||
a.or_(rsp, 0x8);
|
||||
a.jmp(reset_state);
|
||||
});
|
||||
|
||||
return reinterpret_cast<size_t>(stub);
|
||||
}
|
||||
|
||||
std::string get_timestamp()
|
||||
{
|
||||
tm ltime{};
|
||||
@ -79,6 +157,49 @@ namespace exception
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
std::string info{};
|
||||
const auto line = [&info](const std::string& text)
|
||||
{
|
||||
info.append(text);
|
||||
info.append("\r\n");
|
||||
};
|
||||
|
||||
line("S1x Crash Dump");
|
||||
line("");
|
||||
line("Version: "s + VERSION);
|
||||
line("Environment: "s + game::environment::get_string());
|
||||
line("Timestamp: "s + get_timestamp());
|
||||
line("Clean game: "s + (system_check::is_valid() ? "Yes" : "No"));
|
||||
line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode));
|
||||
line(utils::string::va("Address: 0x%llX", exceptioninfo->ExceptionRecord->ExceptionAddress));
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
OSVERSIONINFOEXA version_info;
|
||||
ZeroMemory(&version_info, sizeof(version_info));
|
||||
version_info.dwOSVersionInfoSize = sizeof(version_info);
|
||||
GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&version_info));
|
||||
#pragma warning(pop)
|
||||
|
||||
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
const std::string crash_name = utils::string::va("minidumps/s1x-crash-%d-%s.zip",
|
||||
game::environment::get_real_mode(),
|
||||
get_timestamp().data());
|
||||
|
||||
utils::compression::zip::archive zip_file{};
|
||||
zip_file.add("crash.dmp", create_minidump(exceptioninfo));
|
||||
zip_file.add("info.txt", generate_crash_info(exceptioninfo));
|
||||
zip_file.write(crash_name, "S1x Crash Dump");
|
||||
}
|
||||
|
||||
bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
const auto code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
@ -87,13 +208,17 @@ namespace exception
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
if (!is_harmless_error(exceptioninfo))
|
||||
if (is_harmless_error(exceptioninfo))
|
||||
{
|
||||
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
|
||||
display_error_dialog();
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
write_minidump(exceptioninfo);
|
||||
|
||||
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
|
||||
exceptioninfo->ContextRecord->Rip = get_reset_state_stub();
|
||||
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
@ -111,6 +236,16 @@ namespace exception
|
||||
{
|
||||
SetUnhandledExceptionFilter(exception_filter);
|
||||
utils::hook::jump(SetUnhandledExceptionFilter, set_unhandled_exception_filter_stub, true);
|
||||
|
||||
scheduler::on_game_initialized([]()
|
||||
{
|
||||
is_initialized() = true;
|
||||
});
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
dvars::cg_legacyCrashHandling = game::Dvar_RegisterBool("cg_legacyCrashHandling", false, game::DVAR_FLAG_SAVED, "Disable new crash handling");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -11,15 +11,16 @@
|
||||
namespace fastfiles
|
||||
{
|
||||
static std::string current_fastfile;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour db_try_load_x_file_internal_hook;
|
||||
|
||||
void db_try_load_x_file_internal(const char* zoneName, const int flags)
|
||||
void db_try_load_x_file_internal(const char* zone_name, const int flags)
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "Loading fastfile %s\n", zoneName);
|
||||
current_fastfile = zoneName;
|
||||
return db_try_load_x_file_internal_hook.invoke<void>(zoneName, flags);
|
||||
game_console::print(game_console::con_type_info, "Loading fastfile %s\n", zone_name);
|
||||
current_fastfile = zone_name;
|
||||
return db_try_load_x_file_internal_hook.invoke<void>(zone_name, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,15 +29,37 @@ namespace fastfiles
|
||||
return current_fastfile.data();
|
||||
}
|
||||
|
||||
void reallocate_asset_pool(const game::XAssetType type, const unsigned int new_size)
|
||||
constexpr int get_asset_type_size(const game::XAssetType type)
|
||||
{
|
||||
const size_t element_size = game::DB_GetXAssetTypeSize(type);
|
||||
constexpr int asset_type_sizes[] =
|
||||
{
|
||||
96, 88, 128, 56, 40, 216, 56, 680,
|
||||
480, 32, 32, 32, 32, 32, 352, 1456,
|
||||
104, 32, 24, 152, 152, 152, 16, 64,
|
||||
640, 40, 16, 408, 24, 288, 176, 2800,
|
||||
48, -1, 40, 24, 200, 88, 16, 120,
|
||||
3560, 32, 64, 16, 16, -1, -1, -1,
|
||||
-1, 24, 40, 24, 40, 24, 128, 2256,
|
||||
136, 32, 72, 24, 64, 88, 48, 32,
|
||||
96, 152, 64, 32,
|
||||
};
|
||||
|
||||
auto* new_pool = utils::memory::get_allocator()->allocate(new_size * element_size);
|
||||
std::memmove(new_pool, game::DB_XAssetPool[type], game::g_poolSize[type] * element_size);
|
||||
return asset_type_sizes[type];
|
||||
}
|
||||
|
||||
game::DB_XAssetPool[type] = new_pool;
|
||||
game::g_poolSize[type] = new_size;
|
||||
template <game::XAssetType Type, size_t Size>
|
||||
char* reallocate_asset_pool()
|
||||
{
|
||||
constexpr auto element_size = get_asset_type_size(Type);
|
||||
static char new_pool[element_size * Size] = {0};
|
||||
assert(get_asset_type_size(Type) == game::DB_GetXAssetTypeSize(Type));
|
||||
|
||||
std::memmove(new_pool, game::DB_XAssetPool[Type], game::g_poolSize[Type] * element_size);
|
||||
|
||||
game::DB_XAssetPool[Type] = new_pool;
|
||||
game::g_poolSize[Type] = Size;
|
||||
|
||||
return new_pool;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -55,41 +78,33 @@ namespace fastfiles
|
||||
return;
|
||||
}
|
||||
|
||||
const char* name = params.get(1);
|
||||
game::DB_LoadXAssets(&name, 1u, game::DBSyncMode::DB_LOAD_SYNC);
|
||||
});
|
||||
|
||||
command::add("materiallist", [](const command::params& params)
|
||||
{
|
||||
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_MATERIAL, [](const game::XAssetHeader header, void*)
|
||||
{
|
||||
if(header.material && header.material->name)
|
||||
{
|
||||
printf("%s\n", header.material->name);
|
||||
}
|
||||
}, 0, false);
|
||||
});
|
||||
|
||||
command::add("fontlist", [](const command::params& params)
|
||||
{
|
||||
game::DB_EnumXAssets_FastFile(game::ASSET_TYPE_FONT, [](const game::XAssetHeader header, void*)
|
||||
{
|
||||
if (header.font && header.font->fontName)
|
||||
{
|
||||
printf("%s\n", header.font->fontName);
|
||||
}
|
||||
}, 0, false);
|
||||
game::XZoneInfo info{};
|
||||
info.name = params.get(1);
|
||||
info.allocFlags = 1;
|
||||
info.freeFlags = 0;
|
||||
game::DB_LoadXAssets(&info, 1u, game::DBSyncMode::DB_LOAD_SYNC);
|
||||
});
|
||||
|
||||
command::add("g_poolSizes", []()
|
||||
{
|
||||
for (auto i = 0; i < game::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
printf("g_poolSize[%i]: %i // %s\n", i, game::g_poolSize[i], game::g_assetNames[i]);
|
||||
game_console::print(game_console::con_type_info, "g_poolSize[%i]: %i // %s\n", i,
|
||||
game::g_poolSize[i], game::g_assetNames[i]);
|
||||
}
|
||||
});
|
||||
|
||||
reallocate_asset_pool(game::ASSET_TYPE_FONT, 48);
|
||||
reallocate_asset_pool<game::ASSET_TYPE_FONT, 48>();
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
const auto* xmodel_pool = reallocate_asset_pool<game::ASSET_TYPE_XMODEL, 8832>();
|
||||
utils::hook::inject(0x14026FD63, xmodel_pool + 8);
|
||||
utils::hook::inject(0x14026FDB3, xmodel_pool + 8);
|
||||
utils::hook::inject(0x14026FFAC, xmodel_pool + 8);
|
||||
utils::hook::inject(0x14027463C, xmodel_pool + 8);
|
||||
utils::hook::inject(0x140274689, xmodel_pool + 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -5,5 +5,4 @@
|
||||
namespace fastfiles
|
||||
{
|
||||
const char* get_current_fastfile();
|
||||
void reallocate_asset_pool(const game::XAssetType type, const unsigned int new_size);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace filesystem
|
||||
void post_unpack() override
|
||||
{
|
||||
// Set fs_basegame
|
||||
dvars::override::Dvar_RegisterString("fs_basegame", "S1x", 2048);
|
||||
dvars::override::Dvar_RegisterString("fs_basegame", "s1x", 2048);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -12,12 +12,12 @@ namespace fps
|
||||
{
|
||||
namespace
|
||||
{
|
||||
float fps_color_good[4] = { 0.6f, 1.0f, 0.0f, 1.0f };
|
||||
float fps_color_ok[4] = { 1.0f, 0.7f, 0.3f, 1.0f };
|
||||
float fps_color_bad[4] = { 1.0f, 0.3f, 0.3f, 1.0f };
|
||||
float fps_color_good[4] = {0.6f, 1.0f, 0.0f, 1.0f};
|
||||
float fps_color_ok[4] = {1.0f, 0.7f, 0.3f, 1.0f};
|
||||
float fps_color_bad[4] = {1.0f, 0.3f, 0.3f, 1.0f};
|
||||
|
||||
//float origin_color[4] = { 1.0f, 0.67f, 0.13f, 1.0f };
|
||||
float ping_color[4] = { 1.0f, 1.0f, 1.0f, 0.65f };
|
||||
float ping_color[4] = {1.0f, 1.0f, 1.0f, 0.65f};
|
||||
|
||||
struct cg_perf_data
|
||||
{
|
||||
@ -161,13 +161,15 @@ namespace fps
|
||||
// change cg_drawfps flags to saved
|
||||
utils::hook::call(SELECT_VALUE(0x1400EF951, 0x1401A4B8E), &cg_draw_fps_register_stub);
|
||||
|
||||
// fix ping value
|
||||
utils::hook::nop(0x140213031, 2);
|
||||
|
||||
scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer);
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, 0, "Choose to draw ping");
|
||||
scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -171,57 +171,7 @@ namespace game_console
|
||||
const auto _y = con.globals.font_height + con.globals.y + (con.globals.font_height * (line + 1)) + 15.0f;
|
||||
|
||||
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color,
|
||||
0);
|
||||
}
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact)
|
||||
{
|
||||
if (exact && text == input) return true;
|
||||
if (!exact && text.find(input) != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
|
||||
{
|
||||
input = utils::string::to_lower(input);
|
||||
|
||||
for (int i = 0; i < *game::dvarCount; i++)
|
||||
{
|
||||
if (game::sortedDvars[i] && game::sortedDvars[i]->name)
|
||||
{
|
||||
std::string name = utils::string::to_lower(game::sortedDvars[i]->name);
|
||||
|
||||
if (match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(game::sortedDvars[i]->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
std::string name = utils::string::to_lower(cmd->name);
|
||||
|
||||
if (match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(cmd->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
0);
|
||||
}
|
||||
|
||||
void draw_input()
|
||||
@ -379,7 +329,7 @@ namespace game_console
|
||||
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
|
||||
|
||||
game::R_AddCmdDrawText(game::Dvar_FindVar("version")->current.string, 0x7FFFFFFF, console_font, x,
|
||||
((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_s1, 0);
|
||||
((height - 12.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_s1, 0);
|
||||
|
||||
draw_output_scrollbar(x, y, width, height);
|
||||
draw_output_text(x, y);
|
||||
@ -408,7 +358,7 @@ namespace game_console
|
||||
|
||||
void print_internal(const char* fmt, ...)
|
||||
{
|
||||
char va_buffer[0x200] = { 0 };
|
||||
char va_buffer[0x200] = {0};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
@ -496,6 +446,7 @@ namespace game_console
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.display_line_offset = 0;
|
||||
con.output.clear();
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
@ -542,6 +493,11 @@ namespace game_console
|
||||
{
|
||||
if (key == game::keyNum_t::K_F10)
|
||||
{
|
||||
if (game::mp::svs_clients[localClientNum].header.state >= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
game::Cmd_ExecuteSingleCommand(localClientNum, 0, "lui_open menu_systemlink_join\n");
|
||||
}
|
||||
|
||||
@ -672,6 +628,56 @@ namespace game_console
|
||||
return true;
|
||||
}
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact)
|
||||
{
|
||||
if (exact && text == input) return true;
|
||||
if (!exact && text.find(input) != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
|
||||
{
|
||||
input = utils::string::to_lower(input);
|
||||
|
||||
for (int i = 0; i < *game::dvarCount; i++)
|
||||
{
|
||||
if (game::sortedDvars[i] && game::sortedDvars[i]->name)
|
||||
{
|
||||
std::string name = utils::string::to_lower(game::sortedDvars[i]->name);
|
||||
|
||||
if (game_console::match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(game::sortedDvars[i]->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
std::string name = utils::string::to_lower(cmd->name);
|
||||
|
||||
if (game_console::match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(cmd->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -713,6 +719,7 @@ namespace game_console
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.display_line_offset = 0;
|
||||
con.output.clear();
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
@ -747,7 +754,6 @@ namespace game_console
|
||||
dvars::con_inputCmdMatchColor = game::Dvar_RegisterVec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, 1, "color of console matched command");
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -14,4 +14,7 @@ namespace game_console
|
||||
|
||||
bool console_char_event(int local_client_num, int key);
|
||||
bool console_key_event(int local_client_num, int key, int down);
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact);
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact);
|
||||
}
|
||||
|
@ -18,12 +18,14 @@ namespace gameplay
|
||||
}
|
||||
|
||||
void cm_transformed_capsule_trace_stub(struct trace_t* results, const float* start, const float* end,
|
||||
struct Bounds* bounds, struct Bounds* capsule, int contents, const float* origin, const float* angles)
|
||||
struct Bounds* bounds, struct Bounds* capsule, int contents,
|
||||
const float* origin, const float* angles)
|
||||
{
|
||||
if (dvars::g_playerCollision->current.enabled)
|
||||
{
|
||||
reinterpret_cast<void(*)
|
||||
(struct trace_t*, const float*, const float*, struct Bounds*, struct Bounds*, unsigned int, const float*, const float*)>
|
||||
(struct trace_t*, const float*, const float*, struct Bounds*, struct Bounds*, unsigned int,
|
||||
const float*, const float*)>
|
||||
(0x1403AB1C0)
|
||||
(results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace
|
||||
}
|
||||
@ -62,18 +64,23 @@ namespace gameplay
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
// Implement player ejection dvar
|
||||
dvars::g_playerEjection = game::Dvar_RegisterBool("g_playerEjection", true, game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off");
|
||||
dvars::g_playerEjection = game::Dvar_RegisterBool("g_playerEjection", true, game::DVAR_FLAG_REPLICATED,
|
||||
"Flag whether player ejection is on or off");
|
||||
utils::hook::call(0x1402D5E4A, stuck_in_client_stub);
|
||||
|
||||
// Implement player collision dvar
|
||||
dvars::g_playerCollision = game::Dvar_RegisterBool("g_playerCollision", true, game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off");
|
||||
dvars::g_playerCollision = game::Dvar_RegisterBool("g_playerCollision", true, game::DVAR_FLAG_REPLICATED,
|
||||
"Flag whether player collision is on or off");
|
||||
utils::hook::call(0x1404563DA, cm_transformed_capsule_trace_stub); // SV_ClipMoveToEntity
|
||||
utils::hook::call(0x1401F7F8F, cm_transformed_capsule_trace_stub); // CG_ClipMoveToEntity
|
||||
|
||||
// Implement bouncing dvar
|
||||
utils::hook::jump(0x14014DF91, pm_bouncing_stub_mp, true);
|
||||
dvars::pm_bouncing = game::Dvar_RegisterBool("pm_bouncing", false,
|
||||
game::DVAR_FLAG_REPLICATED, "Enable bouncing");
|
||||
game::DVAR_FLAG_REPLICATED, "Enable bouncing");
|
||||
|
||||
// Change jump_slowdownEnable dvar flags to just "replicated"
|
||||
utils::hook::set<uint8_t>(0x140135992, game::DVAR_FLAG_REPLICATED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "localized_strings.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace localized_strings
|
||||
@ -11,37 +12,30 @@ namespace localized_strings
|
||||
{
|
||||
utils::hook::detour seh_string_ed_get_string_hook;
|
||||
|
||||
std::unordered_map<std::string, std::string>& get_localized_overrides()
|
||||
{
|
||||
static std::unordered_map<std::string, std::string> overrides;
|
||||
return overrides;
|
||||
}
|
||||
|
||||
std::mutex& get_synchronization_mutex()
|
||||
{
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
using localized_map = std::unordered_map<std::string, std::string>;
|
||||
utils::concurrency::container<localized_map> localized_overrides;
|
||||
|
||||
const char* seh_string_ed_get_string(const char* reference)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(get_synchronization_mutex());
|
||||
|
||||
auto& overrides = get_localized_overrides();
|
||||
const auto entry = overrides.find(reference);
|
||||
if (entry != overrides.end())
|
||||
return localized_overrides.access<const char*>([&](const localized_map& map)
|
||||
{
|
||||
return utils::string::va("%s", entry->second.data());
|
||||
}
|
||||
const auto entry = map.find(reference);
|
||||
if (entry != map.end())
|
||||
{
|
||||
return utils::string::va("%s", entry->second.data());
|
||||
}
|
||||
|
||||
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
|
||||
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void override(const std::string& key, const std::string& value)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(get_synchronization_mutex());
|
||||
get_localized_overrides()[key] = value;
|
||||
localized_overrides.access([&](localized_map& map)
|
||||
{
|
||||
map[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
|
@ -2,8 +2,6 @@
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "game_console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
@ -12,21 +10,69 @@ namespace logger
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void print_error(const char* msg)
|
||||
void print_error(const char* msg, ...)
|
||||
{
|
||||
game_console::print(game_console::con_type_error, msg);
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
game_console::print(game_console::con_type_error, buffer);
|
||||
}
|
||||
|
||||
void print_warning(const char* msg)
|
||||
void print_warning(const char* msg, ...)
|
||||
{
|
||||
game_console::print(game_console::con_type_warning, msg);
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
game_console::print(game_console::con_type_warning, buffer);
|
||||
}
|
||||
|
||||
void print(const char* msg)
|
||||
void print(const char* msg, ...)
|
||||
{
|
||||
game_console::print(game_console::con_type_info, msg);
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
game_console::print(game_console::con_type_info, buffer);
|
||||
}
|
||||
|
||||
|
||||
void print_dev(const char* msg, ...)
|
||||
{
|
||||
static auto* enabled =
|
||||
game::Dvar_RegisterBool("logger_dev", false, game::DVAR_FLAG_SAVED, "Print dev stuff");
|
||||
if (!enabled->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
game_console::print(game_console::con_type_info, buffer);
|
||||
}
|
||||
|
||||
// nullsub_56
|
||||
void nullsub_56()
|
||||
{
|
||||
@ -60,6 +106,18 @@ namespace logger
|
||||
utils::hook::call(0x1400DAE67, print_warning);
|
||||
utils::hook::call(0x1400DB019, print_warning);
|
||||
}
|
||||
|
||||
// sub_1400E7420
|
||||
void sub_1400E7420()
|
||||
{
|
||||
utils::hook::call(0x1400CEC1C, print_warning);
|
||||
utils::hook::call(0x1400D218E, print_warning);
|
||||
utils::hook::call(0x1400D2319, print_warning);
|
||||
utils::hook::call(0x1400D65BC, print_dev);
|
||||
utils::hook::call(0x1400D7C94, print_dev);
|
||||
utils::hook::call(0x1400DAB6A, print_dev);
|
||||
utils::hook::call(0x1400DB9BC, print_dev);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -70,8 +128,9 @@ namespace logger
|
||||
if (!game::environment::is_mp()) return;
|
||||
|
||||
nullsub_56();
|
||||
sub_1400E7420();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(logger::component)
|
||||
REGISTER_COMPONENT(logger::component)
|
||||
|
@ -49,4 +49,4 @@ namespace lui
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(lui::component)
|
||||
REGISTER_COMPONENT(lui::component)
|
||||
|
@ -28,7 +28,8 @@ namespace map_rotation
|
||||
void launch_default_map()
|
||||
{
|
||||
auto* mapname = game::Dvar_FindVar("mapname");
|
||||
if (mapname && mapname->current.string && strlen(mapname->current.string) && mapname->current.string != "mp_vlobby_room"s)
|
||||
if (mapname && mapname->current.string && strlen(mapname->current.string) && mapname->current.string !=
|
||||
"mp_vlobby_room"s)
|
||||
{
|
||||
launch_map(mapname->current.string);
|
||||
}
|
||||
@ -102,6 +103,12 @@ namespace map_rotation
|
||||
else if (key == "map")
|
||||
{
|
||||
store_new_rotation(rotation, i + 2);
|
||||
if (!game::SV_MapExists(value.data()))
|
||||
{
|
||||
printf("map_rotation: '%s' map doesn't exist!\n", value.data());
|
||||
launch_default_map();
|
||||
return;
|
||||
}
|
||||
launch_map(value);
|
||||
return;
|
||||
}
|
||||
|
@ -163,10 +163,10 @@ namespace network
|
||||
}
|
||||
|
||||
game::dvar_t* register_netport_stub(const char* dvarName, int value, int min, int max, unsigned int flags,
|
||||
const char* description)
|
||||
const char* description)
|
||||
{
|
||||
auto dvar = game::Dvar_RegisterInt("net_port", 27016, 0, 0xFFFFu, game::DVAR_FLAG_LATCHED, "Network port");
|
||||
|
||||
|
||||
// read net_port from command line
|
||||
command::read_startup_variable("net_port");
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "game_console.hpp"
|
||||
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
@ -26,10 +27,11 @@ namespace party
|
||||
} connect_state;
|
||||
|
||||
std::string sv_motd;
|
||||
|
||||
|
||||
void perform_game_initialization()
|
||||
{
|
||||
command::execute("onlinegame 1", true);
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("xblive_privatematch 1", true);
|
||||
command::execute("startentitlements", true);
|
||||
}
|
||||
@ -41,17 +43,35 @@ namespace party
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::Live_SyncOnlineDataFlags(0))
|
||||
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
||||
{
|
||||
scheduler::once([=]()
|
||||
// initialize the game after onlinedataflags is 32 (workaround)
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 32)
|
||||
{
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("disconnect", true); // 32 -> 0
|
||||
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
perform_game_initialization();
|
||||
|
||||
// exit from virtuallobby
|
||||
reinterpret_cast<void(*)()>(0x14020EB90)();
|
||||
|
||||
// CL_ConnectFromParty
|
||||
char session_info[0x100] = {};
|
||||
reinterpret_cast<void(*)(int, char*, const game::netadr_s*, const char*, const char*)>(0x140209360)(
|
||||
@ -110,7 +130,7 @@ namespace party
|
||||
{
|
||||
if (game::mp::g_entities[i].client)
|
||||
{
|
||||
char client_name[16] = { 0 };
|
||||
char client_name[16] = {0};
|
||||
strncpy_s(client_name, game::mp::g_entities[i].client->name, 16);
|
||||
game::I_CleanStr(client_name);
|
||||
|
||||
@ -174,9 +194,9 @@ namespace party
|
||||
|
||||
void start_map(const std::string& mapname)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
||||
if (game::Live_SyncOnlineDataFlags(0) > 32)
|
||||
{
|
||||
scheduler::on_game_initialized([mapname]()
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("map " + mapname, false);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
@ -185,24 +205,31 @@ namespace party
|
||||
{
|
||||
if (!game::SV_MapExists(mapname.data()))
|
||||
{
|
||||
printf("Map '%s' doesn't exist.", mapname.data());
|
||||
game_console::print(game_console::con_type_info, "Map '%s' doesn't exist.\n", mapname.data());
|
||||
return;
|
||||
}
|
||||
|
||||
auto* current_mapname = game::Dvar_FindVar("mapname");
|
||||
if (current_mapname && utils::string::to_lower(current_mapname->current.string) ==
|
||||
utils::string::to_lower(mapname) && (game::SV_Loaded() && !game::VirtualLobby_Loaded()))
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "Restarting map: %s\n", mapname.data());
|
||||
command::execute("map_restart", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
if (game::SV_Loaded())
|
||||
{
|
||||
const auto* args = "Leave";
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
}
|
||||
|
||||
perform_game_initialization();
|
||||
}
|
||||
|
||||
auto* current_mapname = game::Dvar_FindVar("mapname");
|
||||
if (current_mapname && utils::string::to_lower(current_mapname->current.string) == utils::string::to_lower(mapname) && game::SV_Loaded())
|
||||
{
|
||||
printf("Restarting map: %s\n", mapname.data());
|
||||
command::execute("map_restart", false);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Starting map: %s\n", mapname.data());
|
||||
game_console::print(game_console::con_type_info, "Starting map: %s\n", mapname.data());
|
||||
|
||||
auto* gametype = game::Dvar_FindVar("g_gametype");
|
||||
if (gametype && gametype->current.string)
|
||||
@ -211,11 +238,31 @@ namespace party
|
||||
}
|
||||
command::execute(utils::string::va("ui_mapname %s", mapname.data()), true);
|
||||
|
||||
// StartServer
|
||||
reinterpret_cast<void(*)(unsigned int)>(0x140492260)(0);
|
||||
/*auto* maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||
if (maxclients)
|
||||
{
|
||||
command::execute(utils::string::va("ui_maxclients %i", maxclients->current.integer), true);
|
||||
command::execute(utils::string::va("party_maxplayers %i", maxclients->current.integer), true);
|
||||
}*/
|
||||
|
||||
//game::SV_StartMapForParty(0, mapname.data(), false, false);
|
||||
//return;
|
||||
const auto* args = "StartServer";
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect_stub()
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
// CL_ForwardCommandToServer
|
||||
reinterpret_cast<void (*)(int, const char*)>(0x14020B310)(0, "disconnect");
|
||||
// CL_WritePacket
|
||||
reinterpret_cast<void (*)(int)>(0x1402058F0)(0);
|
||||
}
|
||||
// CL_Disconnect
|
||||
reinterpret_cast<void (*)(int)>(0x140209EC0)(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,6 +276,9 @@ namespace party
|
||||
return;
|
||||
}
|
||||
|
||||
// hook disconnect command function
|
||||
utils::hook::jump(0x14020A010, disconnect_stub);
|
||||
|
||||
command::add("map", [](const command::params& argument)
|
||||
{
|
||||
if (argument.size() != 2)
|
||||
@ -241,7 +291,7 @@ namespace party
|
||||
|
||||
command::add("map_restart", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -253,7 +303,7 @@ namespace party
|
||||
|
||||
command::add("fast_restart", []()
|
||||
{
|
||||
if (game::SV_Loaded())
|
||||
if (game::SV_Loaded() && !game::VirtualLobby_Loaded())
|
||||
{
|
||||
game::SV_FastRestart(0);
|
||||
}
|
||||
@ -277,7 +327,7 @@ namespace party
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
printf("usage: kickClient <num>\n");
|
||||
game_console::print(game_console::con_type_info, "usage: kickClient <num>\n");
|
||||
return;
|
||||
}
|
||||
const auto client_num = atoi(params.get(1));
|
||||
@ -293,7 +343,7 @@ namespace party
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
printf("usage: kick <name>\n");
|
||||
game_console::print(game_console::con_type_info, "usage: kick <name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -348,7 +398,8 @@ namespace party
|
||||
const auto client_num = atoi(params.get(1));
|
||||
const auto message = params.join(2);
|
||||
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
printf("%i: %s\n", client_num, message.data());
|
||||
});
|
||||
|
||||
@ -376,7 +427,8 @@ namespace party
|
||||
|
||||
const auto message = params.join(1);
|
||||
|
||||
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
printf("%s\n", message.data());
|
||||
});
|
||||
|
||||
|
@ -12,29 +12,12 @@
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
namespace patches
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* register_virtual_lobby_enabled_stub(const char* name, bool /*value*/,
|
||||
unsigned int /*flags*/,
|
||||
const char* description)
|
||||
{
|
||||
return game::Dvar_RegisterBool(name, false, game::DVAR_FLAG_READ, description);
|
||||
}
|
||||
|
||||
game::dvar_t* register_virtual_lobby_stubs(const char* name, bool value,
|
||||
unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_CORE)
|
||||
{
|
||||
value = true;
|
||||
flags = game::DVAR_FLAG_READ;
|
||||
}
|
||||
return game::Dvar_RegisterBool(name, value, game::DVAR_FLAG_READ, description);
|
||||
}
|
||||
|
||||
utils::hook::detour live_get_local_client_name_hook;
|
||||
|
||||
const char* live_get_local_client_name()
|
||||
@ -44,14 +27,14 @@ namespace patches
|
||||
|
||||
utils::hook::detour sv_kick_client_num_hook;
|
||||
|
||||
void sv_kick_client_num(const int clientNum, const char* reason)
|
||||
void sv_kick_client_num(const int client_num, const char* reason)
|
||||
{
|
||||
// Don't kick bot to equalize team balance.
|
||||
if (reason == "EXE_PLAYERKICKED_BOT_BALANCE"s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return sv_kick_client_num_hook.invoke<void>(clientNum, reason);
|
||||
return sv_kick_client_num_hook.invoke<void>(client_num, reason);
|
||||
}
|
||||
|
||||
utils::hook::detour com_register_dvars_hook;
|
||||
@ -71,15 +54,15 @@ namespace patches
|
||||
}
|
||||
|
||||
game::dvar_t* register_com_maxfps_stub(const char* name, int /*value*/, int /*min*/, int /*max*/,
|
||||
const unsigned int /*flags*/,
|
||||
const char* description)
|
||||
const unsigned int /*flags*/,
|
||||
const char* description)
|
||||
{
|
||||
return game::Dvar_RegisterInt(name, 0, 0, 1000, game::DVAR_FLAG_SAVED, description);
|
||||
}
|
||||
|
||||
game::dvar_t* register_cg_fov_stub(const char* name, float value, float min, float /*max*/,
|
||||
const unsigned int flags,
|
||||
const char* description)
|
||||
const unsigned int /*flags*/,
|
||||
const char* description)
|
||||
{
|
||||
return game::Dvar_RegisterFloat(name, value, min, 160, game::DVAR_FLAG_SAVED, description);
|
||||
}
|
||||
@ -88,8 +71,7 @@ namespace patches
|
||||
unsigned int /*flags*/,
|
||||
const char* desc)
|
||||
{
|
||||
// changed max value from 2.0f -> 5.0f and min value from 0.5f -> 0.1f
|
||||
return game::Dvar_RegisterFloat(name, 1.0f, 0.1f, 5.0f, game::DVAR_FLAG_SAVED, desc);
|
||||
return game::Dvar_RegisterFloat(name, 1.0f, 0.2f, 5.0f, game::DVAR_FLAG_SAVED, desc);
|
||||
}
|
||||
|
||||
int dvar_command_patch() // game makes this return an int and compares with eax instead of al -_-
|
||||
@ -104,8 +86,8 @@ namespace patches
|
||||
{
|
||||
if (args.size() == 1)
|
||||
{
|
||||
const auto current = game::Dvar_ValueToString(dvar, dvar->current);
|
||||
const auto reset = game::Dvar_ValueToString(dvar, dvar->reset);
|
||||
const auto* const current = game::Dvar_ValueToString(dvar, dvar->current);
|
||||
const auto* const reset = game::Dvar_ValueToString(dvar, dvar->reset);
|
||||
game_console::print(game_console::con_type_info, "\"%s\" is: \"%s^7\" default: \"%s^7\"",
|
||||
dvar->name, current, reset);
|
||||
game_console::print(game_console::con_type_info, " %s\n",
|
||||
@ -140,7 +122,8 @@ namespace patches
|
||||
}
|
||||
|
||||
// DB_ReadRawFile
|
||||
return reinterpret_cast<const char*(*)(const char*, char*, int)>(SELECT_VALUE(0x140180E30, 0x140273080))(filename, buf, size);
|
||||
return reinterpret_cast<const char*(*)(const char*, char*, int)>(SELECT_VALUE(0x140180E30, 0x140273080))(
|
||||
filename, buf, size);
|
||||
}
|
||||
|
||||
void aim_assist_add_to_target_list(void* a1, void* a2)
|
||||
@ -153,7 +136,8 @@ namespace patches
|
||||
|
||||
void missing_content_error_stub(int /*mode*/, const char* /*message*/)
|
||||
{
|
||||
game::Com_Error(game::ERR_DROP, utils::string::va("MISSING FILE\n%s.ff", fastfiles::get_current_fastfile()));
|
||||
game::Com_Error(game::ERR_DROP,
|
||||
utils::string::va("MISSING FILE\n%s.ff", fastfiles::get_current_fastfile()));
|
||||
}
|
||||
|
||||
void bsp_sys_error_stub(const char* error, const char* arg1)
|
||||
@ -189,7 +173,7 @@ namespace patches
|
||||
// Unlock fps in main menu
|
||||
utils::hook::set<BYTE>(SELECT_VALUE(0x140144F5B, 0x140213C3B), 0xEB);
|
||||
|
||||
// Unlock fps
|
||||
// Unlock com_maxfps
|
||||
utils::hook::call(SELECT_VALUE(0x1402F8726, 0x1403CF8CA), register_com_maxfps_stub);
|
||||
|
||||
// Unlock cg_fov
|
||||
@ -214,6 +198,10 @@ namespace patches
|
||||
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
// Allow kbam input when gamepad is enabled
|
||||
utils::hook::nop(SELECT_VALUE(0x14013EF83, 0x140206DB3), 2);
|
||||
utils::hook::nop(SELECT_VALUE(0x14013CBAC, 0x140204710), 6);
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
patch_sp();
|
||||
@ -226,11 +214,6 @@ namespace patches
|
||||
|
||||
static void patch_mp()
|
||||
{
|
||||
// Disable virtualLobby
|
||||
utils::hook::call(0x1403CFDCC, register_virtual_lobby_enabled_stub); // virtualLobbyEnabled
|
||||
//utils::hook::call(0x14013E0C0, register_virtual_lobby_stubs); // virtualLobbyReady
|
||||
utils::hook::call(0x1403CFE6A, register_virtual_lobby_stubs); // virtualLobbyAllocated
|
||||
|
||||
// Use name dvar
|
||||
live_get_local_client_name_hook.create(0x1404D47F0, &live_get_local_client_name);
|
||||
|
||||
@ -245,13 +228,17 @@ namespace patches
|
||||
|
||||
// client side aim assist dvar
|
||||
dvars::aimassist_enabled = game::Dvar_RegisterBool("aimassist_enabled", true,
|
||||
game::DvarFlags::DVAR_FLAG_SAVED,
|
||||
"Enables aim assist for controllers");
|
||||
game::DvarFlags::DVAR_FLAG_SAVED,
|
||||
"Enables aim assist for controllers");
|
||||
utils::hook::call(0x140003609, aim_assist_add_to_target_list);
|
||||
|
||||
// unlock all items
|
||||
utils::hook::jump(0x1403BD790, is_item_unlocked); // LiveStorage_IsItemUnlockedFromTable_LocalClient
|
||||
utils::hook::jump(0x1403BD290, is_item_unlocked); // LiveStorage_IsItemUnlockedFromTable
|
||||
utils::hook::jump(0x1403BAF60, is_item_unlocked); // idk ( unlocks loot etc )
|
||||
|
||||
// isProfanity
|
||||
utils::hook::set(0x14023BDC0, 0xC3C033);
|
||||
|
||||
// disable emblems
|
||||
dvars::override::Dvar_RegisterInt("emblems_active", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
@ -263,11 +250,16 @@ namespace patches
|
||||
|
||||
// disable codPointStore
|
||||
dvars::override::Dvar_RegisterInt("codPointStore_enabled", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
|
||||
// don't register every replicated dvar as a network dvar
|
||||
utils::hook::nop(0x1403534BE, 5); // dvar_foreach
|
||||
|
||||
// patch "Server is different version" to show the server client version
|
||||
utils::hook::inject(0x1404398B2, VERSION);
|
||||
}
|
||||
|
||||
static void patch_sp()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ namespace redirect
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
|
||||
auto* arguments = const_cast<char*>(utils::string::va("%s%s%s", self.get_path().data(),
|
||||
(singleplayer ? " -singleplayer" : " -multiplayer"),
|
||||
(mode.empty() ? "" : (" +"s + mode).data())));
|
||||
(singleplayer ? " -singleplayer" : " -multiplayer"),
|
||||
(mode.empty() ? "" : (" +"s + mode).data())));
|
||||
CreateProcessA(self.get_path().data(), arguments, nullptr, nullptr, false, NULL, nullptr, nullptr,
|
||||
&startup_info, &process_info);
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace renderer
|
||||
{
|
||||
@ -26,7 +25,7 @@ namespace renderer
|
||||
}
|
||||
}
|
||||
|
||||
void r_init_draw_method_stub()
|
||||
void gfxdrawmethod()
|
||||
{
|
||||
game::gfxDrawMethod->drawScene = game::GFX_DRAW_SCENE_STANDARD;
|
||||
game::gfxDrawMethod->baseTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_LIT;
|
||||
@ -34,17 +33,19 @@ namespace renderer
|
||||
game::gfxDrawMethod->forceTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : 182;
|
||||
}
|
||||
|
||||
void r_init_draw_method_stub()
|
||||
{
|
||||
gfxdrawmethod();
|
||||
}
|
||||
|
||||
bool r_update_front_end_dvar_options_stub()
|
||||
{
|
||||
if (dvars::r_fullbright->modified)
|
||||
{
|
||||
dvars::r_fullbright->modified = false;
|
||||
game::Dvar_ClearModified(dvars::r_fullbright);
|
||||
game::R_SyncRenderThread();
|
||||
|
||||
game::gfxDrawMethod->drawScene = game::GFX_DRAW_SCENE_STANDARD;
|
||||
game::gfxDrawMethod->baseTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_LIT;
|
||||
game::gfxDrawMethod->emissiveTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_EMISSIVE;
|
||||
game::gfxDrawMethod->forceTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : 182;
|
||||
gfxdrawmethod();
|
||||
}
|
||||
|
||||
return r_update_front_end_dvar_options_hook.invoke<bool>();
|
||||
@ -65,6 +66,15 @@ namespace renderer
|
||||
|
||||
r_init_draw_method_hook.create(SELECT_VALUE(0x14046C150, 0x140588B00), &r_init_draw_method_stub);
|
||||
r_update_front_end_dvar_options_hook.create(SELECT_VALUE(0x1404A5330, 0x1405C3AE0), &r_update_front_end_dvar_options_stub);
|
||||
|
||||
// use "saved" flags for "r_normalMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x14047E0B8, 0x14059AD71), game::DVAR_FLAG_SAVED);
|
||||
|
||||
// use "saved" flags for "r_specularMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x14047E0DA, 0x14059AD99), game::DVAR_FLAG_SAVED);
|
||||
|
||||
// use "saved" flags for "r_specOccMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x14047E0FC, 0x14059ADC1), game::DVAR_FLAG_SAVED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ namespace scheduler
|
||||
{
|
||||
schedule([=]()
|
||||
{
|
||||
const auto dw_init = game::Live_SyncOnlineDataFlags(0) == 0;
|
||||
const auto dw_init = game::environment::is_sp() ? true : game::Live_SyncOnlineDataFlags(0) == 0;
|
||||
if (dw_init && game::Sys_IsDatabaseReady2())
|
||||
{
|
||||
once(callback, type, delay);
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "game_console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
@ -44,7 +42,7 @@ namespace server_list
|
||||
|
||||
size_t server_list_index = 0;
|
||||
int server_list_page = 0;
|
||||
|
||||
|
||||
volatile bool update_server_list = false;
|
||||
|
||||
void refresh_server_list()
|
||||
@ -98,12 +96,12 @@ namespace server_list
|
||||
update_server_list = false;
|
||||
return 0;
|
||||
}
|
||||
auto count = static_cast<int>(servers.size());
|
||||
const auto count = static_cast<int>(servers.size());
|
||||
return count > 15 ? 15 : count;
|
||||
}
|
||||
|
||||
const char* ui_feeder_item_text(int /*localClientNum*/, void* /*a2*/, void* /*a3*/, const size_t index,
|
||||
const size_t column)
|
||||
const char* ui_feeder_item_text(int /*localClientNum*/, void* /*a2*/, void* /*a3*/, const int index,
|
||||
const int column)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
@ -126,12 +124,22 @@ namespace server_list
|
||||
|
||||
if (column == 2)
|
||||
{
|
||||
return utils::string::va("%d/%d [%d]", servers[i].clients, servers[index].max_clients, servers[i].bots);
|
||||
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
|
||||
}
|
||||
|
||||
if (column == 3)
|
||||
{
|
||||
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
|
||||
auto num_spaces = 20;
|
||||
if (servers[i].clients >= 10) num_spaces -= 2;
|
||||
if (servers[i].max_clients >= 10) num_spaces -= 2;
|
||||
if (servers[i].bots >= 10) num_spaces -= 2;
|
||||
std::string spaces;
|
||||
while (num_spaces > 0)
|
||||
{
|
||||
spaces.append(" ");
|
||||
num_spaces--;
|
||||
}
|
||||
return utils::string::va("%d/%d [%d]%s%d", servers[i].clients, servers[index].max_clients, servers[i].bots, spaces.data(), servers[i].ping);
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -141,7 +149,7 @@ namespace server_list
|
||||
{
|
||||
std::stable_sort(servers.begin(), servers.end(), [](const server_info& a, const server_info& b)
|
||||
{
|
||||
if(a.clients == b.clients)
|
||||
if (a.clients == b.clients)
|
||||
{
|
||||
return a.ping < b.ping;
|
||||
}
|
||||
@ -255,7 +263,7 @@ namespace server_list
|
||||
}
|
||||
|
||||
void lui_open_menu_stub(int /*controllerIndex*/, const char* /*menu*/, int /*a3*/, int /*a4*/,
|
||||
unsigned int /*a5*/)
|
||||
unsigned int /*a5*/)
|
||||
{
|
||||
refresh_server_list();
|
||||
game::Cmd_ExecuteSingleCommand(0, 0, "lui_open menu_systemlink_join\n");
|
||||
@ -288,14 +296,14 @@ namespace server_list
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
|
||||
{
|
||||
// Don't show servers that aren't running!
|
||||
auto sv_running = std::atoi(info.get("sv_running").data());
|
||||
const auto sv_running = std::atoi(info.get("sv_running").data());
|
||||
if (!sv_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle servers of the same playmode!
|
||||
auto playmode = game::CodPlayMode(std::atoi(info.get("playmode").data()));
|
||||
const auto playmode = game::CodPlayMode(std::atoi(info.get("playmode").data()));
|
||||
if (game::Com_GetCurrentCoDPlayMode() != playmode)
|
||||
{
|
||||
return;
|
||||
@ -346,7 +354,9 @@ namespace server_list
|
||||
localized_strings::override("LUA_MENU_STORE", "Server List");
|
||||
localized_strings::override("LUA_MENU_STORE_DESC", "Browse available servers.");
|
||||
|
||||
localized_strings::override("MENU_NUMPLAYERS", "Players");
|
||||
// shitty ping workaround
|
||||
localized_strings::override("MENU_NUMPLAYERS", "Type");
|
||||
localized_strings::override("MENU_TYPE1", "Players"s + " " + "Ping");
|
||||
|
||||
// hook LUI_OpenMenu to show server list instead of store popup
|
||||
utils::hook::call(0x1404D5550, &lui_open_menu_stub);
|
||||
|
53
src/client/component/slowmotion.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace slowmotion
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void scr_cmd_set_slow_motion()
|
||||
{
|
||||
if (game::Scr_GetNumParam() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int duration = 1000;
|
||||
float end = 1.0f;
|
||||
const float start = game::Scr_GetFloat(0);
|
||||
|
||||
if (game::Scr_GetNumParam() >= 2)
|
||||
{
|
||||
end = game::Scr_GetFloat(1u);
|
||||
}
|
||||
|
||||
if (game::Scr_GetNumParam() >= 3)
|
||||
{
|
||||
duration = static_cast<int>(game::Scr_GetFloat(2u) * 1000.0f);
|
||||
}
|
||||
|
||||
game::SV_SetConfigstring(10, utils::string::va("%i %i %g %g", *game::mp::gameTime, duration, start, end));
|
||||
game::Com_SetSlowMotion(start, end, duration);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::jump(0x14030EF90, scr_cmd_set_slow_motion);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(slowmotion::component)
|
64
src/client/component/stats.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "game_console.hpp"
|
||||
|
||||
namespace stats
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
command::add("setPlayerDataInt", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "usage: setPlayerDataInt <data>, <value>");
|
||||
return;
|
||||
}
|
||||
|
||||
// SL_FindString
|
||||
const auto lookup_string = game::SL_FindString(params.get(1));
|
||||
const auto value = atoi(params.get(2));
|
||||
|
||||
// SetPlayerDataInt
|
||||
reinterpret_cast<void(*)(signed int, unsigned int, unsigned int, unsigned int)>(0x1403BF550)(
|
||||
0, lookup_string, value, 0);
|
||||
});
|
||||
|
||||
command::add("getPlayerDataInt", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "usage: getPlayerDataInt <data>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// SL_FindString
|
||||
const auto lookup_string = game::SL_FindString(params.get(1));
|
||||
|
||||
// GetPlayerDataInt
|
||||
const auto result = reinterpret_cast<int(*)(signed int, unsigned int, unsigned int)>(0x1403BE860)(
|
||||
0, lookup_string, 0);
|
||||
game_console::print(game_console::con_type_info, "%d\n", result);
|
||||
});
|
||||
|
||||
command::add("unlockstats", []()
|
||||
{
|
||||
command::execute("setPlayerDataInt prestige 30");
|
||||
command::execute("setPlayerDataInt experience 1002100");
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(stats::component)
|
@ -17,7 +17,7 @@ namespace steam_proxy
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::binary_resource runner_file(RUNNER, "runner.exe");
|
||||
utils::binary_resource runner_file(RUNNER, "s1x-runner.exe");
|
||||
|
||||
bool is_disabled()
|
||||
{
|
||||
@ -40,14 +40,14 @@ namespace steam_proxy
|
||||
this->clean_up_on_error();
|
||||
|
||||
#ifndef DEV_BUILD
|
||||
try
|
||||
{
|
||||
this->start_mod("\xF0\x9F\x90\xA4" " S1x: "s + (game::environment::is_sp() ? "Singleplayer" : "Multiplayer"), game::environment::is_sp() ? 209650 : 209660);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Steam: %s\n", e.what());
|
||||
}
|
||||
try
|
||||
{
|
||||
this->start_mod("\xF0\x9F\x94\xB1" " S1x: "s + game::environment::get_string(), game::environment::is_sp() ? 209650 : 209660);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Steam: %s\n", e.what());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
46
src/client/component/survival.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <game/game.hpp>
|
||||
|
||||
namespace survival
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char* get_commandline_stub()
|
||||
{
|
||||
static std::string commandline{};
|
||||
if (commandline.empty())
|
||||
{
|
||||
commandline = GetCommandLineA();
|
||||
|
||||
const auto real_mode = game::environment::get_real_mode();
|
||||
if (real_mode == launcher::mode::survival)
|
||||
{
|
||||
commandline += " +survival 01";
|
||||
}
|
||||
else if (real_mode == launcher::mode::zombies)
|
||||
{
|
||||
commandline += " +zombiesMode 01";
|
||||
}
|
||||
}
|
||||
|
||||
return commandline.data();
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (function == "GetCommandLineA")
|
||||
{
|
||||
return get_commandline_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(survival::component)
|
89
src/client/component/system_check.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "system_check.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace system_check
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string read_zone(const std::string& name)
|
||||
{
|
||||
std::string data{};
|
||||
if (utils::io::read_file(name, &data))
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
if (utils::io::read_file("zone/" + name, &data))
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string hash_zone(const std::string& name)
|
||||
{
|
||||
const auto data = read_zone(name);
|
||||
return utils::cryptography::sha256::compute(data, true);
|
||||
}
|
||||
|
||||
bool verify_hashes(const std::unordered_map<std::string, std::string>& zone_hashes)
|
||||
{
|
||||
for (const auto& zone_hash : zone_hashes)
|
||||
{
|
||||
const auto hash = hash_zone(zone_hash.first);
|
||||
if (hash != zone_hash.second)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_system_valid()
|
||||
{
|
||||
static std::unordered_map<std::string, std::string> mp_zone_hashes =
|
||||
{
|
||||
{"patch_common_mp.ff", "23B15B4EF0AC9B52B3C6F9F681290B25B6B24B49F17238076A3D7F3CCEF9A0E1"},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, std::string> sp_zone_hashes =
|
||||
{
|
||||
// Steam doesn't necessarily deliver this file :(
|
||||
//{"patch_common.ff", "4624A974C6C7F8BECD9C343E7951722D8378889AC08ED4F2B22459B171EC553C"},
|
||||
{"patch_common_zm_mp.ff", "DA16B546B7233BBC4F48E1E9084B49218CB9271904EA7120A0EB4CB8723C19CF"},
|
||||
};
|
||||
|
||||
return verify_hashes(mp_zone_hashes) && (game::environment::is_dedi() || verify_hashes(sp_zone_hashes));
|
||||
}
|
||||
}
|
||||
|
||||
bool is_valid()
|
||||
{
|
||||
static auto valid = is_system_valid();
|
||||
return valid;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (!is_valid())
|
||||
{
|
||||
MessageBoxA(nullptr, "Your game files are outdated or unsupported.\n"
|
||||
"Please get the latest officially supported Call of Duty: Advanced Warfare files, or you will get random crashes and issues.",
|
||||
"Invalid game files!", MB_ICONINFORMATION);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(system_check::component)
|
6
src/client/component/system_check.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace system_check
|
||||
{
|
||||
bool is_valid();
|
||||
}
|
60
src/client/component/thread_names.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
namespace thread_names
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void set_thread_names()
|
||||
{
|
||||
static std::unordered_map<int, std::string> thread_names =
|
||||
{
|
||||
{game::THREAD_CONTEXT_MAIN, "Main"},
|
||||
{game::THREAD_CONTEXT_BACKEND, "Backend"}, // Renderer
|
||||
{game::THREAD_CONTEXT_WORKER0, "Worker0"},
|
||||
{game::THREAD_CONTEXT_WORKER1, "Worker1"},
|
||||
{game::THREAD_CONTEXT_WORKER2, "Worker2"},
|
||||
{game::THREAD_CONTEXT_WORKER3, "Worker3"},
|
||||
{game::THREAD_CONTEXT_WORKER4, "Worker4"},
|
||||
{game::THREAD_CONTEXT_WORKER5, "Worker5"},
|
||||
{game::THREAD_CONTEXT_WORKER6, "Worker6"},
|
||||
{game::THREAD_CONTEXT_WORKER7, "Worker7"},
|
||||
{game::THREAD_CONTEXT_SERVER, "Server"},
|
||||
{game::THREAD_CONTEXT_CINEMATIC, "Cinematic"},
|
||||
{game::THREAD_CONTEXT_DATABASE, "Database"},
|
||||
{game::THREAD_CONTEXT_STREAM, "Stream"},
|
||||
{game::THREAD_CONTEXT_SNDSTREAMPACKETCALLBACK, "Snd stream packet callback"},
|
||||
{game::THREAD_CONTEXT_STATS_WRITE, "Stats write"},
|
||||
};
|
||||
|
||||
for (const auto& thread_name : thread_names)
|
||||
{
|
||||
const auto id = game::threadIds[thread_name.first];
|
||||
if (id)
|
||||
{
|
||||
utils::thread::set_name(id, thread_name.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
set_thread_names();
|
||||
scheduler::once(set_thread_names, scheduler::pipeline::main);
|
||||
scheduler::once(set_thread_names, scheduler::pipeline::renderer);
|
||||
scheduler::once(set_thread_names, scheduler::pipeline::server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(thread_names::component)
|
135
src/client/component/updater.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/toast.hpp>
|
||||
#include <utils/binary_resource.hpp>
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
#define APPVEYOR_ARTIFACT_BASE "https://master.xlabs.dev:444"
|
||||
#define APPVEYOR_PROJECT "s1x"
|
||||
#define APPVEYOR_BRANCH GIT_BRANCH
|
||||
|
||||
#ifdef DEBUG
|
||||
#define APPVEYOR_CONFIGURATION "Debug"
|
||||
#else
|
||||
#define APPVEYOR_CONFIGURATION "Release"
|
||||
#endif
|
||||
|
||||
#define APPVEYOR_ARTIFACT_URL(artifact) (APPVEYOR_ARTIFACT_BASE "/" APPVEYOR_PROJECT "/" APPVEYOR_BRANCH "/" APPVEYOR_CONFIGURATION "/" artifact)
|
||||
|
||||
#define APPVEYOR_S1X_EXE APPVEYOR_ARTIFACT_URL("build/bin/x64/" APPVEYOR_CONFIGURATION "/" APPVEYOR_PROJECT ".exe")
|
||||
#define APPVEYOR_VERSION_TXT APPVEYOR_ARTIFACT_URL("build/version.txt")
|
||||
|
||||
namespace updater
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::binary_resource s1x_icon(ICON_IMAGE, "s1x-icon.png");
|
||||
|
||||
std::string download_file_sync(const std::string& url)
|
||||
{
|
||||
CComPtr<IStream> stream;
|
||||
|
||||
if (FAILED(URLOpenBlockingStreamA(nullptr, url.data(), &stream, 0, nullptr)))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
char buffer[0x1000];
|
||||
std::string result;
|
||||
|
||||
HRESULT status{};
|
||||
|
||||
do
|
||||
{
|
||||
DWORD bytes_read = 0;
|
||||
status = stream->Read(buffer, sizeof(buffer), &bytes_read);
|
||||
|
||||
if (bytes_read > 0)
|
||||
{
|
||||
result.append(buffer, bytes_read);
|
||||
}
|
||||
}
|
||||
while (SUCCEEDED(status) && status != S_FALSE);
|
||||
|
||||
if (FAILED(status))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_update_available()
|
||||
{
|
||||
const auto version = download_file_sync(APPVEYOR_VERSION_TXT);
|
||||
return !version.empty() && version != GIT_HASH;
|
||||
}
|
||||
|
||||
void perform_update(const std::string& target)
|
||||
{
|
||||
const auto binary = download_file_sync(APPVEYOR_S1X_EXE);
|
||||
utils::io::write_file(target, binary);
|
||||
}
|
||||
|
||||
void delete_old_file(const std::string& file)
|
||||
{
|
||||
// Wait for other process to die
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
{
|
||||
utils::io::remove_file(file);
|
||||
|
||||
if (utils::io::file_exists(file))
|
||||
{
|
||||
std::this_thread::sleep_for(2s);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool try_updating()
|
||||
{
|
||||
const utils::nt::library self;
|
||||
const auto self_file = self.get_path();
|
||||
const auto dead_file = self_file + ".old";
|
||||
|
||||
delete_old_file(dead_file);
|
||||
|
||||
if (!is_update_available())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto info = utils::toast::show("Updating S1x", "Please wait...", s1x_icon.get_extracted_file());
|
||||
|
||||
utils::io::move_file(self_file, dead_file);
|
||||
perform_update(self_file);
|
||||
utils::nt::relaunch_self();
|
||||
|
||||
info.hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_start() override
|
||||
{
|
||||
if (try_updating())
|
||||
{
|
||||
component_loader::trigger_premature_shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(CI) && !defined(DEBUG)
|
||||
REGISTER_COMPONENT(updater::component)
|
||||
#endif
|
@ -1,6 +1,5 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "videos.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
@ -8,54 +7,22 @@
|
||||
|
||||
namespace videos
|
||||
{
|
||||
class video_replace
|
||||
{
|
||||
public:
|
||||
std::string replace;
|
||||
std::string with;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
T* find_vid(std::vector<T>* vec, const std::string& name)
|
||||
{
|
||||
for (auto i = 0ull; i < vec->size(); i++)
|
||||
{
|
||||
if (name == vec->at(i).replace)
|
||||
{
|
||||
return &vec->at(i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<video_replace> replaces;
|
||||
|
||||
void replace(const std::string& what, const std::string& with)
|
||||
{
|
||||
video_replace vid;
|
||||
vid.replace = what;
|
||||
vid.with = with;
|
||||
replaces.push_back(std::move(vid));
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour playvid_hook;
|
||||
std::unordered_map<std::string, std::string> video_replaces;
|
||||
|
||||
void playvid(const char* name, int a2, int a3)
|
||||
void playvid(const char* name, const int a2, const int a3)
|
||||
{
|
||||
auto* vid = find_vid(&replaces, name);
|
||||
if (vid)
|
||||
const auto vid = video_replaces.find(name);
|
||||
if (vid != video_replaces.end())
|
||||
{
|
||||
char path[256];
|
||||
game::Sys_BuildAbsPath(path, 256, game::SF_VIDEO, vid->with.data(), ".bik");
|
||||
game::Sys_BuildAbsPath(path, 256, game::SF_VIDEO, vid->second.data(), ".bik");
|
||||
|
||||
if (game::Sys_FileExists(path))
|
||||
{
|
||||
name = vid->with.data();
|
||||
name = vid->second.data();
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,17 +39,17 @@ namespace videos
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
replace("menus_bg_comp2", "menus_bg_s1x");
|
||||
replace("mp_menus_bg_options", "menus_bg_s1x_blur");
|
||||
video_replaces["menus_bg_comp2"] = "menus_bg_s1x";
|
||||
video_replaces["mp_menus_bg_options"] = "menus_bg_s1x_blur";
|
||||
}
|
||||
else if (game::environment::is_sp())
|
||||
{
|
||||
replace("sp_menus_bg_main_menu", "menus_bg_s1x_sp");
|
||||
replace("sp_menus_bg_campaign", "menus_bg_s1x_sp");
|
||||
replace("sp_menus_bg_options", "menus_bg_s1x_sp");
|
||||
video_replaces["sp_menus_bg_main_menu"] = "menus_bg_s1x_sp";
|
||||
video_replaces["sp_menus_bg_campaign"] = "menus_bg_s1x_sp";
|
||||
video_replaces["sp_menus_bg_options"] = "menus_bg_s1x_sp";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//REGISTER_COMPONENT(videos::component)
|
||||
REGISTER_COMPONENT(videos::component)
|
||||
|
@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace videos
|
||||
{
|
||||
void replace(const std::string& what, const std::string& with);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
#include <std_include.hpp>
|
||||
#include "demonware.hpp"
|
||||
#include "bit_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
@ -100,7 +100,7 @@ namespace demonware
|
||||
auto cur_out = 0;
|
||||
|
||||
const char* bytes = this->buffer_.data();
|
||||
const auto output_bytes = reinterpret_cast<unsigned char*>(output);
|
||||
const auto output_bytes = static_cast<unsigned char*>(output);
|
||||
|
||||
while (bits > 0)
|
||||
{
|
||||
@ -133,7 +133,7 @@ namespace demonware
|
||||
|
||||
int bit = bits;
|
||||
const auto bytes = const_cast<char*>(this->buffer_.data());
|
||||
const auto* input_bytes = reinterpret_cast<const unsigned char*>(data);
|
||||
const auto* input_bytes = static_cast<const unsigned char*>(data);
|
||||
|
||||
while (bit > 0)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include <std_include.hpp>
|
||||
#include "demonware.hpp"
|
||||
#include "byte_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
@ -132,7 +132,7 @@ namespace demonware
|
||||
}
|
||||
|
||||
bool byte_buffer::read_array_header(const unsigned char expected, unsigned int* element_count,
|
||||
unsigned int* element_size)
|
||||
unsigned int* element_size)
|
||||
{
|
||||
if (element_count) *element_count = 0;
|
||||
if (element_size) *element_size = 0;
|
||||
@ -237,7 +237,7 @@ namespace demonware
|
||||
}
|
||||
|
||||
bool byte_buffer::write_array_header(const unsigned char type, const unsigned int element_count,
|
||||
const unsigned int element_size)
|
||||
const unsigned int element_size)
|
||||
{
|
||||
const auto using_types = this->is_using_data_types();
|
||||
this->set_use_data_types(false);
|
||||
@ -266,7 +266,7 @@ namespace demonware
|
||||
|
||||
bool byte_buffer::write(const int bytes, const void* data)
|
||||
{
|
||||
this->buffer_.append(reinterpret_cast<const char*>(data), bytes);
|
||||
this->buffer_.append(static_cast<const char*>(data), bytes);
|
||||
this->current_byte_ += bytes;
|
||||
return true;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace demonware
|
||||
bool read_data_type(char expected);
|
||||
|
||||
bool read_array_header(unsigned char expected, unsigned int* element_count,
|
||||
unsigned int* element_size = nullptr);
|
||||
unsigned int* element_size = nullptr);
|
||||
|
||||
bool write_byte(char data);
|
||||
bool write_bool(bool data);
|
||||
|
@ -1,14 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "byte_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
class bdTaskResult
|
||||
{
|
||||
public:
|
||||
virtual ~bdTaskResult() = default;
|
||||
virtual void serialize(byte_buffer*) { }
|
||||
virtual void deserialize(byte_buffer*) { }
|
||||
|
||||
virtual void serialize(byte_buffer*)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void deserialize(byte_buffer*)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class bdFileData final : public bdTaskResult
|
||||
@ -134,5 +141,4 @@ namespace demonware
|
||||
buffer->read_string(&this->timezone);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
#include <std_include.hpp>
|
||||
#include "demonware.hpp"
|
@ -1,130 +1,130 @@
|
||||
#include <std_include.hpp>
|
||||
#include "demonware.hpp"
|
||||
#include "keys.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
struct data_t
|
||||
{
|
||||
char m_session_key[24];
|
||||
char m_response[8];
|
||||
char m_hmac_key[20];
|
||||
char m_enc_key[16];
|
||||
char m_dec_key[16];
|
||||
}data{};
|
||||
struct data_t
|
||||
{
|
||||
char m_session_key[24];
|
||||
char m_response[8];
|
||||
char m_hmac_key[20];
|
||||
char m_enc_key[16];
|
||||
char m_dec_key[16];
|
||||
} data{};
|
||||
|
||||
std::string packet_buffer;
|
||||
std::string packet_buffer;
|
||||
|
||||
void calculate_hmacs_s1(const char* data_, unsigned int data_size, const char* key, unsigned int key_size, char* dst, unsigned int dst_size)
|
||||
{
|
||||
char buffer[64];
|
||||
unsigned int pos = 0;
|
||||
unsigned int out_offset = 0;
|
||||
char count = 1;
|
||||
std::string result;
|
||||
void calculate_hmacs_s1(const char* data_, const unsigned int data_size, const char* key,
|
||||
const unsigned int key_size,
|
||||
char* dst, const unsigned int dst_size)
|
||||
{
|
||||
char buffer[64];
|
||||
unsigned int pos = 0;
|
||||
unsigned int out_offset = 0;
|
||||
char count = 1;
|
||||
std::string result;
|
||||
|
||||
// buffer add key
|
||||
std::memcpy(&buffer[pos], key, key_size);
|
||||
pos += key_size;
|
||||
// buffer add key
|
||||
std::memcpy(&buffer[pos], key, key_size);
|
||||
pos += key_size;
|
||||
|
||||
// buffer add count
|
||||
buffer[pos] = count;
|
||||
pos++;
|
||||
// buffer add count
|
||||
buffer[pos] = count;
|
||||
pos++;
|
||||
|
||||
// calculate hmac
|
||||
unsigned int outlen = 20;
|
||||
result = utils::cryptography::hmac_sha1::process(std::string(buffer, pos), std::string(data_, data_size), &outlen);
|
||||
// calculate hmac
|
||||
result = utils::cryptography::hmac_sha1::compute(std::string(buffer, pos), std::string(data_, data_size));
|
||||
|
||||
// save output
|
||||
std::memcpy(dst, result.data(), std::min(20u, (dst_size - out_offset)));
|
||||
out_offset = 20;
|
||||
// save output
|
||||
std::memcpy(dst, result.data(), std::min(20u, (dst_size - out_offset)));
|
||||
out_offset = 20;
|
||||
|
||||
// second loop
|
||||
while (1)
|
||||
{
|
||||
// if we filled the output buffer, exit
|
||||
if (out_offset >= dst_size)
|
||||
break;
|
||||
// second loop
|
||||
while (true)
|
||||
{
|
||||
// if we filled the output buffer, exit
|
||||
if (out_offset >= dst_size)
|
||||
break;
|
||||
|
||||
// buffer add last result
|
||||
pos = 0;
|
||||
std::memcpy(&buffer[pos], result.data(), 20);
|
||||
pos += 20;
|
||||
// buffer add last result
|
||||
pos = 0;
|
||||
std::memcpy(&buffer[pos], result.data(), 20);
|
||||
pos += 20;
|
||||
|
||||
// buffer add key
|
||||
std::memcpy(&buffer[pos], key, key_size);
|
||||
pos += key_size;
|
||||
// buffer add key
|
||||
std::memcpy(&buffer[pos], key, key_size);
|
||||
pos += key_size;
|
||||
|
||||
// buffer add count
|
||||
count++;
|
||||
buffer[pos] = count;
|
||||
pos++;
|
||||
// buffer add count
|
||||
count++;
|
||||
buffer[pos] = count;
|
||||
pos++;
|
||||
|
||||
// calculate hmac
|
||||
unsigned int outlen_ = 20;
|
||||
result = utils::cryptography::hmac_sha1::process(std::string(buffer, pos), std::string(data_, data_size), &outlen_);
|
||||
// calculate hmac
|
||||
result = utils::cryptography::hmac_sha1::compute(std::string(buffer, pos), std::string(data_, data_size));
|
||||
|
||||
// save output
|
||||
std::memcpy(&((char*)dst)[out_offset], result.data(), std::min(20u, (dst_size - out_offset)));
|
||||
out_offset += 20;
|
||||
}
|
||||
}
|
||||
// save output
|
||||
std::memcpy(dst + out_offset, result.data(), std::min(20u, (dst_size - out_offset)));
|
||||
out_offset += 20;
|
||||
}
|
||||
}
|
||||
|
||||
void derive_keys_s1()
|
||||
{
|
||||
std::string out_1 = utils::cryptography::sha1::compute(packet_buffer); // out_1 size 20
|
||||
void derive_keys_s1()
|
||||
{
|
||||
const auto out_1 = utils::cryptography::sha1::compute(packet_buffer); // out_1 size 20
|
||||
|
||||
unsigned int len = 20;
|
||||
std::string data_3 = utils::cryptography::hmac_sha1::process(data.m_session_key, out_1, &len);
|
||||
auto data_3 = utils::cryptography::hmac_sha1::compute(data.m_session_key, out_1);
|
||||
|
||||
char out_2[16];
|
||||
calculate_hmacs_s1(data_3.data(), 20, "CLIENTCHAL", 10, out_2, 16);
|
||||
char out_2[16];
|
||||
calculate_hmacs_s1(data_3.data(), 20, "CLIENTCHAL", 10, out_2, 16);
|
||||
|
||||
char out_3[72];
|
||||
calculate_hmacs_s1(data_3.data(), 20, "BDDATA", 6, out_3, 72);
|
||||
char out_3[72];
|
||||
calculate_hmacs_s1(data_3.data(), 20, "BDDATA", 6, out_3, 72);
|
||||
|
||||
std::memcpy(data.m_response, &out_2[8], 8);
|
||||
std::memcpy(data.m_hmac_key, &out_3[20], 20);
|
||||
std::memcpy(data.m_dec_key, &out_3[40], 16);
|
||||
std::memcpy(data.m_enc_key, &out_3[56], 16);
|
||||
std::memcpy(data.m_response, &out_2[8], 8);
|
||||
std::memcpy(data.m_hmac_key, &out_3[20], 20);
|
||||
std::memcpy(data.m_dec_key, &out_3[40], 16);
|
||||
std::memcpy(data.m_enc_key, &out_3[56], 16);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[demonware] Response id: %s\n", utils::string::dump_hex(std::string(&out_2[8], 8)).data());
|
||||
printf("[demonware] Hash verify: %s\n", utils::string::dump_hex(std::string(&out_3[20], 20)).data());
|
||||
printf("[demonware] AES dec key: %s\n", utils::string::dump_hex(std::string(&out_3[40], 16)).data());
|
||||
printf("[demonware] AES enc key: %s\n", utils::string::dump_hex(std::string(&out_3[56], 16)).data());
|
||||
printf("[demonware] Bravo 6, going dark.\n");
|
||||
printf("[DW] Response id: %s\n", utils::string::dump_hex(std::string(&out_2[8], 8)).data());
|
||||
printf("[DW] Hash verify: %s\n", utils::string::dump_hex(std::string(&out_3[20], 20)).data());
|
||||
printf("[DW] AES dec key: %s\n", utils::string::dump_hex(std::string(&out_3[40], 16)).data());
|
||||
printf("[DW] AES enc key: %s\n", utils::string::dump_hex(std::string(&out_3[56], 16)).data());
|
||||
printf("[DW] Bravo 6, going dark.\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void queue_packet_to_hash(const std::string& packet)
|
||||
{
|
||||
packet_buffer.append(packet);
|
||||
}
|
||||
void queue_packet_to_hash(const std::string& packet)
|
||||
{
|
||||
packet_buffer.append(packet);
|
||||
}
|
||||
|
||||
void set_session_key(const std::string& key)
|
||||
{
|
||||
std::memcpy(data.m_session_key, key.data(), 24);
|
||||
}
|
||||
void set_session_key(const std::string& key)
|
||||
{
|
||||
std::memcpy(data.m_session_key, key.data(), 24);
|
||||
}
|
||||
|
||||
std::string get_decrypt_key()
|
||||
{
|
||||
return std::string(data.m_dec_key, 16);
|
||||
}
|
||||
std::string get_decrypt_key()
|
||||
{
|
||||
return std::string(data.m_dec_key, 16);
|
||||
}
|
||||
|
||||
std::string get_encrypt_key()
|
||||
{
|
||||
return std::string(data.m_enc_key, 16);
|
||||
}
|
||||
std::string get_encrypt_key()
|
||||
{
|
||||
return std::string(data.m_enc_key, 16);
|
||||
}
|
||||
|
||||
std::string get_hmac_key()
|
||||
{
|
||||
return std::string(data.m_hmac_key, 20);
|
||||
}
|
||||
std::string get_hmac_key()
|
||||
{
|
||||
return std::string(data.m_hmac_key, 20);
|
||||
}
|
||||
|
||||
std::string get_response_id()
|
||||
{
|
||||
return std::string(data.m_response, 8);
|
||||
}
|
||||
std::string get_response_id()
|
||||
{
|
||||
return std::string(data.m_response, 8);
|
||||
}
|
||||
}
|
||||
|
12
src/client/game/demonware/keys.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void derive_keys_s1();
|
||||
void queue_packet_to_hash(const std::string& packet);
|
||||
void set_session_key(const std::string& key);
|
||||
std::string get_decrypt_key();
|
||||
std::string get_encrypt_key();
|
||||
std::string get_hmac_key();
|
||||
std::string get_response_id();
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
#include <std_include.hpp>
|
||||
#include "demonware.hpp"
|
||||
#include "keys.hpp"
|
||||
#include "reply.hpp"
|
||||
#include "servers/service_server.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
std::string unencrypted_reply::data()
|
||||
{
|
||||
byte_buffer result;
|
||||
@ -26,9 +28,9 @@ namespace demonware
|
||||
byte_buffer enc_buffer;
|
||||
enc_buffer.set_use_data_types(false);
|
||||
|
||||
enc_buffer.write_uint32(static_cast<unsigned int>(this->buffer_.size())); // service data size CHECKTHIS!!
|
||||
enc_buffer.write_byte(this->type()); // TASK_REPLY type
|
||||
enc_buffer.write(this->buffer_); // service data
|
||||
enc_buffer.write_uint32(static_cast<unsigned int>(this->buffer_.size())); // service data size CHECKTHIS!!
|
||||
enc_buffer.write_byte(this->type()); // TASK_REPLY type
|
||||
enc_buffer.write(this->buffer_); // service data
|
||||
|
||||
auto aligned_data = enc_buffer.get_buffer();
|
||||
auto size = aligned_data.size();
|
||||
@ -39,10 +41,10 @@ namespace demonware
|
||||
std::string seed("\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED", 16);
|
||||
|
||||
// encrypt
|
||||
auto enc_data = utils::cryptography::aes::encrypt(aligned_data, seed, demonware::get_encrypt_key());
|
||||
const auto enc_data = utils::cryptography::aes::encrypt(aligned_data, seed, demonware::get_encrypt_key());
|
||||
|
||||
// header : encrypted service data : hash
|
||||
static std::int32_t msg_count = 0;
|
||||
static auto msg_count = 0;
|
||||
msg_count++;
|
||||
|
||||
byte_buffer response;
|
||||
@ -56,8 +58,7 @@ namespace demonware
|
||||
response.write(enc_data);
|
||||
|
||||
// hash entire packet and append end
|
||||
unsigned int outlen = 20;
|
||||
auto hash_data = utils::cryptography::hmac_sha1::process(response.get_buffer(), demonware::get_hmac_key(), &outlen);
|
||||
auto hash_data = utils::cryptography::hmac_sha1::compute(response.get_buffer(), demonware::get_hmac_key());
|
||||
hash_data.resize(8);
|
||||
response.write(8, hash_data.data());
|
||||
|
||||
@ -83,5 +84,4 @@ namespace demonware
|
||||
|
||||
this->server_->send_reply(reply.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "bit_buffer.hpp"
|
||||
#include "byte_buffer.hpp"
|
||||
#include "data_types.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
class reply
|
||||
{
|
||||
public:
|
||||
reply() = default;
|
||||
|
||||
reply(reply&&) = delete;
|
||||
reply(const reply&) = delete;
|
||||
reply& operator=(reply&&) = delete;
|
||||
reply& operator=(const reply&) = delete;
|
||||
|
||||
virtual ~reply() = default;
|
||||
virtual std::string data() = 0;
|
||||
};
|
||||
@ -22,7 +32,7 @@ namespace demonware
|
||||
{
|
||||
}
|
||||
|
||||
virtual std::string data() override
|
||||
std::string data() override
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
@ -30,16 +40,16 @@ namespace demonware
|
||||
|
||||
class typed_reply : public raw_reply
|
||||
{
|
||||
private:
|
||||
uint8_t type_;
|
||||
public:
|
||||
typed_reply(const uint8_t _type) : type_(_type)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t type() const { return this->type_; }
|
||||
|
||||
public:
|
||||
typed_reply(uint8_t _type) : type_(_type)
|
||||
{
|
||||
}
|
||||
private:
|
||||
uint8_t type_;
|
||||
};
|
||||
|
||||
class encrypted_reply final : public typed_reply
|
||||
@ -55,54 +65,48 @@ namespace demonware
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
virtual std::string data() override;
|
||||
std::string data() override;
|
||||
};
|
||||
|
||||
class unencrypted_reply final : public typed_reply
|
||||
{
|
||||
public:
|
||||
unencrypted_reply(uint8_t _type, bit_buffer* bbuffer) : typed_reply(_type)
|
||||
unencrypted_reply(const uint8_t _type, bit_buffer* bbuffer) : typed_reply(_type)
|
||||
{
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
unencrypted_reply(uint8_t _type, byte_buffer* bbuffer) : typed_reply(_type)
|
||||
unencrypted_reply(const uint8_t _type, byte_buffer* bbuffer) : typed_reply(_type)
|
||||
{
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
virtual std::string data() override;
|
||||
std::string data() override;
|
||||
};
|
||||
|
||||
class service_server;
|
||||
|
||||
class remote_reply final
|
||||
{
|
||||
private:
|
||||
uint8_t type_;
|
||||
service_server* server_;
|
||||
|
||||
public:
|
||||
remote_reply(service_server* server, uint8_t _type) : type_(_type), server_(server)
|
||||
{
|
||||
}
|
||||
|
||||
void send(bit_buffer* buffer, const bool encrypted);
|
||||
void send(byte_buffer* buffer, const bool encrypted);
|
||||
void send(bit_buffer* buffer, bool encrypted);
|
||||
void send(byte_buffer* buffer, bool encrypted);
|
||||
|
||||
uint8_t type() const { return this->type_; }
|
||||
|
||||
private:
|
||||
uint8_t type_;
|
||||
service_server* server_;
|
||||
};
|
||||
|
||||
class service_reply final
|
||||
{
|
||||
private:
|
||||
uint8_t type_;
|
||||
uint32_t error_;
|
||||
remote_reply reply_;
|
||||
std::vector<std::shared_ptr<bdTaskResult>> objects_;
|
||||
|
||||
public:
|
||||
service_reply(service_server* _server, uint8_t _type, uint32_t _error)
|
||||
service_reply(service_server* _server, const uint8_t _type, const uint32_t _error)
|
||||
: type_(_type), error_(_error), reply_(_server, 1)
|
||||
{
|
||||
}
|
||||
@ -150,6 +154,11 @@ namespace demonware
|
||||
{
|
||||
this->add(std::shared_ptr<bdTaskResult>(object));
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
uint8_t type_;
|
||||
uint32_t error_;
|
||||
remote_reply reply_;
|
||||
std::vector<std::shared_ptr<bdTaskResult>> objects_;
|
||||
};
|
||||
}
|
||||
|
60
src/client/game/demonware/server_registry.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "servers/base_server.hpp"
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
template <typename T>
|
||||
class server_registry
|
||||
{
|
||||
static_assert(std::is_base_of<base_server, T>::value, "Invalid server registry type");
|
||||
|
||||
public:
|
||||
template <typename S, typename ...Args>
|
||||
void create(Args&&... args)
|
||||
{
|
||||
static_assert(std::is_base_of<T, S>::value, "Invalid server type");
|
||||
|
||||
auto server = std::make_unique<S>(std::forward<Args>(args)...);
|
||||
const auto address = server->get_address();
|
||||
servers_[address] = std::move(server);
|
||||
}
|
||||
|
||||
void for_each(const std::function<void(T&)>& callback) const
|
||||
{
|
||||
for (auto& server : servers_)
|
||||
{
|
||||
callback(*server.second);
|
||||
}
|
||||
}
|
||||
|
||||
T* find(const std::string& name)
|
||||
{
|
||||
const auto address = utils::cryptography::jenkins_one_at_a_time::compute(name);
|
||||
return find(address);
|
||||
}
|
||||
|
||||
T* find(const uint32_t address)
|
||||
{
|
||||
const auto it = servers_.find(address);
|
||||
if (it == servers_.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void frame()
|
||||
{
|
||||
for (auto& server : servers_)
|
||||
{
|
||||
server.second->frame();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, std::unique_ptr<T>> servers_;
|
||||
};
|
||||
}
|
162
src/client/game/demonware/servers/auth3_server.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "auth3_server.hpp"
|
||||
#include "../keys.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
namespace
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct auth_ticket
|
||||
{
|
||||
unsigned int m_magicNumber;
|
||||
char m_type;
|
||||
unsigned int m_titleID;
|
||||
unsigned int m_timeIssued;
|
||||
unsigned int m_timeExpires;
|
||||
unsigned __int64 m_licenseID;
|
||||
unsigned __int64 m_userID;
|
||||
char m_username[64];
|
||||
char m_sessionKey[24];
|
||||
char m_usingHashMagicNumber[3];
|
||||
char m_hash[4];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
void auth3_server::send_reply(reply* data)
|
||||
{
|
||||
if (!data) return;
|
||||
this->send(data->data());
|
||||
}
|
||||
|
||||
void auth3_server::handle(const std::string& packet)
|
||||
{
|
||||
if (packet.starts_with("POST /auth/"))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [auth]: user requested authentication.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int title_id = 0;
|
||||
unsigned int iv_seed = 0;
|
||||
std::string identity{};
|
||||
std::string token{};
|
||||
|
||||
rapidjson::Document j;
|
||||
j.Parse(packet.data(), packet.size());
|
||||
|
||||
if (j.HasMember("title_id") && j["title_id"].IsString())
|
||||
{
|
||||
title_id = std::stoul(j["title_id"].GetString());
|
||||
}
|
||||
|
||||
if (j.HasMember("iv_seed") && j["iv_seed"].IsString())
|
||||
{
|
||||
iv_seed = std::stoul(j["iv_seed"].GetString());
|
||||
}
|
||||
|
||||
if (j.HasMember("extra_data") && j["extra_data"].IsString())
|
||||
{
|
||||
rapidjson::Document extra_data;
|
||||
auto& ed = j["extra_data"];
|
||||
extra_data.Parse(ed.GetString(), ed.GetStringLength());
|
||||
|
||||
if (extra_data.HasMember("token") && extra_data["token"].IsString())
|
||||
{
|
||||
auto& token_field = extra_data["token"];
|
||||
std::string token_b64(token_field.GetString(), token_field.GetStringLength());
|
||||
token = utils::cryptography::base64::decode(token_b64);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [auth]: authenticating user %s\n", token.data() + 64);
|
||||
#endif
|
||||
|
||||
std::string auth_key(reinterpret_cast<char*>(token.data() + 32), 24);
|
||||
std::string session_key(
|
||||
"\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37", 24);
|
||||
|
||||
// client_ticket
|
||||
auth_ticket ticket{};
|
||||
std::memset(&ticket, 0x0, sizeof ticket);
|
||||
ticket.m_magicNumber = 0x0EFBDADDE;
|
||||
ticket.m_type = 0;
|
||||
ticket.m_titleID = title_id;
|
||||
ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
|
||||
ticket.m_timeExpires = ticket.m_timeIssued + 30000;
|
||||
ticket.m_licenseID = 0;
|
||||
ticket.m_userID = reinterpret_cast<uint64_t>(token.data() + 56);
|
||||
strncpy_s(ticket.m_username, sizeof(ticket.m_username), reinterpret_cast<char*>(token.data() + 64), 64);
|
||||
std::memcpy(ticket.m_sessionKey, session_key.data(), 24);
|
||||
|
||||
const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&iv_seed), 4));
|
||||
const auto ticket_enc = utils::cryptography::des3::encrypt(
|
||||
std::string(reinterpret_cast<char*>(&ticket), sizeof(ticket)), iv, auth_key);
|
||||
const auto ticket_b64 = utils::cryptography::base64::encode(
|
||||
reinterpret_cast<const unsigned char*>(ticket_enc.data()), 128);
|
||||
|
||||
// server_ticket
|
||||
uint8_t auth_data[128];
|
||||
std::memset(&auth_data, 0, sizeof auth_data);
|
||||
std::memcpy(auth_data, session_key.data(), 24);
|
||||
const auto auth_data_b64 = utils::cryptography::base64::encode(auth_data, 128);
|
||||
|
||||
demonware::set_session_key(session_key);
|
||||
|
||||
// header time
|
||||
char date[64];
|
||||
const auto now = time(nullptr);
|
||||
tm gmtm{};
|
||||
gmtime_s(&gmtm, &now);
|
||||
strftime(date, 64, "%a, %d %b %G %T", &gmtm);
|
||||
|
||||
// json content
|
||||
rapidjson::Document doc;
|
||||
doc.SetObject();
|
||||
|
||||
doc.AddMember("auth_task", "29", doc.GetAllocator());
|
||||
doc.AddMember("code", "700", doc.GetAllocator());
|
||||
|
||||
auto seed = std::to_string(iv_seed);
|
||||
doc.AddMember("iv_seed", rapidjson::StringRef(seed.data(), seed.size()), doc.GetAllocator());
|
||||
doc.AddMember("client_ticket", rapidjson::StringRef(ticket_b64.data(), ticket_b64.size()), doc.GetAllocator());
|
||||
doc.AddMember("server_ticket", rapidjson::StringRef(auth_data_b64.data(), auth_data_b64.size()),
|
||||
doc.GetAllocator());
|
||||
doc.AddMember("client_id", "", doc.GetAllocator());
|
||||
doc.AddMember("account_type", "steam", doc.GetAllocator());
|
||||
doc.AddMember("crossplay_enabled", false, doc.GetAllocator());
|
||||
doc.AddMember("loginqueue_eanbled", false, doc.GetAllocator());
|
||||
|
||||
rapidjson::Value value{};
|
||||
doc.AddMember("lsg_endpoint", value, doc.GetAllocator());
|
||||
|
||||
rapidjson::StringBuffer buffer{};
|
||||
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
|
||||
writer(buffer);
|
||||
doc.Accept(writer);
|
||||
|
||||
// http stuff
|
||||
std::string result;
|
||||
result.append("HTTP/1.1 200 OK\r\n");
|
||||
result.append("Server: TornadoServer/4.5.3\r\n");
|
||||
result.append("Content-Type: application/json\r\n");
|
||||
result.append(utils::string::va("Date: %s GMT\r\n", date));
|
||||
result.append(utils::string::va("Content-Length: %d\r\n\r\n", buffer.GetLength()));
|
||||
result.append(buffer.GetString(), buffer.GetLength());
|
||||
|
||||
raw_reply reply(result);
|
||||
this->send_reply(&reply);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [auth]: user successfully authenticated.\n");
|
||||
#endif
|
||||
}
|
||||
}
|
16
src/client/game/demonware/servers/auth3_server.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "tcp_server.hpp"
|
||||
#include "../reply.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class auth3_server : public tcp_server
|
||||
{
|
||||
public:
|
||||
using tcp_server::tcp_server;
|
||||
|
||||
private:
|
||||
void send_reply(reply* data);
|
||||
void handle(const std::string& packet) override;
|
||||
};
|
||||
}
|
22
src/client/game/demonware/servers/base_server.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include <std_include.hpp>
|
||||
#include "base_server.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
base_server::base_server(std::string name): name_(std::move(name))
|
||||
{
|
||||
this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_);
|
||||
}
|
||||
|
||||
const std::string& base_server::get_name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
uint32_t base_server::get_address() const
|
||||
{
|
||||
return this->address_;
|
||||
}
|
||||
}
|
30
src/client/game/demonware/servers/base_server.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class base_server
|
||||
{
|
||||
public:
|
||||
using stream_queue = std::queue<char>;
|
||||
using data_queue = std::queue<std::string>;
|
||||
|
||||
base_server(std::string name);
|
||||
|
||||
base_server(base_server&&) = delete;
|
||||
base_server(const base_server&) = delete;
|
||||
base_server& operator=(base_server&&) = delete;
|
||||
base_server& operator=(const base_server&) = delete;
|
||||
|
||||
virtual ~base_server() = default;
|
||||
|
||||
const std::string& get_name() const;
|
||||
|
||||
uint32_t get_address() const;
|
||||
|
||||
virtual void frame() = 0;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::uint32_t address_ = 0;
|
||||
};
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../demonware.hpp"
|
||||
#include "lobby_server.hpp"
|
||||
|
||||
#include "../services.hpp"
|
||||
#include "../keys.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
server_lobby::server_lobby(std::string name) : server_base(std::move(name))
|
||||
lobby_server::lobby_server(std::string name) : tcp_server(std::move(name))
|
||||
{
|
||||
this->register_service<bdAnticheat>();
|
||||
this->register_service<bdBandwidthTest>();
|
||||
@ -21,72 +25,18 @@ namespace demonware
|
||||
this->register_service<bdUNK63>();
|
||||
this->register_service<bdUNK80>();
|
||||
this->register_service<bdPresence>();
|
||||
this->register_service<bdUNK104>();
|
||||
this->register_service<bdMarketingComms>();
|
||||
this->register_service<bdMatchMaking2>();
|
||||
this->register_service<bdMarketing>();
|
||||
};
|
||||
|
||||
void server_lobby::frame()
|
||||
{
|
||||
if (!this->incoming_queue_.empty())
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
const auto packet = this->incoming_queue_.front();
|
||||
this->incoming_queue_.pop();
|
||||
|
||||
this->dispatch(packet);
|
||||
}
|
||||
}
|
||||
|
||||
int server_lobby::recv(const char* buf, int len)
|
||||
{
|
||||
if (len <= 3) return -1;
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
|
||||
this->incoming_queue_.push(std::string(buf, len));
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int server_lobby::send(char* buf, int len)
|
||||
{
|
||||
if (len > 0 && !this->outgoing_queue_.empty())
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
|
||||
len = std::min(len, static_cast<int>(this->outgoing_queue_.size()));
|
||||
for (auto i = 0; i < len; ++i)
|
||||
{
|
||||
buf[i] = this->outgoing_queue_.front();
|
||||
this->outgoing_queue_.pop();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
bool server_lobby::pending_data()
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
return !this->outgoing_queue_.empty();
|
||||
}
|
||||
|
||||
void server_lobby::send_reply(reply* data)
|
||||
void lobby_server::send_reply(reply* data)
|
||||
{
|
||||
if (!data) return;
|
||||
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto buffer = data->data();
|
||||
for (auto& byte : buffer)
|
||||
{
|
||||
this->outgoing_queue_.push(byte);
|
||||
}
|
||||
this->send(data->data());
|
||||
}
|
||||
|
||||
void server_lobby::dispatch(const std::string& packet)
|
||||
void lobby_server::handle(const std::string& packet)
|
||||
{
|
||||
byte_buffer buffer(packet);
|
||||
buffer.set_use_data_types(false);
|
||||
@ -108,7 +58,7 @@ namespace demonware
|
||||
else if (size == 0xC8)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [lobby]: received client_header_ack.\n");
|
||||
printf("[DW]: [lobby]: received client_header_ack.\n");
|
||||
#endif
|
||||
|
||||
int c8;
|
||||
@ -116,22 +66,24 @@ namespace demonware
|
||||
std::string packet_1 = buffer.get_remaining();
|
||||
demonware::queue_packet_to_hash(packet_1);
|
||||
|
||||
const std::string packet_2("\x16\x00\x00\x00\xab\x81\xd2\x00\x00\x00\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37", 26);
|
||||
const std::string packet_2(
|
||||
"\x16\x00\x00\x00\xab\x81\xd2\x00\x00\x00\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37",
|
||||
26);
|
||||
demonware::queue_packet_to_hash(packet_2);
|
||||
|
||||
raw_reply reply(packet_2);
|
||||
this->send_reply(&reply);
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [lobby]: sending server_header_ack.\n");
|
||||
printf("[DW]: [lobby]: sending server_header_ack.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.size() < size_t(size)) return;
|
||||
|
||||
uint8_t check_AB;
|
||||
buffer.read_byte(&check_AB);
|
||||
if (check_AB == 0xAB)
|
||||
uint8_t check_ab;
|
||||
buffer.read_byte(&check_ab);
|
||||
if (check_ab == 0xAB)
|
||||
{
|
||||
uint8_t type;
|
||||
buffer.read_byte(&type);
|
||||
@ -139,7 +91,7 @@ namespace demonware
|
||||
if (type == 0x82)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [lobby]: received client_auth.\n");
|
||||
printf("[DW]: [lobby]: received client_auth.\n");
|
||||
#endif
|
||||
std::string packet_3(packet.data(), packet.size() - 8); // this 8 are client hash check?
|
||||
|
||||
@ -154,14 +106,14 @@ namespace demonware
|
||||
this->send_reply(&reply);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [lobby]: sending server_auth_done.\n");
|
||||
printf("[DW]: [lobby]: sending server_auth_done.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
else if (type == 0x85)
|
||||
{
|
||||
uint32_t msgCount;
|
||||
buffer.read_uint32(&msgCount);
|
||||
uint32_t msg_count;
|
||||
buffer.read_uint32(&msg_count);
|
||||
|
||||
char seed[16];
|
||||
buffer.read(16, &seed);
|
||||
@ -171,7 +123,9 @@ namespace demonware
|
||||
char hash[8];
|
||||
std::memcpy(hash, &(enc.data()[enc.size() - 8]), 8);
|
||||
|
||||
std::string dec = utils::cryptography::aes::decrypt(std::string(enc.data(), enc.size() - 8), std::string(seed, 16), demonware::get_decrypt_key());
|
||||
std::string dec = utils::cryptography::aes::decrypt(
|
||||
std::string(enc.data(), enc.size() - 8), std::string(seed, 16),
|
||||
demonware::get_decrypt_key());
|
||||
|
||||
byte_buffer serv(dec);
|
||||
serv.set_use_data_types(false);
|
||||
@ -191,7 +145,7 @@ namespace demonware
|
||||
}
|
||||
}
|
||||
|
||||
printf("[demonware]: [lobby]: ERROR! received unk message.\n");
|
||||
printf("[DW]: [lobby]: ERROR! received unk message.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -200,7 +154,7 @@ namespace demonware
|
||||
}
|
||||
}
|
||||
|
||||
void server_lobby::call_service(std::uint8_t id, const std::string& data)
|
||||
void lobby_server::call_service(const uint8_t id, const std::string& data)
|
||||
{
|
||||
const auto& it = this->services_.find(id);
|
||||
|
||||
@ -210,15 +164,14 @@ namespace demonware
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("[demonware]: [lobby]: missing service '%s'\n", utils::string::va("%d", id));
|
||||
printf("[DW]: [lobby]: missing service '%s'\n", utils::string::va("%d", id));
|
||||
|
||||
// return no error
|
||||
byte_buffer buffer(data);
|
||||
std::uint8_t task_id;
|
||||
uint8_t task_id;
|
||||
buffer.read_byte(&task_id);
|
||||
|
||||
this->create_reply(task_id)->send();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
src/client/game/demonware/servers/lobby_server.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_server.hpp"
|
||||
#include "service_server.hpp"
|
||||
#include "../service.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class lobby_server : public tcp_server, service_server
|
||||
{
|
||||
public:
|
||||
lobby_server(std::string name);
|
||||
|
||||
template <typename T>
|
||||
void register_service()
|
||||
{
|
||||
static_assert(std::is_base_of<service, T>::value, "service must inherit from service");
|
||||
|
||||
auto service = std::make_unique<T>();
|
||||
const uint8_t id = service->id();
|
||||
|
||||
this->services_[id] = std::move(service);
|
||||
}
|
||||
|
||||
void send_reply(reply* data) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint8_t, std::unique_ptr<service>> services_;
|
||||
|
||||
void handle(const std::string& packet) override;
|
||||
void call_service(uint8_t id, const std::string& data);
|
||||
};
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../demonware.hpp"
|
||||
#include <utils/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
int server_auth3::recv(const char* buf, const int len)
|
||||
{
|
||||
if (len <= 0) return -1;
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
this->incoming_queue_.push(std::string(buf, len));
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int server_auth3::send(char* buf, int len)
|
||||
{
|
||||
if (len > 0 && !this->outgoing_queue_.empty())
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex_);
|
||||
|
||||
len = std::min(len, static_cast<int>(this->outgoing_queue_.size()));
|
||||
for (auto i = 0; i < len; ++i)
|
||||
{
|
||||
buf[i] = this->outgoing_queue_.front();
|
||||
this->outgoing_queue_.pop();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
void server_auth3::send_reply(reply* data)
|
||||
{
|
||||
if (!data) return;
|
||||
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto buffer = data->data();
|
||||
for (auto& byte : buffer)
|
||||
{
|
||||
this->outgoing_queue_.push(byte);
|
||||
}
|
||||
}
|
||||
|
||||
void server_auth3::frame()
|
||||
{
|
||||
if (!this->incoming_queue_.empty())
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
const auto packet = this->incoming_queue_.front();
|
||||
this->incoming_queue_.pop();
|
||||
|
||||
this->dispatch(packet);
|
||||
}
|
||||
}
|
||||
|
||||
bool server_auth3::pending_data()
|
||||
{
|
||||
std::lock_guard _(this->mutex_);
|
||||
return !this->outgoing_queue_.empty();
|
||||
}
|
||||
|
||||
void server_auth3::dispatch(const std::string& packet)
|
||||
{
|
||||
if (packet.starts_with("POST /auth/"))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [auth]: user requested authentication.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned int title_id = 0;
|
||||
unsigned int iv_seed = 0;
|
||||
std::string identity = "";
|
||||
std::string token = "";
|
||||
|
||||
json j = json::parse(packet);
|
||||
|
||||
if (j.contains("title_id") && j["title_id"].is_string())
|
||||
title_id = std::stoul(j["title_id"].get<std::string>());
|
||||
|
||||
if (j.contains("iv_seed") && j["iv_seed"].is_string())
|
||||
iv_seed = std::stoul(j["iv_seed"].get<std::string>());
|
||||
|
||||
if (j.contains("extra_data") && j["extra_data"].is_string())
|
||||
{
|
||||
json extra_data = json::parse(j["extra_data"].get<std::string>());
|
||||
|
||||
if (extra_data.contains("token") && extra_data["token"].is_string())
|
||||
{
|
||||
std::string token_b64 = extra_data["token"].get<std::string>();
|
||||
token = utils::cryptography::base64::decode(token_b64);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [auth]: authenticating user %s\n", std::string(&token.data()[64]).data());
|
||||
#endif
|
||||
|
||||
std::string auth_key(reinterpret_cast<char*>(&token.data()[32]), 24);
|
||||
std::string session_key("\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37", 24);
|
||||
|
||||
// client_ticket
|
||||
auth_ticket_t ticket{};
|
||||
std::memset(&ticket, 0x0, sizeof ticket);
|
||||
ticket.m_magicNumber = 0x0EFBDADDE;
|
||||
ticket.m_type = 0;
|
||||
ticket.m_titleID = title_id;
|
||||
ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
|
||||
ticket.m_timeExpires = ticket.m_timeIssued + 30000;
|
||||
ticket.m_licenseID = 0;
|
||||
ticket.m_userID = reinterpret_cast<uint64_t>(&token.data()[56]);
|
||||
strncpy_s(ticket.m_username, sizeof(ticket.m_username), reinterpret_cast<char*>(&token.data()[64]), 64);
|
||||
std::memcpy(ticket.m_sessionKey, session_key.data(), 24);
|
||||
|
||||
const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&iv_seed), 4));
|
||||
std::string ticket_enc = utils::cryptography::des3::encrypt(
|
||||
std::string(reinterpret_cast<char*>(&ticket), sizeof(ticket)), iv, auth_key);
|
||||
std::string ticket_b64 = utils::cryptography::base64::encode((const unsigned char*)ticket_enc.c_str(), 128);
|
||||
|
||||
// server_ticket
|
||||
uint8_t auth_data[128];
|
||||
std::memset(&auth_data, 0, sizeof auth_data);
|
||||
std::memcpy(auth_data, session_key.data(), 24);
|
||||
std::string auth_data_b64 = utils::cryptography::base64::encode(auth_data, 128);
|
||||
|
||||
demonware::set_session_key(session_key);
|
||||
|
||||
// header time
|
||||
char date[64];
|
||||
time_t now = time(0);
|
||||
tm gmtm;
|
||||
gmtime_s(&gmtm, &now);
|
||||
strftime(date, 64, "%a, %d %b %G %T", &gmtm);
|
||||
|
||||
// json content
|
||||
std::string content;
|
||||
content.append("{\"auth_task\": \"29\",");
|
||||
content.append("\"code\": \"700\",");
|
||||
content.append(utils::string::va("\"iv_seed\": \"%u\",", iv_seed));
|
||||
content.append(utils::string::va("\"client_ticket\": \"%s\",", ticket_b64.c_str()));
|
||||
content.append(utils::string::va("\"server_ticket\": \"%s\",", auth_data_b64.c_str()));
|
||||
content.append("\"client_id\": \"\",");
|
||||
content.append("\"account_type\": \"steam\",");
|
||||
content.append("\"crossplay_enabled\": false,");
|
||||
content.append("\"loginqueue_eanbled\": false,");
|
||||
content.append("\"lsg_endpoint\": null,");
|
||||
content.append("}");
|
||||
|
||||
// http stuff
|
||||
std::string result;
|
||||
result.append("HTTP/1.1 200 OK\r\n");
|
||||
result.append("Server: TornadoServer/4.5.3\r\n");
|
||||
result.append("Content-Type: application/json\r\n");
|
||||
result.append(utils::string::va("Date: %s GMT\r\n", date));
|
||||
result.append(utils::string::va("Content-Length: %d\r\n\r\n", content.size()));
|
||||
result.append(content);
|
||||
|
||||
raw_reply reply(result);
|
||||
this->send_reply(&reply);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[demonware]: [auth]: user successfully authenticated.\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
class server_base
|
||||
{
|
||||
std::string name_;
|
||||
std::uint32_t address_ = 0;
|
||||
|
||||
public:
|
||||
server_base(std::string name) : name_(std::move(name))
|
||||
{
|
||||
this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_);
|
||||
}
|
||||
|
||||
std::uint32_t address()
|
||||
{
|
||||
return this->address_;
|
||||
}
|
||||
|
||||
virtual void frame()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
virtual int recv(const char* buf, int size)
|
||||
{
|
||||
printf("PACKET\n%s\n", std::string(buf, size).data());
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int send(char* buf, int size)
|
||||
{
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
virtual bool pending_data()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
using server_ptr = std::shared_ptr<server_base>;
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct auth_ticket_t
|
||||
{
|
||||
unsigned int m_magicNumber;
|
||||
char m_type;
|
||||
unsigned int m_titleID;
|
||||
unsigned int m_timeIssued;
|
||||
unsigned int m_timeExpires;
|
||||
unsigned __int64 m_licenseID;
|
||||
unsigned __int64 m_userID;
|
||||
char m_username[64];
|
||||
char m_sessionKey[24];
|
||||
char m_usingHashMagicNumber[3];
|
||||
char m_hash[4];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class server_auth3 : public server_base
|
||||
{
|
||||
public:
|
||||
explicit server_auth3(std::string name) : server_base(name) {};
|
||||
|
||||
void frame() override;
|
||||
int recv(const char* buf, int len) override;
|
||||
int send(char* buf, int len) override;
|
||||
bool pending_data()override;
|
||||
|
||||
private:
|
||||
std::recursive_mutex mutex_;
|
||||
std::queue<char> outgoing_queue_;
|
||||
std::queue<std::string> incoming_queue_;
|
||||
|
||||
void send_reply(reply* data);
|
||||
void dispatch(const std::string& packet);
|
||||
};
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
class server_lobby : public server_base, service_server
|
||||
{
|
||||
public:
|
||||
explicit server_lobby(std::string name);
|
||||
|
||||
void frame() override;
|
||||
int recv(const char* buf, int len) override;
|
||||
int send(char* buf, int len) override;
|
||||
bool pending_data()override;
|
||||
|
||||
template <typename T>
|
||||
void register_service()
|
||||
{
|
||||
static_assert(std::is_base_of<service, T>::value, "service must inherit from service");
|
||||
|
||||
auto service = std::make_unique<T>();
|
||||
const uint8_t id = service->id();
|
||||
|
||||
this->services_[id] = std::move(service);
|
||||
}
|
||||
|
||||
void send_reply(reply* data) override;
|
||||
|
||||
private:
|
||||
std::recursive_mutex mutex_;
|
||||
std::queue<char> outgoing_queue_;
|
||||
std::queue<std::string> incoming_queue_;
|
||||
std::map<std::uint8_t, std::unique_ptr<service>> services_;
|
||||
|
||||
void dispatch(const std::string& packet);
|
||||
void call_service(std::uint8_t type, const std::string& data);
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../reply.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
class service_server
|
||||
{
|
||||
public:
|
||||
virtual ~service_server() = default;
|
||||
|
||||
virtual std::shared_ptr<remote_reply> create_message(uint8_t type)
|
||||
{
|
||||
auto reply = std::make_shared<remote_reply>(this, type);
|
||||
@ -21,5 +24,4 @@ namespace demonware
|
||||
|
||||
virtual void send_reply(reply* data) = 0;
|
||||
};
|
||||
|
||||
} // namespace demonware
|
||||
}
|
62
src/client/game/demonware/servers/stun_server.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include <std_include.hpp>
|
||||
#include "stun_server.hpp"
|
||||
|
||||
#include "../byte_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void stun_server::handle(const endpoint_data& endpoint, const std::string& packet)
|
||||
{
|
||||
uint8_t type, version, padding;
|
||||
|
||||
byte_buffer buffer(packet);
|
||||
buffer.set_use_data_types(false);
|
||||
buffer.read_byte(&type);
|
||||
buffer.read_byte(&version);
|
||||
buffer.read_byte(&padding);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 30:
|
||||
this->ip_discovery(endpoint);
|
||||
break;
|
||||
case 20:
|
||||
this->nat_discovery(endpoint);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void stun_server::ip_discovery(const endpoint_data& endpoint)
|
||||
{
|
||||
const uint32_t ip = 0x0100007f;
|
||||
|
||||
byte_buffer buffer;
|
||||
buffer.set_use_data_types(false);
|
||||
buffer.write_byte(31); // type
|
||||
buffer.write_byte(2); // version
|
||||
buffer.write_byte(0); // version
|
||||
buffer.write_uint32(ip); // external ip
|
||||
buffer.write_uint16(3074); // port
|
||||
|
||||
this->send(endpoint, buffer.get_buffer());
|
||||
}
|
||||
|
||||
void stun_server::nat_discovery(const endpoint_data& endpoint)
|
||||
{
|
||||
const uint32_t ip = 0x0100007f;
|
||||
|
||||
byte_buffer buffer;
|
||||
buffer.set_use_data_types(false);
|
||||
buffer.write_byte(21); // type
|
||||
buffer.write_byte(2); // version
|
||||
buffer.write_byte(0); // version
|
||||
buffer.write_uint32(ip); // external ip
|
||||
buffer.write_uint16(3074); // port
|
||||
buffer.write_uint32(this->get_address()); // server ip
|
||||
buffer.write_uint16(3074); // server port
|
||||
|
||||
this->send(endpoint, buffer.get_buffer());
|
||||
}
|
||||
}
|
18
src/client/game/demonware/servers/stun_server.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "udp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class stun_server : public udp_server
|
||||
{
|
||||
public:
|
||||
using udp_server::udp_server;
|
||||
|
||||
private:
|
||||
void handle(const endpoint_data& endpoint, const std::string& packet) override;
|
||||
|
||||
void ip_discovery(const endpoint_data& endpoint);
|
||||
void nat_discovery(const endpoint_data& endpoint);
|
||||
};
|
||||
}
|
84
src/client/game/demonware/servers/tcp_server.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include <std_include.hpp>
|
||||
#include "tcp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void tcp_server::handle_input(const char* buf, size_t size)
|
||||
{
|
||||
in_queue_.access([&](data_queue& queue)
|
||||
{
|
||||
queue.emplace(buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
size_t tcp_server::handle_output(char* buf, size_t size)
|
||||
{
|
||||
if (out_queue_.get_raw().empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return out_queue_.access<size_t>([&](stream_queue& queue)
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
if (queue.empty())
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
buf[i] = queue.front();
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
return size;
|
||||
});
|
||||
}
|
||||
|
||||
bool tcp_server::pending_data()
|
||||
{
|
||||
return !this->out_queue_.get_raw().empty();
|
||||
}
|
||||
|
||||
void tcp_server::frame()
|
||||
{
|
||||
if (this->in_queue_.get_raw().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
std::string packet{};
|
||||
const auto result = this->in_queue_.access<bool>([&](data_queue& queue)
|
||||
{
|
||||
if (queue.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
packet = std::move(queue.front());
|
||||
queue.pop();
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
this->handle(packet);
|
||||
}
|
||||
}
|
||||
|
||||
void tcp_server::send(const std::string& data)
|
||||
{
|
||||
out_queue_.access([&](stream_queue& queue)
|
||||
{
|
||||
for (const auto& val : data)
|
||||
{
|
||||
queue.push(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
27
src/client/game/demonware/servers/tcp_server.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "base_server.hpp"
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class tcp_server : public base_server
|
||||
{
|
||||
public:
|
||||
using base_server::base_server;
|
||||
|
||||
void handle_input(const char* buf, size_t size);
|
||||
size_t handle_output(char* buf, size_t size);
|
||||
bool pending_data();
|
||||
void frame() override;
|
||||
|
||||
protected:
|
||||
virtual void handle(const std::string& data) = 0;
|
||||
|
||||
void send(const std::string& data);
|
||||
|
||||
private:
|
||||
utils::concurrency::container<data_queue> in_queue_;
|
||||
utils::concurrency::container<stream_queue> out_queue_;
|
||||
};
|
||||
}
|
103
src/client/game/demonware/servers/udp_server.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include <std_include.hpp>
|
||||
#include "udp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void udp_server::handle_input(const char* buf, size_t size, endpoint_data endpoint)
|
||||
{
|
||||
in_queue_.access([&](in_queue& queue)
|
||||
{
|
||||
in_packet p;
|
||||
p.data = std::string{buf, size};
|
||||
p.endpoint = std::move(endpoint);
|
||||
|
||||
queue.emplace(std::move(p));
|
||||
});
|
||||
}
|
||||
|
||||
size_t udp_server::handle_output(SOCKET socket, char* buf, size_t size, sockaddr* address, int* addrlen)
|
||||
{
|
||||
return out_queue_.access<size_t>([&](socket_queue_map& map) -> size_t
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& queue = entry->second;
|
||||
|
||||
if (queue.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data = std::move(queue.front());
|
||||
queue.pop();
|
||||
|
||||
const auto copy_size = std::min(size, data.data.size());
|
||||
std::memcpy(buf, data.data.data(), copy_size);
|
||||
std::memcpy(address, &data.address, sizeof(data.address));
|
||||
*addrlen = sizeof(data.address);
|
||||
|
||||
return copy_size;
|
||||
});
|
||||
}
|
||||
|
||||
bool udp_server::pending_data(SOCKET socket)
|
||||
{
|
||||
return this->out_queue_.access<bool>([&](const socket_queue_map& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !entry->second.empty();
|
||||
});
|
||||
}
|
||||
|
||||
void udp_server::send(const endpoint_data& endpoint, std::string data)
|
||||
{
|
||||
out_queue_.access([&](socket_queue_map& map)
|
||||
{
|
||||
out_packet p;
|
||||
p.data = std::move(data);
|
||||
p.address = endpoint.address;
|
||||
|
||||
map[endpoint.socket].emplace(std::move(p));
|
||||
});
|
||||
}
|
||||
|
||||
void udp_server::frame()
|
||||
{
|
||||
if (this->in_queue_.get_raw().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
in_packet packet{};
|
||||
const auto result = this->in_queue_.access<bool>([&](in_queue& queue)
|
||||
{
|
||||
if (queue.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
packet = std::move(queue.front());
|
||||
queue.pop();
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
this->handle(packet.endpoint, std::move(packet.data));
|
||||
}
|
||||
}
|
||||
}
|
62
src/client/game/demonware/servers/udp_server.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "base_server.hpp"
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class udp_server : public base_server
|
||||
{
|
||||
public:
|
||||
struct endpoint_data
|
||||
{
|
||||
SOCKET socket{};
|
||||
sockaddr_in address{};
|
||||
|
||||
endpoint_data() = default;
|
||||
|
||||
endpoint_data(const SOCKET sock, const sockaddr* addr, const int size)
|
||||
{
|
||||
if (size != sizeof(this->address))
|
||||
{
|
||||
throw std::runtime_error("Invalid size");
|
||||
}
|
||||
|
||||
this->socket = sock;
|
||||
std::memcpy(&this->address, addr, sizeof(this->address));
|
||||
}
|
||||
};
|
||||
|
||||
using base_server::base_server;
|
||||
|
||||
void handle_input(const char* buf, size_t size, endpoint_data endpoint);
|
||||
size_t handle_output(SOCKET socket, char* buf, size_t size, sockaddr* address, int* addrlen);
|
||||
bool pending_data(SOCKET socket);
|
||||
|
||||
void frame() override;
|
||||
|
||||
protected:
|
||||
virtual void handle(const endpoint_data& endpoint, const std::string& data) = 0;
|
||||
void send(const endpoint_data& endpoint, std::string data);
|
||||
|
||||
private:
|
||||
struct in_packet
|
||||
{
|
||||
std::string data;
|
||||
endpoint_data endpoint;
|
||||
};
|
||||
|
||||
struct out_packet
|
||||
{
|
||||
std::string data;
|
||||
sockaddr_in address;
|
||||
};
|
||||
|
||||
using in_queue = std::queue<in_packet>;
|
||||
using out_queue = std::queue<out_packet>;
|
||||
using socket_queue_map = std::unordered_map<SOCKET, out_queue>;
|
||||
|
||||
utils::concurrency::container<in_queue> in_queue_;
|
||||
utils::concurrency::container<socket_queue_map> out_queue_;
|
||||
};
|
||||
}
|
11
src/client/game/demonware/servers/umbrella_server.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "umbrella_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void umbrella_server::handle(const std::string& packet)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
}
|
14
src/client/game/demonware/servers/umbrella_server.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "tcp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class umbrella_server : public tcp_server
|
||||
{
|
||||
public:
|
||||
using tcp_server::tcp_server;
|
||||
|
||||
private:
|
||||
void handle(const std::string& packet) override;
|
||||
};
|
||||
}
|
@ -1,76 +1,89 @@
|
||||
#pragma once
|
||||
#include <utils/string.hpp>
|
||||
#include "servers/service_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class service
|
||||
{
|
||||
using callback_t = std::function<void(service_server*, byte_buffer*)>;
|
||||
|
||||
class service
|
||||
{
|
||||
using callback_t = std::function<void(service_server*, byte_buffer*)>;
|
||||
uint8_t id_;
|
||||
std::string name_;
|
||||
std::mutex mutex_;
|
||||
uint8_t task_id_;
|
||||
std::map<uint8_t, callback_t> tasks_;
|
||||
|
||||
std::uint8_t id_;
|
||||
std::string name_;
|
||||
std::mutex mutex_;
|
||||
std::uint8_t task_id_;
|
||||
std::map<std::uint8_t, callback_t> tasks_;
|
||||
public:
|
||||
virtual ~service() = default;
|
||||
service(service&&) = delete;
|
||||
service(const service&) = delete;
|
||||
service& operator=(const service&) = delete;
|
||||
|
||||
public:
|
||||
virtual ~service() = default;
|
||||
service(service&&) = delete;
|
||||
service(const service&) = delete;
|
||||
service& operator=(const service&) = delete;
|
||||
service(std::uint8_t id, std::string name) : id_(id), task_id_(0), name_(std::move(name)) { }
|
||||
service(const uint8_t id, std::string name) : id_(id), name_(std::move(name)), task_id_(0)
|
||||
{
|
||||
}
|
||||
|
||||
auto id() const -> std::uint8_t { return this->id_; }
|
||||
auto name() const -> std::string { return this->name_; }
|
||||
auto task_id() const -> std::uint8_t { return this->task_id_; }
|
||||
uint8_t id() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
virtual void exec_task(service_server* server, const std::string& data)
|
||||
{
|
||||
std::lock_guard $(this->mutex_);
|
||||
const std::string& name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
byte_buffer buffer(data);
|
||||
uint8_t task_id() const
|
||||
{
|
||||
return this->task_id_;
|
||||
}
|
||||
|
||||
buffer.read_byte(&this->task_id_);
|
||||
virtual void exec_task(service_server* server, const std::string& data)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(this->mutex_);
|
||||
|
||||
const auto& it = this->tasks_.find(this->task_id_);
|
||||
byte_buffer buffer(data);
|
||||
|
||||
if (it != this->tasks_.end())
|
||||
{
|
||||
buffer.read_byte(&this->task_id_);
|
||||
|
||||
const auto& it = this->tasks_.find(this->task_id_);
|
||||
|
||||
if (it != this->tasks_.end())
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("demonware::%s: executing task '%d'\n", name_.data(), this->task_id_);
|
||||
printf("[DW] %s: executing task '%d'\n", name_.data(), this->task_id_);
|
||||
#endif
|
||||
|
||||
it->second(server, &buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("demonware::%s: missing task '%d'\n", name_.data(), this->task_id_);
|
||||
it->second(server, &buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("[DW] %s: missing task '%d'\n", name_.data(), this->task_id_);
|
||||
|
||||
// return no error
|
||||
server->create_reply(this->task_id_)->send();
|
||||
}
|
||||
}
|
||||
// return no error
|
||||
server->create_reply(this->task_id_)->send();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
protected:
|
||||
|
||||
template <typename Class, typename T, typename... Args>
|
||||
void register_task(const uint8_t id, T(Class::* callback)(Args ...) const)
|
||||
{
|
||||
this->tasks_[id] = [this, callback](Args ... args) -> T
|
||||
{
|
||||
return (reinterpret_cast<Class*>(this)->*callback)(args...);
|
||||
};
|
||||
}
|
||||
template <typename Class, typename T, typename... Args>
|
||||
void register_task(const uint8_t id, T (Class::* callback)(Args ...) const)
|
||||
{
|
||||
this->tasks_[id] = [this, callback](Args ... args) -> T
|
||||
{
|
||||
return (reinterpret_cast<Class*>(this)->*callback)(args...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Class, typename T, typename... Args>
|
||||
void register_task(const uint8_t id, T(Class::* callback)(Args ...))
|
||||
{
|
||||
this->tasks_[id] = [this, callback](Args ... args) -> T
|
||||
{
|
||||
return (reinterpret_cast<Class*>(this)->*callback)(args...);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace demonware
|
||||
template <typename Class, typename T, typename... Args>
|
||||
void register_task(const uint8_t id, T (Class::* callback)(Args ...))
|
||||
{
|
||||
this->tasks_[id] = [this, callback](Args ... args) -> T
|
||||
{
|
||||
return (reinterpret_cast<Class*>(this)->*callback)(args...);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "bit_buffer.hpp"
|
||||
#include "byte_buffer.hpp"
|
||||
#include "data_types.hpp"
|
||||
#include "reply.hpp"
|
||||
#include "server.hpp"
|
||||
#include "service.hpp"
|
||||
#include "servers/service_server.hpp"
|
||||
|
||||
//#include "services/bdTeams.hpp" // 3
|
||||
#include "services/bdStats.hpp" // 4
|
||||
@ -32,22 +30,6 @@
|
||||
#include "services/bdUNK80.hpp"
|
||||
// AccountLinking // 86
|
||||
#include "services/bdPresence.hpp" //103
|
||||
#include "services/bdUNK104.hpp" //104 Marketing
|
||||
#include "services/bdMarketingComms.hpp" //104
|
||||
#include "services/bdMatchMaking2.hpp" //138
|
||||
#include "services/bdMarketing.hpp" //139
|
||||
|
||||
// servers
|
||||
#include "servers/server_auth3.hpp"
|
||||
#include "servers/server_lobby.hpp"
|
||||
#include "servers/server_stun.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void derive_keys_s1();
|
||||
void queue_packet_to_hash(const std::string& packet);
|
||||
void set_session_key(const std::string& key);
|
||||
std::string get_decrypt_key();
|
||||
std::string get_encrypt_key();
|
||||
std::string get_hmac_key();
|
||||
std::string get_response_id();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../demonware.hpp"
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
@ -9,14 +9,14 @@ namespace demonware
|
||||
this->register_task(4, &bdAnticheat::report_console_details);
|
||||
}
|
||||
|
||||
void bdAnticheat::unk2(service_server* server, byte_buffer* buffer) const
|
||||
void bdAnticheat::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO: Read data as soon as needed
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdAnticheat::report_console_details(service_server* server, byte_buffer* buffer) const
|
||||
void bdAnticheat::report_console_details(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO: Read data as soon as needed
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../demonware.hpp"
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../demonware.hpp"
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
@ -9,14 +9,14 @@ namespace demonware
|
||||
this->register_task(3, &bdContentStreaming::unk3);
|
||||
}
|
||||
|
||||
void bdContentStreaming::unk2(service_server* server, byte_buffer* buffer) const
|
||||
void bdContentStreaming::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdContentStreaming::unk3(service_server* server, byte_buffer* buffer) const
|
||||
void bdContentStreaming::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
|