Skip to content

Commit

Permalink
feat: batch pattern scan and various small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
xxCherry authored and KotRikD committed Aug 7, 2024
1 parent cf6d758 commit 0521911
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 49 deletions.
9 changes: 8 additions & 1 deletion development.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ pnpm run start


6. Compile tosu

For Windows:
```
pnpm install && pnpm build:win
```

For Linux
```
pnpm install && pnpm build
pnpm install && pnpm build:linux
```

7. Go to `/tosu/packages/tosu/dist`, and there is your's tosu build
2 changes: 1 addition & 1 deletion packages/tosu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"run:dev": "pnpm run genver && pnpm run ts:run src/index.ts",
"compile:prepare-htmls": "cp -rf node_modules/@tosu/server/assets ./dist",
"compile:win": "pnpm run genver && pnpm run ts:compile && pnpm run compile:prepare-htmls && pkg --output dist/tosu.exe --debug --config pkg.win.json --compress brotli dist/index.js && pnpm run ts:run src/postBuild.ts",
"compile:linux": "pnpm run genver && pnpm run ts:compile && pnpm run compile:prepare-htmls && pkg --output dist/tosu --debug --config pkg.linux.json --compress brotli dist/index.js"
"compile:linux": "pnpm run genver && pnpm run ts:compile && pnpm run compile:prepare-htmls && pkg --output dist/tosu --debug --config pkg.linux.json --compress brotli dist/index.js"
},
"dependencies": {
"@tosu/common": "workspace:*",
Expand Down
6 changes: 3 additions & 3 deletions packages/tosu/src/entities/AllTimesData/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class AllTimesData extends AbstractEntity {
const {
statusPtr,
menuModsPtr,
chatCheckerAddr,
chatCheckerPtr,
skinDataAddr,
settingsClassAddr,
canRunSlowlyAddr,
Expand All @@ -50,7 +50,7 @@ export class AllTimesData extends AbstractEntity {
} = patterns.getPatterns([
'statusPtr',
'menuModsPtr',
'chatCheckerAddr',
'chatCheckerPtr',
'skinDataAddr',
'settingsClassAddr',
'canRunSlowlyAddr',
Expand All @@ -63,7 +63,7 @@ export class AllTimesData extends AbstractEntity {
// [MenuMods + 0x9]
this.MenuMods = process.readPointer(menuModsPtr);
// ChatChecker - 0x20
this.ChatStatus = process.readByte(chatCheckerAddr - 0x20);
this.ChatStatus = process.readByte(process.readInt(chatCheckerPtr));
this.IsWatchingReplay = process.readByte(
process.readInt(canRunSlowlyAddr + 0x46)
);
Expand Down
54 changes: 24 additions & 30 deletions packages/tosu/src/objects/instanceManager/osuInstance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { config, sleep, updateProgressBar, wLogger } from '@tosu/common';
import { config, sleep, wLogger } from '@tosu/common';
import { injectGameOverlay } from '@tosu/game-overlay';
import EventEmitter from 'events';
import fs from 'fs';
Expand Down Expand Up @@ -36,8 +36,9 @@ const SCAN_PATTERNS: {
playTimeAddr: {
pattern: '5E 5F 5D C3 A1 ?? ?? ?? ?? 89 ?? 04'
},
chatCheckerAddr: {
pattern: '0A D7 23 3C 00 00 ?? 01'
chatCheckerPtr: {
pattern: '83 3D ?? ?? ?? ?? 00 75 ?? 80 3D',
offset: 0x2
},
skinDataAddr: {
pattern: '74 2C 85 FF 75 28 A1 ?? ?? ?? ?? 8D 15'
Expand Down Expand Up @@ -147,7 +148,7 @@ export class OsuInstance {
}

async start() {
wLogger.info(`[${this.pid}] Running memory chimera..`);
wLogger.info(`[${this.pid}] Running memory chimera...`);
while (!this.isReady) {
const patternsRepo = this.entities.get('patterns');
if (!patternsRepo) {
Expand All @@ -157,37 +158,30 @@ export class OsuInstance {
}

try {
const total = Object.keys(SCAN_PATTERNS).length;
let completed = 0;
for (const baseKey in SCAN_PATTERNS) {
const s1 = performance.now();
const patternValue = this.process.scanSync(
SCAN_PATTERNS[baseKey].pattern
);
completed += 1;
if (patternValue === 0) {
updateProgressBar(
`[${this.pid}] Scanning`,
completed / total,
`${(performance.now() - s1).toFixed(
2
)}ms ${baseKey}`
);
continue;
}
const s1 = performance.now();

patternsRepo.setPattern(
baseKey as never,
patternValue + (SCAN_PATTERNS[baseKey].offset || 0)
);
const results = this.process.scanBatch(
Object.values(SCAN_PATTERNS).map((x) => x.pattern)
);

const indexToKey = Object.keys(SCAN_PATTERNS);

for (let i = 0; i < results.length; i++) {
const baseKey = indexToKey[
results[i].index
] as keyof PatternData;

updateProgressBar(
`[${this.pid}] Scanning`,
completed / total,
`${(performance.now() - s1).toFixed(2)}ms ${baseKey}`
patternsRepo.setPattern(
baseKey,
results[i].address +
(SCAN_PATTERNS[baseKey].offset || 0)
);
}

wLogger.debug(
`[${this.pid}] Took ${(performance.now() - s1).toFixed(2)} ms to scan patterns`
);

if (!patternsRepo.checkIsBasesValid()) {
throw new Error('Memory resolve failed');
}
Expand Down
4 changes: 2 additions & 2 deletions packages/tosu/src/objects/memoryBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface BaseData {
baseAddr: number;
menuModsAddr: number;
playTimeAddr: number;
chatCheckerAddr: number;
chatCheckerPtr: number;
skinDataAddr: number;
configurationAddr: number;
bindingsAddr: number;
Expand All @@ -31,7 +31,7 @@ export class MemoryBase {
baseAddr: 0,
menuModsAddr: 0,
playTimeAddr: 0,
chatCheckerAddr: 0,
chatCheckerPtr: 0,
skinDataAddr: 0,
configurationAddr: 0,
bindingsAddr: 0,
Expand Down
4 changes: 2 additions & 2 deletions packages/tosu/src/objects/memoryPatterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { wLogger } from '@tosu/common';
export interface PatternData {
baseAddr: number;
playTimeAddr: number;
chatCheckerAddr: number;
chatCheckerPtr: number;
skinDataAddr: number;
settingsClassAddr: number;
configurationAddr: number;
Expand All @@ -29,7 +29,7 @@ export class MemoryPatterns {
this.patterns = {
baseAddr: 0,
playTimeAddr: 0,
chatCheckerAddr: 0,
chatCheckerPtr: 0,
skinDataAddr: 0,
settingsClassAddr: 0,
configurationAddr: 0,
Expand Down
5 changes: 4 additions & 1 deletion packages/tsprocess/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
'sources': [ 'lib/functions.cc', 'lib/memory/memory_linux.cc', 'lib/memory/memory_windows.cc' ],
'include_dirs': ["<!@(node -p \"require('node-addon-api').include\")"],
'dependencies': ["<!(node -p \"require('node-addon-api').gyp\")"],
"cflags": ["-std=c++23", "-fno-exceptions"],
"cflags_cc": ["-std=c++20", "-fno-exceptions"],
'msvs_settings': {
'VCCLCompilerTool': { 'AdditionalOptions': [ '-std:c++20' ] }
},
"defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"],
},
{
Expand Down
42 changes: 42 additions & 0 deletions packages/tsprocess/lib/functions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,47 @@ Napi::Value scan_sync(const Napi::CallbackInfo &args) {
return Napi::Number::New(env, result);
}

Napi::Value batch_scan(const Napi::CallbackInfo &args) {
Napi::Env env = args.Env();
if (args.Length() < 2) {
Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException();
return env.Null();
}

auto handle = reinterpret_cast<void *>(args[0].As<Napi::Number>().Int64Value());
auto pattern_array = args[1].As<Napi::Array>();

std::vector<Pattern> patterns;

for (size_t i = 0; i < pattern_array.Length(); i++) {
Pattern pattern;

auto iter_obj = pattern_array.Get(i).As<Napi::Object>();
auto signature = iter_obj.Get("signature").As<Napi::Uint8Array>();
auto mask = iter_obj.Get("mask").As<Napi::Uint8Array>();

pattern.index = i;
pattern.signature = std::span<uint8_t>(reinterpret_cast<uint8_t *>(signature.Data()), signature.ByteLength());
pattern.mask = std::span<uint8_t>(reinterpret_cast<uint8_t *>(mask.Data()), mask.ByteLength());
pattern.found = false;

patterns.push_back(pattern);
}

auto result = memory::batch_find_pattern(handle, patterns);
auto result_array = Napi::Array::New(env, result.size());

for (size_t i = 0; i < result.size(); i++) {
auto obj = Napi::Object::New(env);
obj.Set("index", Napi::Number::New(env, result[i].index));
obj.Set("address", Napi::Number::New(env, result[i].address));

result_array.Set(i, obj);
}

return result_array;
}

Napi::Value read_buffer(const Napi::CallbackInfo &args) {
Napi::Env env = args.Env();
if (args.Length() < 3) {
Expand Down Expand Up @@ -406,6 +447,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports["readCSharpString"] = Napi::Function::New(env, read_csharp_string);
exports["scanSync"] = Napi::Function::New(env, scan_sync);
exports["scan"] = Napi::Function::New(env, scan);
exports["batchScan"] = Napi::Function::New(env, batch_scan);
exports["openProcess"] = Napi::Function::New(env, open_process);
exports["findProcesses"] = Napi::Function::New(env, find_processes);
exports["isProcessExist"] = Napi::Function::New(env, is_process_exist);
Expand Down
81 changes: 78 additions & 3 deletions packages/tsprocess/lib/memory/memory.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <chrono>
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <tuple>
Expand All @@ -11,6 +13,18 @@ struct MemoryRegion {
std::size_t size;
};

struct Pattern {
int index;
std::span<uint8_t> signature;
std::span<uint8_t> mask;
bool found;
};

struct PatternResult {
int index;
uintptr_t address;
};

namespace memory {

std::vector<MemoryRegion> query_regions(void *process);
Expand All @@ -21,7 +35,7 @@ void *open_process(uint32_t id);
bool is_process_exist(void *process);
std::string get_process_path(void *process);
std::string get_process_command_line(void *process);
std::string get_process_cwd(void* process);
std::string get_process_cwd(void *process);

bool read_buffer(void *process, uintptr_t address, std::size_t size, uint8_t *buffer);

Expand All @@ -32,8 +46,31 @@ std::tuple<T, bool> read(void *process, uintptr_t address) {
return std::make_tuple(data, success);
}

inline bool
scan(std::vector<uint8_t> buffer, const std::vector<uint8_t> signature, const std::vector<uint8_t> mask, size_t &offset) {
inline bool scan(std::vector<uint8_t> buffer, std::span<uint8_t> signature, std::span<uint8_t> mask, size_t &offset) {
offset = 0;

for (size_t i = 0; i + signature.size() <= buffer.size(); ++i) {
bool found = true;
for (size_t j = 0; j < signature.size(); ++j) {
if (buffer[i + j] == signature[j] || mask[j] == 0)
continue;

found = false;
break;
}

if (!found) {
continue;
}

offset = static_cast<size_t>(i);
return true;
}

return false;
}

inline bool scan(std::vector<uint8_t> buffer, std::vector<uint8_t> signature, std::vector<uint8_t> mask, size_t &offset) {
offset = 0;

for (size_t i = 0; i + signature.size() <= buffer.size(); ++i) {
Expand Down Expand Up @@ -77,4 +114,42 @@ inline uintptr_t find_pattern(void *process, const std::vector<uint8_t> signatur
return 0;
}

inline std::vector<PatternResult> batch_find_pattern(void *process, std::vector<Pattern> patterns) {
const auto regions = query_regions(process);

auto results = std::vector<PatternResult>();

for (auto &region : regions) {
auto buffer = std::vector<uint8_t>(region.size);
if (!read_buffer(process, region.address, region.size, buffer.data())) {
continue;
}

for (auto &pattern : patterns) {
if (pattern.found) {
continue;
}

size_t offset;
if (!scan(buffer, pattern.signature, pattern.mask, offset)) {
continue;
}

PatternResult result;
result.index = pattern.index;
result.address = region.address + offset;

results.push_back(result);

pattern.found = true;

if (patterns.size() == results.size()) {
return results;
}
}
}

return results;
}

} // namespace memory
2 changes: 1 addition & 1 deletion packages/tsprocess/lib/memory/memory_linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ std::vector<MemoryRegion> memory::query_regions(void *process) {
region.size = end_address - region.address;
const auto protections = line.substr(first_space_pos + 1, 5);

if (protections[0] == 'r' && protections[1] == 'w') {
if (protections[0] == 'r' && protections[1] == 'w' && protections[2] == 'x') {
regions.push_back(region);
}
}
Expand Down
8 changes: 3 additions & 5 deletions packages/tsprocess/lib/memory/memory_windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ std::vector<MemoryRegion> memory::query_regions(void *process) {

MEMORY_BASIC_INFORMATION info;
for (uint8_t *address = 0; VirtualQueryEx(process, address, &info, sizeof(info)) != 0; address += info.RegionSize) {
if ((info.State & MEM_COMMIT) == 0 || (info.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) == 0) {
if ((info.State & MEM_COMMIT) == 0 || (info.Protect & (PAGE_EXECUTE_READWRITE)) == 0) {
continue;
}

if (info.Protect == PAGE_EXECUTE_READWRITE || info.Protect == PAGE_READWRITE) {
regions.push_back(MemoryRegion{reinterpret_cast<uintptr_t>(info.BaseAddress), info.RegionSize});
}
regions.push_back(MemoryRegion{reinterpret_cast<uintptr_t>(info.BaseAddress), info.RegionSize});
}

return regions;
Expand Down Expand Up @@ -76,7 +74,7 @@ std::string memory::get_process_path(void *handle) {
return filePath;
}

std::string memory::get_process_cwd(void* process) {
std::string memory::get_process_cwd(void *process) {
return "";
}

Expand Down
Loading

0 comments on commit 0521911

Please sign in to comment.