Compare commits

...

3 Commits

Author SHA1 Message Date
Nukoooo
dfc9859806 Make signature support IDA/x64Dbg style (#407)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2024-04-13 12:11:26 +10:00
Nukoooo
f99f58402a Add mising `GameData.GetSignature to NetworkStateChangedFunc` (#415) 2024-04-13 11:00:51 +10:00
Michael Wilson
6317559bd2 [no ci] Update .clang-format & add formatting tool install scripts (#410) 2024-04-12 12:36:39 +10:00
15 changed files with 311 additions and 108 deletions

View File

@@ -1,21 +1,72 @@
BasedOnStyle: LLVM
# Spacing
IndentWidth: 4
ColumnLimit: 100
UseTab: Never
ColumnLimit: 140
# Line Endings
LineEnding: LF
InsertNewlineAtEOF: true
DerivePointerAlignment: false
PointerAlignment: Left
AlignAfterOpenBracket: Align
KeepEmptyLinesAtTheStartOfBlocks: false
SortIncludes: false
SpaceBeforeParens: ControlStatements
AllowAllArgumentsOnNextLine: true
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false
IndentCaseLabels: true
# Line Breaks
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterStruct: true
AfterControlStatement: Always
AfterEnum: true
AfterUnion: true
AfterNamespace: false
AfterFunction: true
AfterNamespace: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
PointerAlignment: Left
SortIncludes: CaseSensitive
IncludeBlocks: Regroup
IncludeCategories:
# External headers in <> with extension or /
- Regex: '<[-\w\/-_]+[\.\/][-\w\/-_]+>'
Priority: 2
# Standard headers in <>
- Regex: '<[-\w\/-_]+>'
Priority: 3
# Local headers in ""
- Regex: '"[-\w\/-_]*"'
Priority: 4
ReflowComments: true
CompactNamespaces: false
Cpp11BracedListStyle: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignEscapedNewlines: Left
AlignTrailingComments: Never
AllowShortBlocksOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: OnlyFirstIf
AllowShortLambdasOnASingleLine: Empty
BinPackArguments: true
BinPackParameters: false
LambdaBodyIndentation: OuterScope

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
max_line_length = 140
trim_trailing_whitespace = true
[*.json]
indent_size = 2

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
.cmake/
cmake-build-*/
.kdev4/
.vscode/
generated/
# configure_file auto generated.

4
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"editor.defaultFormatter": null,
"editor.formatOnSave": true
}

View File

@@ -0,0 +1,37 @@
# Tool taken from dotnet/runtime
# https://github.com/dotnet/runtime/blob/a8158c170b694f8c1dbae114c63c346b38244901/eng/formatting/download-tools.ps1
function DownloadClangTool {
param (
[string]
$toolName,
[string]
$downloadOutputPath
)
$clangVersion = "17.0.6"
$clangToolsRootUrl = "https://clrjit2.blob.core.windows.net/clang-tools"
$clangPlatform = "windows-x64"
$toolUrl = "$clangToolsRootUrl/$clangVersion/$clangPlatform/$toolName.exe"
$targetPath = "$downloadOutputPath\$toolName.exe"
if (-not $(ls $downloadOutputPath | Where-Object { $_.Name -eq "$toolName.exe" })) {
Write-Output "Downloading '$toolUrl' to '$targetPath'"
# Pass -PassThru as otherwise Invoke-WebRequest leaves a corrupted file if the download fails. With -PassThru the download is buffered first.
# -UseBasicParsing is necessary for older PowerShells when Internet Explorer might not be installed/configured
$null = Invoke-WebRequest -Uri "$toolUrl" -OutFile $(Join-Path $downloadOutputPath -ChildPath "$toolName.exe") -PassThru -UseBasicParsing
}
else {
Write-Output "Found '$targetPath'"
}
}
$downloadPathFolder = Split-Path $PSScriptRoot -Parent | Split-Path -Parent | Join-Path -ChildPath "artifacts" | Join-Path -ChildPath "tools"
mkdir $downloadPathFolder -ErrorAction SilentlyContinue
DownloadClangTool "clang-format" "$downloadPathFolder"
# Add to path to enable scripts to skip additional downloading steps since the tools will already be on the path.
$env:PATH = "$downloadPathFolder;$env:PATH"

View File

@@ -0,0 +1,60 @@
# Tool taken from dotnet/runtime
# https://github.com/dotnet/runtime/blob/a8158c170b694f8c1dbae114c63c346b38244901/eng/formatting/download-tools.sh
#!/usr/bin/env bash
set -ue
source="${BASH_SOURCE[0]}"
# resolve $source until the file is no longer a symlink
while [[ -h "$source" ]]; do
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
source="$(readlink "$source")"
# if $source was a relative symlink, we need to resolve it relative to the path where the
# symlink file was located
[[ $source != /* ]] && source="$scriptroot/$source"
done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
function DownloadClangTool {
clangVersion="17.0.6"
clangToolsRootUrl="https://clrjit2.blob.core.windows.net/clang-tools"
clangPlatform="$(dotnet --info | grep 'RID:')"
clangPlatform="${clangPlatform##*RID:* }"
echo "dotnet RID: ${clangPlatform}"
# override common RIDs with compatible version so we don't need to upload binaries for each RID
case $clangPlatform in
ubuntu.*-x64)
clangPlatform=linux-x64
;;
esac
toolUrl="${clangToolsRootUrl}/${clangVersion}/${clangPlatform}/$1"
toolOutput=$2/$1
echo "Downloading $1 from ${toolUrl} to ${toolOutput}"
if [[ ! -x "$toolOutput" ]]; then
curl --silent --retry 5 --fail -o "${toolOutput}" "$toolUrl"
chmod 751 $toolOutput
fi
if [[ ! -x "$toolOutput" ]]; then
echo "Failed to download $1"
exit 1
fi
}
engFolder="$(cd -P "$( dirname "$scriptroot" )" && pwd )"
downloadPathFolder="$(cd -P "$( dirname "$engFolder" )" && pwd )/artifacts/tools"
mkdir -p "$downloadPathFolder"
DownloadClangTool "clang-format" "$downloadPathFolder"
export PATH=$downloadPathFolder:$PATH

29
eng/formatting/format.sh Normal file
View File

@@ -0,0 +1,29 @@
# Tool taken from dotnet/runtime
# https://github.com/dotnet/runtime/blob/a8158c170b694f8c1dbae114c63c346b38244901/eng/formatting/format.sh
#!/bin/sh
LC_ALL=C
# Select files to format
NATIVE_FILES=$(git diff --cached --name-only --diff-filter=ACM "*.h" "*.hpp" "*.c" "*.cpp" "*.inl" | sed 's| |\\ |g')
MANAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM "*.cs" "*.vb" | sed 's| |\\ |g')
exec 1>&2
if [ -n "$NATIVE_FILES" ]; then
# Format all selected files
echo "$NATIVE_FILES" | cat | xargs | sed -e 's/ /,/g' | xargs "./artifacts/tools/clang-format" -style=file -i
# Add back the modified files to staging
echo "$NATIVE_FILES" | xargs git add
fi
if [ -n "$MANAGED_FILES" ]; then
# Format all selected files
echo "$MANAGED_FILES" | cat | xargs | sed -e 's/ /,/g' | dotnet format whitespace --include - --folder
# Add back the modified files to staging
echo "$MANAGED_FILES" | xargs git add
fi
exit 0

View File

@@ -12,7 +12,7 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
)
# TODO: Use C++20 instead.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
if (LINUX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")

View File

@@ -89,7 +89,7 @@ public static class VirtualFunctions
new(GameData.GetSignature("StateChanged"));
public static Action<IntPtr, IntPtr, int, short, short> StateChanged = StateChangedFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, int, long> NetworkStateChangedFunc = new("NetworkStateChanged");
public static MemoryFunctionVoid<IntPtr, int, long> NetworkStateChangedFunc = new(GameData.GetSignature("NetworkStateChanged"));
public static Action<IntPtr, int, long> NetworkStateChanged = NetworkStateChangedFunc.Invoke;
}

View File

@@ -165,11 +165,11 @@ void* CGameConfig::ResolveSignature(const char* name)
return nullptr;
}
size_t iLength = 0;
byte* pSignature = HexToByte(signature, iLength);
if (!pSignature) {
auto pSignature = HexToByte(signature);
if (pSignature.empty()) {
return nullptr;
}
address = (*module)->FindSignature(pSignature, iLength);
address = (*module)->FindSignature(pSignature);
}
if (!address) {
@@ -190,47 +190,63 @@ std::string CGameConfig::GetDirectoryName(const std::string& directoryPathInput)
return "";
}
int CGameConfig::HexStringToUint8Array(const char* hexString, uint8_t* byteArray, size_t maxBytes)
std::vector<int16_t> CGameConfig::HexToByte(std::string_view src)
{
if (!hexString) {
printf("Invalid hex string.\n");
if (src.empty()) {
return {};
}
auto hex_char_to_byte = [](char c) -> int16_t {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
// a valid hex char can never go up to 0xFF
return -1;
}
};
size_t hexStringLength = strlen(hexString);
size_t byteCount = hexStringLength / 4; // Each "\\x" represents one byte.
std::vector<int16_t> result{};
if (hexStringLength % 4 != 0 || byteCount == 0 || byteCount > maxBytes) {
printf("Invalid hex string format or byte count.\n");
return -1; // Return an error code.
}
const bool is_code_style = src[0] == '\\';
for (size_t i = 0; i < hexStringLength; i += 4) {
if (sscanf(hexString + i, "\\x%2hhX", &byteArray[i / 4]) != 1) {
printf("Failed to parse hex string at position %zu.\n", i);
return -1; // Return an error code.
const std::string_view pattern = is_code_style ? R"(\x)" : " ";
const std::string_view wildcard = is_code_style ? "2A" : "?";
auto split = std::views::split(src, pattern);
for (auto&& str : split) {
if (str.empty()) [[unlikely]] {
continue;
}
// std::string_view(std::subrange) constructor only exists in C++23 or above
// use this when compiler is GCC >= 12.1 or Clang >= 16
// const std::string_view byte(str.begin(), str.end());
// a workaround for GCC < 12.1, it doesn't work with Clang < 16
// https://stackoverflow.com/a/48403210
std::string_view byte (&*str.begin(), std::ranges::distance(str));
if (byte.starts_with(wildcard)) {
result.emplace_back(-1);
continue;
}
const auto high = hex_char_to_byte(byte[0]);
const auto low = hex_char_to_byte(byte[1]);
// if then is malformed, return nothing
// maybe print error message here?
if (high == 0xFF || low == 0xFF) [[unlikely]] {
return {};
}
result.emplace_back((high << 4) | low);
}
byteArray[byteCount] = '\0'; // Add a null-terminating character.
return byteCount; // Return the number of bytes successfully converted.
return result;
}
byte* CGameConfig::HexToByte(const char* src, size_t& length)
{
if (!src || strlen(src) <= 0) {
CSSHARP_CORE_INFO("Invalid hex string\n");
return nullptr;
}
length = strlen(src) / 4;
uint8_t* dest = new uint8_t[length];
int byteCount = HexStringToUint8Array(src, dest, length);
if (byteCount <= 0) {
CSSHARP_CORE_INFO("Invalid hex format %s\n", src);
return nullptr;
}
return dest;
}
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -33,8 +33,7 @@ class CGameConfig
void* ResolveSignature(const char* name);
static std::string GetDirectoryName(const std::string& directoryPathInput);
static int HexStringToUint8Array(const char* hexString, uint8_t* byteArray, size_t maxBytes);
static byte* HexToByte(const char* src, size_t& length);
static std::vector<int16_t> HexToByte(std::string_view src);
private:
std::string m_sPath;

View File

@@ -30,12 +30,15 @@
#include <Psapi.h>
#endif
#include <cstdio>
#include "gameconfig.h"
#include <cstdio>
#include "memory_module.h"
#include "metamod_oslink.h"
#include "wchartypes.h"
#include <vector>
#if __linux__
struct ModuleInfo {
const char *path; // in
@@ -118,29 +121,13 @@ int GetModuleInformation(void *hModule, void **base, size_t *length) {
}
#endif
byte *ConvertToByteArray(const char *str, size_t *outLength) {
size_t len = strlen(str) / 4; // Every byte is represented as \xHH
byte *result = (byte *)malloc(len);
for (size_t i = 0, j = 0; i < len; ++i, j += 4) {
sscanf(str + j, "\\x%2hhx", &result[i]);
}
*outLength = len;
return result;
}
void* FindSignature(const char* moduleName, const char* bytesStr) {
size_t iSigLength;
auto sigBytes = ConvertToByteArray(bytesStr, &iSigLength);
auto module = dlmount(moduleName);
if (module == nullptr) {
return nullptr;
}
void *moduleBase;
void* moduleBase;
size_t moduleSize;
#if __linux__
if (GetModuleInformation(module, &moduleBase, &moduleSize) != 0) {
@@ -154,18 +141,30 @@ void* FindSignature(const char* moduleName, const char* bytesStr) {
moduleSize = m_hModuleInfo.SizeOfImage;
#endif
unsigned char *pMemory;
void *returnAddr = nullptr;
auto sigBytes = counterstrikesharp::CGameConfig::HexToByte(bytesStr);
pMemory = (byte *)moduleBase;
if (sigBytes.empty()) {
return nullptr;
}
for (size_t i = 0; i < moduleSize; i++) {
size_t matches = 0;
while (*(pMemory + i + matches) == sigBytes[matches] || sigBytes[matches] == '\x2A') {
matches++;
if (matches == iSigLength) {
returnAddr = (void *)(pMemory + i);
}
void* returnAddr = nullptr;
const auto first_byte = sigBytes[0];
auto pMemory = (byte*)moduleBase;
std::uint8_t* end = pMemory + moduleSize - sigBytes.size();
for (std::uint8_t* current = pMemory; current <= end; ++current) {
if (first_byte != -1)
current = std::find(current, end, first_byte);
if (current == end) {
break;
}
if (std::equal(sigBytes.begin() + 1, sigBytes.end(), current + 1,
[](auto opt, auto byte) { return opt == -1 || opt == byte; })) {
return current;
}
}
@@ -174,4 +173,4 @@ void* FindSignature(const char* moduleName, const char* bytesStr) {
}
return returnAddr;
}
}

View File

@@ -41,29 +41,35 @@ void* CModule::FindSignature(const char* signature)
return nullptr;
}
size_t iSigLength = 0;
byte* pData = CGameConfig::HexToByte(signature, iSigLength);
auto pData = CGameConfig::HexToByte(signature);
if (pData.empty()) [[unlikely]]
return nullptr;
return this->FindSignature(pData, iSigLength);
return this->FindSignature(pData);
}
void* CModule::FindSignature(const byte* pData, size_t iSigLength)
void* CModule::FindSignature(const std::vector<int16_t>& sigBytes)
{
unsigned char* pMemory;
void* return_addr = nullptr;
const auto first_byte = sigBytes[0];
pMemory = (byte*)m_base;
auto pMemory = (std::uint8_t*)m_base;
std::uint8_t* end = pMemory + m_size - sigBytes.size();
for (size_t i = 0; i < m_size; i++) {
size_t Matches = 0;
while (*(pMemory + i + Matches) == pData[Matches] || pData[Matches] == '\x2A') {
Matches++;
if (Matches == iSigLength)
return_addr = (void*)(pMemory + i);
for (std::uint8_t* current = pMemory; current <= end; ++current) {
if (first_byte != -1)
current = std::find(current, end, first_byte);
if (current == end) {
break;
}
if (std::equal(sigBytes.begin() + 1, sigBytes.end(), current + 1,
[](auto opt, auto byte) { return opt == -1 || opt == byte; })) {
return current;
}
}
return return_addr;
return nullptr;
}
void* CModule::FindInterface(const char* name)

View File

@@ -23,6 +23,8 @@
#include "interface.h"
#include "strtools.h"
#include "metamod_oslink.h"
#include <vector>
#undef snprintf
namespace counterstrikesharp::modules {
@@ -34,7 +36,7 @@ class CModule
void* FindSignature(const char* signature);
void* FindSignature(const byte* pData, size_t iSigLength);
void* FindSignature(const std::vector<int16_t>& sigBytes);
void* FindInterface(const char* name);

View File

@@ -26,19 +26,6 @@
namespace counterstrikesharp {
std::vector<ValveFunction*> m_managed_ptrs;
byte* ConvertToByteArray(const char* str, size_t* outLength)
{
size_t len = strlen(str) / 4; // Every byte is represented as \xHH
byte* result = (byte*)malloc(len);
for (size_t i = 0, j = 0; i < len; ++i, j += 4) {
sscanf(str + j, "\\x%2hhx", &result[i]);
}
*outLength = len;
return result;
}
void* FindSignatureNative(ScriptContext& scriptContext)
{
auto moduleName = scriptContext.GetArgument<const char*>(0);