Merge pull request #65 from XLabsProject/release/v0.0.2

Release v0.0.2
This commit is contained in:
Maurice Heumann 2021-04-07 20:56:38 +02:00 committed by GitHub
commit e1fabc395e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
189 changed files with 7132 additions and 27786 deletions

216
.github/workflows/build.yml vendored Normal file
View 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
View File

@ -0,0 +1,17 @@
name: Notify Discord
on:
push:
branches:
- "*"
issues:
jobs:
notify:
runs-on: ubuntu-latest
if: github.repository_owner == 'XLabsProject'
steps:
- name: Send notification to Discord
uses: Ilshidur/action-discord@master
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_CI_BOT_WEBHOOK }}

82
.github/workflows/draft-new-release.yml vendored Normal file
View 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
View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
assets/github/banner.psd Normal file

Binary file not shown.

BIN
assets/icons/s1x-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
assets/icons/s1x-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/icons/s1x-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
assets/icons/s1x-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
assets/icons/s1x-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

BIN
assets/icons/s1x-icon.psd Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

Binary file not shown.

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +0,0 @@
#pragma once
namespace demonware
{
}

View 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

View File

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

View File

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

View File

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

View File

@ -5,5 +5,4 @@
namespace fastfiles
{
const char* get_current_fastfile();
void reallocate_asset_pool(const game::XAssetType type, const unsigned int new_size);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,4 +49,4 @@ namespace lui
};
}
REGISTER_COMPONENT(lui::component)
REGISTER_COMPONENT(lui::component)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

@ -0,0 +1,6 @@
#pragma once
namespace system_check
{
bool is_valid();
}

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

View 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

View File

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

View File

@ -1,6 +0,0 @@
#pragma once
namespace videos
{
void replace(const std::string& what, const std::string& with);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
#include <std_include.hpp>
#include "demonware.hpp"

View File

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

View 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();
}

View File

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

View File

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

View 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_;
};
}

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

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

View 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_;
}
}

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

View File

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

View 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);
};
}

View File

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

View File

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

View File

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

View File

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

View 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());
}
}

View 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);
};
}

View 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);
}
});
}
}

View 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_;
};
}

View 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));
}
}
}

View 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_;
};
}

View File

@ -0,0 +1,11 @@
#include <std_include.hpp>
#include "umbrella_server.hpp"
namespace demonware
{
void umbrella_server::handle(const std::string& packet)
{
// TODO:
}
}

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
#include <std_include.hpp>
#include "../demonware.hpp"
#include "../services.hpp"
namespace demonware
{

View File

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

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