From 05219118734842acf5b0160f422471cd1a2e1241 Mon Sep 17 00:00:00 2001 From: Cherry Date: Wed, 7 Aug 2024 09:03:52 +0300 Subject: [PATCH] feat: batch pattern scan and various small fixes --- development.md | 9 ++- packages/tosu/package.json | 2 +- .../tosu/src/entities/AllTimesData/index.ts | 6 +- .../objects/instanceManager/osuInstance.ts | 54 ++++++------- packages/tosu/src/objects/memoryBase.ts | 4 +- packages/tosu/src/objects/memoryPatterns.ts | 4 +- packages/tsprocess/binding.gyp | 5 +- packages/tsprocess/lib/functions.cc | 42 ++++++++++ packages/tsprocess/lib/memory/memory.h | 81 ++++++++++++++++++- packages/tsprocess/lib/memory/memory_linux.cc | 2 +- .../tsprocess/lib/memory/memory_windows.cc | 8 +- packages/tsprocess/src/process.ts | 32 ++++++++ packages/updater/index.ts | 6 ++ 13 files changed, 206 insertions(+), 49 deletions(-) diff --git a/development.md b/development.md index 246e8d0d..9fa06754 100644 --- a/development.md +++ b/development.md @@ -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 diff --git a/packages/tosu/package.json b/packages/tosu/package.json index 06f06601..f6e267c7 100644 --- a/packages/tosu/package.json +++ b/packages/tosu/package.json @@ -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:*", diff --git a/packages/tosu/src/entities/AllTimesData/index.ts b/packages/tosu/src/entities/AllTimesData/index.ts index 28801c44..c49c0229 100644 --- a/packages/tosu/src/entities/AllTimesData/index.ts +++ b/packages/tosu/src/entities/AllTimesData/index.ts @@ -41,7 +41,7 @@ export class AllTimesData extends AbstractEntity { const { statusPtr, menuModsPtr, - chatCheckerAddr, + chatCheckerPtr, skinDataAddr, settingsClassAddr, canRunSlowlyAddr, @@ -50,7 +50,7 @@ export class AllTimesData extends AbstractEntity { } = patterns.getPatterns([ 'statusPtr', 'menuModsPtr', - 'chatCheckerAddr', + 'chatCheckerPtr', 'skinDataAddr', 'settingsClassAddr', 'canRunSlowlyAddr', @@ -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) ); diff --git a/packages/tosu/src/objects/instanceManager/osuInstance.ts b/packages/tosu/src/objects/instanceManager/osuInstance.ts index e84652d0..4ed01af1 100644 --- a/packages/tosu/src/objects/instanceManager/osuInstance.ts +++ b/packages/tosu/src/objects/instanceManager/osuInstance.ts @@ -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'; @@ -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' @@ -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) { @@ -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'); } diff --git a/packages/tosu/src/objects/memoryBase.ts b/packages/tosu/src/objects/memoryBase.ts index 1c19045b..e0e5d042 100644 --- a/packages/tosu/src/objects/memoryBase.ts +++ b/packages/tosu/src/objects/memoryBase.ts @@ -7,7 +7,7 @@ export interface BaseData { baseAddr: number; menuModsAddr: number; playTimeAddr: number; - chatCheckerAddr: number; + chatCheckerPtr: number; skinDataAddr: number; configurationAddr: number; bindingsAddr: number; @@ -31,7 +31,7 @@ export class MemoryBase { baseAddr: 0, menuModsAddr: 0, playTimeAddr: 0, - chatCheckerAddr: 0, + chatCheckerPtr: 0, skinDataAddr: 0, configurationAddr: 0, bindingsAddr: 0, diff --git a/packages/tosu/src/objects/memoryPatterns.ts b/packages/tosu/src/objects/memoryPatterns.ts index b8c650f5..33514940 100644 --- a/packages/tosu/src/objects/memoryPatterns.ts +++ b/packages/tosu/src/objects/memoryPatterns.ts @@ -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; @@ -29,7 +29,7 @@ export class MemoryPatterns { this.patterns = { baseAddr: 0, playTimeAddr: 0, - chatCheckerAddr: 0, + chatCheckerPtr: 0, skinDataAddr: 0, settingsClassAddr: 0, configurationAddr: 0, diff --git a/packages/tsprocess/binding.gyp b/packages/tsprocess/binding.gyp index 38254fb7..93cd6227 100644 --- a/packages/tsprocess/binding.gyp +++ b/packages/tsprocess/binding.gyp @@ -5,7 +5,10 @@ 'sources': [ 'lib/functions.cc', 'lib/memory/memory_linux.cc', 'lib/memory/memory_windows.cc' ], 'include_dirs': ["(args[0].As().Int64Value()); + auto pattern_array = args[1].As(); + + std::vector patterns; + + for (size_t i = 0; i < pattern_array.Length(); i++) { + Pattern pattern; + + auto iter_obj = pattern_array.Get(i).As(); + auto signature = iter_obj.Get("signature").As(); + auto mask = iter_obj.Get("mask").As(); + + pattern.index = i; + pattern.signature = std::span(reinterpret_cast(signature.Data()), signature.ByteLength()); + pattern.mask = std::span(reinterpret_cast(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) { @@ -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); diff --git a/packages/tsprocess/lib/memory/memory.h b/packages/tsprocess/lib/memory/memory.h index 273e7e58..738da618 100644 --- a/packages/tsprocess/lib/memory/memory.h +++ b/packages/tsprocess/lib/memory/memory.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include #include @@ -11,6 +13,18 @@ struct MemoryRegion { std::size_t size; }; +struct Pattern { + int index; + std::span signature; + std::span mask; + bool found; +}; + +struct PatternResult { + int index; + uintptr_t address; +}; + namespace memory { std::vector query_regions(void *process); @@ -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); @@ -32,8 +46,31 @@ std::tuple read(void *process, uintptr_t address) { return std::make_tuple(data, success); } -inline bool -scan(std::vector buffer, const std::vector signature, const std::vector mask, size_t &offset) { +inline bool scan(std::vector buffer, std::span signature, std::span 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(i); + return true; + } + + return false; +} + +inline bool scan(std::vector buffer, std::vector signature, std::vector mask, size_t &offset) { offset = 0; for (size_t i = 0; i + signature.size() <= buffer.size(); ++i) { @@ -77,4 +114,42 @@ inline uintptr_t find_pattern(void *process, const std::vector signatur return 0; } +inline std::vector batch_find_pattern(void *process, std::vector patterns) { + const auto regions = query_regions(process); + + auto results = std::vector(); + + for (auto ®ion : regions) { + auto buffer = std::vector(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 diff --git a/packages/tsprocess/lib/memory/memory_linux.cc b/packages/tsprocess/lib/memory/memory_linux.cc index 30190fde..3bedf5aa 100644 --- a/packages/tsprocess/lib/memory/memory_linux.cc +++ b/packages/tsprocess/lib/memory/memory_linux.cc @@ -129,7 +129,7 @@ std::vector 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); } } diff --git a/packages/tsprocess/lib/memory/memory_windows.cc b/packages/tsprocess/lib/memory/memory_windows.cc index f5ec8c31..5e9503d3 100644 --- a/packages/tsprocess/lib/memory/memory_windows.cc +++ b/packages/tsprocess/lib/memory/memory_windows.cc @@ -25,13 +25,11 @@ std::vector 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(info.BaseAddress), info.RegionSize}); - } + regions.push_back(MemoryRegion{reinterpret_cast(info.BaseAddress), info.RegionSize}); } return regions; @@ -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 ""; } diff --git a/packages/tsprocess/src/process.ts b/packages/tsprocess/src/process.ts index d1d42ee3..cd682471 100644 --- a/packages/tsprocess/src/process.ts +++ b/packages/tsprocess/src/process.ts @@ -9,6 +9,16 @@ export interface ProcessInfo { pcPriClassBase: number; } +export interface Pattern { + signature: Buffer; + mask: Buffer; +} + +export interface PatternResult { + address: number; + index: number; +} + export class Process { public id: number; public handle: number; @@ -146,4 +156,26 @@ export class Process { } }); } + + scanBatch(signatures: string[]): PatternResult[] { + const patterns: Pattern[] = []; + + for (const signature of signatures) { + const bytes = signature.split(' '); + const signatureBuffer = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : x)).join(''), + 'hex' + ); + const maskBuffer = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : '01')).join(''), + 'hex' + ); + patterns.push({ + signature: signatureBuffer, + mask: maskBuffer + }); + } + + return ProcessUtils.batchScan(this.handle, patterns); + } } diff --git a/packages/updater/index.ts b/packages/updater/index.ts index f529efb9..dac8f81a 100644 --- a/packages/updater/index.ts +++ b/packages/updater/index.ts @@ -90,6 +90,12 @@ export const autoUpdater = async () => { } const { assets, versionName, platformType } = check; + + if (!versionName || !assets) { + wLogger.warn(`Version name/assets not found`); + return 'noFiles'; + } + if (versionName.includes(currentVersion)) { wLogger.info(`You're using latest version v${currentVersion}`);