From dd9f243662c691a5cb299a6ce6c22b7ebd5bdf11 Mon Sep 17 00:00:00 2001 From: Cherry Date: Thu, 1 Aug 2024 10:52:28 +0300 Subject: [PATCH 01/17] feat!: add linux support --- .../tosu/src/entities/AllTimesData/index.ts | 3 +- .../src/entities/TourneyManagerData/index.ts | 5 +- .../objects/instanceManager/osuInstance.ts | 5 +- packages/tsprocess/.gitignore | 2 +- packages/tsprocess/binding.gyp | 6 +- packages/tsprocess/lib/.clang-format | 10 + packages/tsprocess/lib/.clangd | 6 + packages/tsprocess/lib/functions.cc | 436 +++++++----------- packages/tsprocess/lib/memory/memory.h | 79 ++++ packages/tsprocess/lib/memory/memory_linux.cc | 117 +++++ .../tsprocess/lib/memory/memory_windows.cc | 112 +++++ packages/tsprocess/lib/process.h | 199 -------- packages/tsprocess/package.json | 5 +- packages/tsprocess/src/process.ts | 78 ++-- pnpm-lock.yaml | 8 + 15 files changed, 545 insertions(+), 526 deletions(-) create mode 100644 packages/tsprocess/lib/.clang-format create mode 100644 packages/tsprocess/lib/.clangd create mode 100644 packages/tsprocess/lib/memory/memory.h create mode 100644 packages/tsprocess/lib/memory/memory_linux.cc create mode 100644 packages/tsprocess/lib/memory/memory_windows.cc delete mode 100644 packages/tsprocess/lib/process.h diff --git a/packages/tosu/src/entities/AllTimesData/index.ts b/packages/tosu/src/entities/AllTimesData/index.ts index 3810852e..9723fe01 100644 --- a/packages/tosu/src/entities/AllTimesData/index.ts +++ b/packages/tosu/src/entities/AllTimesData/index.ts @@ -123,8 +123,7 @@ export class AllTimesData extends AbstractEntity { ) { if (this.gameTimePtr === 0) { this.gameTimePtr = await process.scanAsync( - GAME_TIME_PTR.pattern, - true + GAME_TIME_PTR.pattern ); wLogger.debug('ATD(updateState) gameTimePtr area found'); return; diff --git a/packages/tosu/src/entities/TourneyManagerData/index.ts b/packages/tosu/src/entities/TourneyManagerData/index.ts index 97892f2a..c9a36674 100644 --- a/packages/tosu/src/entities/TourneyManagerData/index.ts +++ b/packages/tosu/src/entities/TourneyManagerData/index.ts @@ -44,10 +44,7 @@ export class TourneyManagerData extends AbstractEntity { if (this.ChatAreaAddr === 0) { await sleep(1000); - this.ChatAreaAddr = process.scanSync( - TOURNAMENT_CHAT_ENGINE, - true - ); + this.ChatAreaAddr = process.scanSync(TOURNAMENT_CHAT_ENGINE); wLogger.debug('TMD(updateState) Chat area found'); return; } diff --git a/packages/tosu/src/objects/instanceManager/osuInstance.ts b/packages/tosu/src/objects/instanceManager/osuInstance.ts index 13ca04cb..32a41e16 100644 --- a/packages/tosu/src/objects/instanceManager/osuInstance.ts +++ b/packages/tosu/src/objects/instanceManager/osuInstance.ts @@ -64,7 +64,7 @@ const SCAN_PATTERNS: { offset: -0x4 }, menuModsPtr: { - pattern: 'C8 FF 00 00 00 00 00 81 0D 00 00 00 00 00 08 00 00', + pattern: 'C8 FF ?? ?? ?? ?? ?? 81 0D ?? ?? ?? ?? ?? 08 00 00', offset: 0x9 }, getAudioLengthPtr: { @@ -158,8 +158,7 @@ export class OsuInstance { for (const baseKey in SCAN_PATTERNS) { const s1 = performance.now(); const patternValue = this.process.scanSync( - SCAN_PATTERNS[baseKey].pattern, - true + SCAN_PATTERNS[baseKey].pattern ); completed += 1; if (patternValue === 0) { diff --git a/packages/tsprocess/.gitignore b/packages/tsprocess/.gitignore index 85644c4c..5df9179a 100644 --- a/packages/tsprocess/.gitignore +++ b/packages/tsprocess/.gitignore @@ -1,2 +1,2 @@ build/ -disat/ \ No newline at end of file +dist/ \ No newline at end of file diff --git a/packages/tsprocess/binding.gyp b/packages/tsprocess/binding.gyp index 3e5664a7..b4c4fab7 100644 --- a/packages/tsprocess/binding.gyp +++ b/packages/tsprocess/binding.gyp @@ -2,10 +2,14 @@ 'targets': [ { 'target_name': 'tsprocess', - 'sources': [ 'lib/functions.cc' ], + 'sources': [ 'lib/functions.cc', 'lib/memory/memory_linux.cc', 'lib/memory/memory_windows.cc' ], 'include_dirs': [" #include -#include #include +#include +#include "memory/memory.h" + +#if defined(WIN32) || defined(_WIN32) +#include +#endif // https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf template std::string string_format(const std::string &format, Args... args) { - int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + - 1; // Extra space for '\0' + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; if (size_s <= 0) { - throw std::runtime_error("Error during formatting."); + return std::string(); } auto size = static_cast(size_s); std::unique_ptr buf(new char[size]); std::snprintf(buf.get(), size, format.c_str(), args...); - return std::string(buf.get(), - buf.get() + size - 1); // We don't want the '\0' inside + return std::string(buf.get(), buf.get() + size - 1); } -Napi::Value readByte(const Napi::CallbackInfo &args) { +Napi::Value read_byte(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, - string_format("Couldn't read byte at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read byte at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value readShort(const Napi::CallbackInfo &args) { +Napi::Value read_short(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, - string_format("Couldn't read short at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read short at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value readInt(const Napi::CallbackInfo &args) { +Napi::Value read_int(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read int at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read int at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value readUInt(const Napi::CallbackInfo &args) { +Napi::Value read_uint(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, - string_format("Couldn't read uint at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read uint at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value readFloat(const Napi::CallbackInfo &args) { +Napi::Value read_float(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); - auto result = memory::read(handle, address); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); + auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, - string_format("Couldn't read float at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read float at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value readLong(const Napi::CallbackInfo &args) { +Napi::Value read_long(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, - string_format("Couldn't read long at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read long at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value readDouble(const Napi::CallbackInfo &args) { +Napi::Value read_double(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); - auto result = memory::read(handle, address); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); + auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, - string_format("Couldn't read double at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read double at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); } -Napi::Value scanSync(const Napi::CallbackInfo &args) { +Napi::Value scan_sync(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); - if (args.Length() < 4) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + if (args.Length() < 3) { + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto baseAddress = args[1].As().Uint32Value(); - auto signature = args[2].As(); - auto refresh = args[3].As().Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto signature_buffer = args[1].As(); + auto mask_buffer = args[2].As(); + + auto signature = std::vector(signature_buffer.ByteLength()); + memcpy(signature.data(), signature_buffer.Data(), signature_buffer.ByteLength()); - auto vec = std::vector(signature.ByteLength()); - memcpy(vec.data(), signature.Data(), signature.ByteLength()); + auto mask = std::vector(mask_buffer.ByteLength()); + memcpy(mask.data(), mask_buffer.Data(), mask_buffer.ByteLength()); - auto result = memory::find_pattern(handle, vec, refresh, baseAddress); + auto result = memory::find_pattern(handle, signature, mask); if (!result) { - Napi::TypeError::New(env, "Couldn't find signature") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Couldn't find signature").ThrowAsJavaScriptException(); return env.Null(); } - return Napi::Number::New( - env, memory::find_pattern(handle, vec, refresh, baseAddress)); + return Napi::Number::New(env, result); } -Napi::Value readBuffer(const Napi::CallbackInfo &args) { +Napi::Value read_buffer(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 3) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto address = args[1].As().Uint32Value(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto address = args[1].As().Int64Value(); auto size = args[2].As().Uint32Value(); - auto buffer = new char[size]; - auto data = (char *)malloc(sizeof(char) * size); + auto buffer = new uint8_t[size]; + auto data = (uint8_t *)malloc(sizeof(uint8_t) * size); auto result = memory::read_buffer(handle, address, size, data); if (!result) { free(data); delete[] buffer; - Napi::TypeError::New(env, - string_format("Couldn't read buffer at %x", address)) - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, string_format("Couldn't read buffer at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } - auto out = Napi::Buffer::Copy(env, data, size); + auto out = Napi::Buffer::Copy(env, data, size); free(data); delete[] buffer; @@ -232,222 +200,156 @@ static bool scanning = false; Napi::Value scan(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); - if (args.Length() < 5) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + if (args.Length() < 4) { + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); - auto baseAddress = args[1].As().Uint32Value(); - auto signature = args[2].As(); - auto refresh = args[3].As().Value(); - auto callback = Napi::ThreadSafeFunction::New( - args.Env(), args[4].As(), "tsfn", 0, 1); - auto data = signature.Data(); - auto byteLength = signature.ByteLength(); + auto handle = reinterpret_cast(args[0].As().Int64Value()); + auto signature = args[1].As(); + auto mask = args[2].As(); + auto callback = Napi::ThreadSafeFunction::New(args.Env(), args[3].As(), "tsfn", 0, 1); + auto signature_data = signature.Data(); + auto signature_length = signature.ByteLength(); + auto mask_data = mask.Data(); + auto mask_length = mask.ByteLength(); if (!scanning) { scanning = true; std::thread( - [handle, data, byteLength, refresh, - baseAddress](Napi::ThreadSafeFunction tsfn) { - auto vec = std::vector(byteLength); - memcpy(vec.data(), data, byteLength); - - auto res = memory::find_pattern(handle, vec, refresh, baseAddress); - scanning = false; - tsfn.BlockingCall( - [res, tsfn](Napi::Env env, Napi::Function jsCallback) { - jsCallback.Call({Napi::Number::From(env, res)}); - tsfn.Release(); // Release the callback after usage - }); - }, - callback) - .detach(); + [handle, signature_data, signature_length, mask_data, mask_length](Napi::ThreadSafeFunction tsfn) { + auto signature = std::vector(signature_length); + memcpy(signature.data(), signature_data, signature_length); + + auto mask = std::vector(mask_length); + memcpy(mask.data(), mask_data, mask_length); + + const auto result = memory::find_pattern(handle, signature, mask); + scanning = false; + tsfn.BlockingCall([result, tsfn](Napi::Env env, Napi::Function jsCallback) { + jsCallback.Call({Napi::Number::From(env, result)}); + tsfn.Release(); + }); + }, + callback + ) + .detach(); } return env.Undefined(); } -Napi::Value findProcesses(const Napi::CallbackInfo &args) { +Napi::Value find_processes(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 1) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto processName = args[0].As().Utf8Value(); - auto processes = memory::find_processes(processName); + auto process_name = args[0].As().Utf8Value(); + auto processes = memory::find_processes(process_name); auto arr = Napi::Array::New(env, processes.size()); - for (auto i = 0; i < processes.size(); i++) { + for (size_t i = 0; i < processes.size(); i++) { arr.Set(i, processes[i]); } return arr; } -inline std::vector get_processes() { - std::vector processes; - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (snapshot != INVALID_HANDLE_VALUE) { - PROCESSENTRY32 processEntry; - processEntry.dwSize = sizeof(PROCESSENTRY32); - if (Process32First(snapshot, &processEntry)) { - do { - processes.push_back(processEntry); - } while (Process32Next(snapshot, &processEntry)); - } - CloseHandle(snapshot); - } - return processes; -} - -Napi::Value getProcesses(const Napi::CallbackInfo &args) { - Napi::Env env = args.Env(); - if (args.Length() < 0) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); - return env.Null(); - } - - auto processes = get_processes(); - - auto arr = Napi::Array::New(env, processes.size()); - for (auto i = 0; i < processes.size(); i++) { - auto obj = Napi::Object::New(env); - obj.Set("id", processes[i].th32ProcessID); - obj.Set("exeFile", processes[i].szExeFile); - obj.Set("parentId", processes[i].th32ParentProcessID); - obj.Set("pcPriClassBase", processes[i].pcPriClassBase); - - arr.Set(i, obj); - } - - return arr; -} - -Napi::Value openProcess(const Napi::CallbackInfo &args) { +Napi::Value open_process(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 1) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto processId = args[0].As().Uint32Value(); - return Napi::Number::New(env, (int32_t)memory::open_process(processId)); + auto process_id = args[0].As().Int64Value(); + return Napi::Number::New(env, reinterpret_cast(memory::open_process(process_id))); } -Napi::Value isProcessExist(const Napi::CallbackInfo &args) { +Napi::Value is_process_exist(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 1) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Uint32Value()); + auto handle = reinterpret_cast(args[0].As().Int64Value()); return Napi::Boolean::New(env, memory::is_process_exist(handle)); } -Napi::Value getProcessPath(const Napi::CallbackInfo &args) { +Napi::Value get_process_path(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 1) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Uint32Value()); + auto handle = reinterpret_cast(args[0].As().Int64Value()); return Napi::String::From(env, memory::get_process_path(handle)); } -Napi::Value getProcessCommandLine(const Napi::CallbackInfo &args) { +Napi::Value get_process_command_line(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 1) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Uint32Value()); + auto handle = reinterpret_cast(args[0].As().Int64Value()); - std::wstring commandLine = memory::get_proc_command_line(handle); + auto command_line = memory::get_process_command_line(handle); - return Napi::String::New( - env, reinterpret_cast(commandLine.c_str())); + return Napi::String::New(env, command_line.c_str()); } -Napi::Value readCSharpString(const Napi::CallbackInfo &args) { +Napi::Value read_csharp_string(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); + if (args.Length() < 2) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - auto handle = - reinterpret_cast(args[0].As().Int32Value()); + void *handle = reinterpret_cast(args[0].As().Int64Value()); + uintptr_t address = args[1].As().Int64Value(); - auto address = args[1].As().Uint32Value(); - if (address == 0x0) { + if (address == 0) { return Napi::String::New(env, ""); } - // Read the C# string length - int stringLength; - if (!ReadProcessMemory(handle, - reinterpret_cast(address + sizeof(int)), - &stringLength, sizeof(stringLength), NULL)) { - Napi::TypeError::New(env, "Can't read C# string length") - .ThrowAsJavaScriptException(); + int string_length; + if (!memory::read_buffer( + handle, address + sizeof(int), sizeof(string_length), reinterpret_cast(&string_length) + )) { + Napi::TypeError::New(env, "Couldn't read C# string length").ThrowAsJavaScriptException(); return env.Null(); } - if (stringLength <= 0 || stringLength >= 4096) { + if (string_length <= 0 || string_length >= 4096) { return Napi::String::New(env, ""); } - // Allocate buffer for the string data - wchar_t *stringBuffer = new wchar_t[stringLength]; - - // Read the C# string data - if (!ReadProcessMemory(handle, - reinterpret_cast(address + sizeof(int) * 2), - stringBuffer, stringLength * sizeof(wchar_t), NULL)) { - std::cout << stringLength << std::endl; - delete[] stringBuffer; - Napi::TypeError::New(env, "Can't read C# string data") - .ThrowAsJavaScriptException(); + std::vector string_buffer(string_length); + if (!memory::read_buffer( + handle, address + sizeof(int) * 2, string_length * sizeof(wchar_t), reinterpret_cast(string_buffer.data()) + )) { + Napi::TypeError::New(env, "Couldn't read C# string data").ThrowAsJavaScriptException(); return env.Null(); } - // Convert the wide string to a JavaScript string - Napi::Value result = Napi::String::New( - env, reinterpret_cast(stringBuffer), stringLength); - - // Clean up - delete[] stringBuffer; - - return result; + return Napi::String::New(env, reinterpret_cast(string_buffer.data()), string_length); } -Napi::Value disablePowerThrottling(const Napi::CallbackInfo &args) { +Napi::Value disable_power_throttling(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() > 0) { - Napi::TypeError::New(env, "Wrong number of arguments") - .ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); return env.Null(); } - +#if defined(WIN32) || defined(_WIN32) if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) { return Napi::Number::From(env, 0); } @@ -458,64 +360,60 @@ Napi::Value disablePowerThrottling(const Napi::CallbackInfo &args) { state.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION; state.StateMask = 0; - SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &state, - sizeof(PROCESS_POWER_THROTTLING_STATE)); + SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &state, sizeof(PROCESS_POWER_THROTTLING_STATE)); using NtQueryTimerResolution_t = - NTSTATUS(WINAPI *)(PULONG MinimumResolution, PULONG MaximumResolution, - PULONG CurrentResolution); - using NtSetTimerResolution_t = NTSTATUS(WINAPI *)( - ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution); + NTSTATUS(WINAPI *)(PULONG MinimumResolution, PULONG MaximumResolution, PULONG CurrentResolution); + using NtSetTimerResolution_t = + NTSTATUS(WINAPI *)(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution); const auto ntdll = GetModuleHandleA("ntdll.dll"); if (!ntdll) { return Napi::Number::From(env, 0); } - const auto ntQueryTimerResolution = - (NtQueryTimerResolution_t)GetProcAddress(ntdll, "NtQueryTimerResolution"); - const auto ntSetTimerResolution = - (NtSetTimerResolution_t)GetProcAddress(ntdll, "NtSetTimerResolution"); + const auto nt_query_timer_resolution = (NtQueryTimerResolution_t)GetProcAddress(ntdll, "NtQueryTimerResolution"); + const auto nt_set_timer_resolution = (NtSetTimerResolution_t)GetProcAddress(ntdll, "NtSetTimerResolution"); - if (!ntQueryTimerResolution || !ntSetTimerResolution) { + if (!nt_query_timer_resolution || !nt_set_timer_resolution) { return Napi::Number::From(env, 0); } - ULONG minRes, maxRes, currRes; - if (ntQueryTimerResolution(&minRes, &maxRes, &currRes) != 0) { + ULONG min_res, max_res, curr_res; + if (nt_query_timer_resolution(&min_res, &max_res, &curr_res) != 0) { return Napi::Number::From(env, 0); } - if (ntSetTimerResolution(maxRes, TRUE, &currRes) != 0) { + if (nt_set_timer_resolution(max_res, TRUE, &curr_res) != 0) { return Napi::Number::From(env, 0); } - return Napi::Number::From(env, currRes); + return Napi::Number::From(env, curr_res); +#else + return Napi::Number::From(env, 1); +#endif } Napi::Object init(Napi::Env env, Napi::Object exports) { - exports["readByte"] = Napi::Function::New(env, readByte); - exports["readShort"] = Napi::Function::New(env, readShort); - exports["readInt"] = Napi::Function::New(env, readInt); - exports["readUInt"] = Napi::Function::New(env, readUInt); - exports["readFloat"] = Napi::Function::New(env, readFloat); - exports["readLong"] = Napi::Function::New(env, readLong); - exports["readDouble"] = Napi::Function::New(env, readDouble); - exports["readBuffer"] = Napi::Function::New(env, readBuffer); - exports["readCSharpString"] = Napi::Function::New(env, readCSharpString); - exports["scanSync"] = Napi::Function::New(env, scanSync); + exports["readByte"] = Napi::Function::New(env, read_byte); + exports["readShort"] = Napi::Function::New(env, read_short); + exports["readInt"] = Napi::Function::New(env, read_int); + exports["readUInt"] = Napi::Function::New(env, read_uint); + exports["readFloat"] = Napi::Function::New(env, read_float); + exports["readLong"] = Napi::Function::New(env, read_long); + exports["readDouble"] = Napi::Function::New(env, read_double); + exports["readBuffer"] = Napi::Function::New(env, read_buffer); + 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["openProcess"] = Napi::Function::New(env, openProcess); - exports["findProcesses"] = Napi::Function::New(env, findProcesses); - exports["getProcesses"] = Napi::Function::New(env, getProcesses); - exports["isProcessExist"] = Napi::Function::New(env, isProcessExist); - exports["getProcessPath"] = Napi::Function::New(env, getProcessPath); - exports["getProcessCommandLine"] = - Napi::Function::New(env, getProcessCommandLine); - exports["disablePowerThrottling"] = - Napi::Function::New(env, disablePowerThrottling); + 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); + exports["getProcessPath"] = Napi::Function::New(env, get_process_path); + exports["getProcessCommandLine"] = Napi::Function::New(env, get_process_command_line); + exports["disablePowerThrottling"] = Napi::Function::New(env, disable_power_throttling); return exports; } -NODE_API_MODULE(addon, init) \ No newline at end of file +NODE_API_MODULE(addon, init) diff --git a/packages/tsprocess/lib/memory/memory.h b/packages/tsprocess/lib/memory/memory.h new file mode 100644 index 00000000..2bfec495 --- /dev/null +++ b/packages/tsprocess/lib/memory/memory.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct MemoryRegion { + uintptr_t address; + std::size_t size; +}; + +namespace memory { + +std::vector query_regions(void *process); + +std::vector find_processes(const std::string_view process_name); + +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); + +bool read_buffer(void *process, uintptr_t address, std::size_t size, uint8_t *buffer); + +template +std::tuple read(void *process, uintptr_t address) { + T data; + const auto success = read_buffer(process, address, sizeof(T), reinterpret_cast(&data)); + return std::make_tuple(data, success); +} + +inline bool +scan(std::vector buffer, const std::vector signature, const std::vector 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 uintptr_t find_pattern(void *process, const std::vector signature, const std::vector mask) { + const auto regions = query_regions(process); + + for (auto ®ion : regions) { + auto buffer = std::vector(region.size); + if (!read_buffer(process, region.address, region.size, buffer.data())) { + continue; + } + + size_t offset; + if (!scan(buffer, signature, mask, offset)) { + continue; + } + + return region.address + offset; + } + + return 0; +} + +} // namespace memory diff --git a/packages/tsprocess/lib/memory/memory_linux.cc b/packages/tsprocess/lib/memory/memory_linux.cc new file mode 100644 index 00000000..111f8f80 --- /dev/null +++ b/packages/tsprocess/lib/memory/memory_linux.cc @@ -0,0 +1,117 @@ +#ifdef __unix__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "memory.h" + +namespace { + +std::string read_file(const std::string &path) { + std::ifstream file(path, std::ios::binary); + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + return content; +} + +} // namespace + +std::vector memory::find_processes(const std::string_view process_name) { + std::vector process_ids; + const auto dir = opendir("/proc"); + if (dir) { + dirent *entry; + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_type == DT_DIR) { + std::string pid_str(entry->d_name); + if (std::all_of(pid_str.begin(), pid_str.end(), isdigit)) { + const auto pid = std::stoi(pid_str); + const auto cmdline_path = "/proc/" + pid_str + "/comm"; + const auto cmdline = read_file(cmdline_path); + if (cmdline.find(process_name) != std::string::npos) { + process_ids.push_back(pid); + } + } + } + } + closedir(dir); + } + return process_ids; +} + +void *memory::open_process(uint32_t id) { + return reinterpret_cast(id); +} + +bool memory::is_process_exist(void *process) { + const auto pid = reinterpret_cast(process); + struct stat sts; + const auto proc_path = "/proc/" + std::to_string(pid); + if (stat(proc_path.c_str(), &sts) == -1 && errno == ENOENT) { + return false; + } + return true; +} + +std::string memory::get_process_path(void *process) { + const auto pid = reinterpret_cast(process); + const auto path = "/proc/" + std::to_string(pid) + "/exe"; + char buf[PATH_MAX]; + const auto len = readlink(path.c_str(), buf, sizeof(buf) - 1); + if (len != -1) { + buf[len] = '\0'; + return std::string(buf); + } + return ""; +} + +std::string memory::get_process_command_line(void *process) { + const auto pid = reinterpret_cast(process); + return read_file("/proc/" + std::to_string(pid) + "/cmdline"); +} + +bool memory::read_buffer(void *process, uintptr_t address, std::size_t size, uint8_t *buffer) { + const auto pid = reinterpret_cast(process); + + iovec local_iov{buffer, size}; + iovec remote_iov{reinterpret_cast(address), size}; + + return process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0) == size; +} + +std::vector memory::query_regions(void *process) { + std::vector regions; + + const auto pid = reinterpret_cast(process); + const auto maps_path = "/proc/" + std::to_string(pid) + "/maps"; + std::ifstream maps_file(maps_path); + if (!maps_file.is_open()) { + return regions; + } + + std::string line; + while (std::getline(maps_file, line)) { + MemoryRegion region; + + const auto first_space_pos = line.find(' '); + const auto address_range = line.substr(0, first_space_pos); + + const auto dash_pos = address_range.find('-'); + region.address = std::stoull(address_range.substr(0, dash_pos), nullptr, 16); + const auto end_address = std::stoull(address_range.substr(dash_pos + 1), nullptr, 16); + region.size = end_address - region.address; + + regions.push_back(region); + } + + return regions; +} + +#endif diff --git a/packages/tsprocess/lib/memory/memory_windows.cc b/packages/tsprocess/lib/memory/memory_windows.cc new file mode 100644 index 00000000..444b5094 --- /dev/null +++ b/packages/tsprocess/lib/memory/memory_windows.cc @@ -0,0 +1,112 @@ +#if defined(WIN32) || defined(_WIN32) + +// clang-format off +#include +#include +// clang-format on + +#include +#include +#include +#include +#include +#include +#include "memory.h" + +#pragma comment(lib, "Psapi.lib") +#pragma comment(lib, "ntdll.lib") + +bool memory::read_buffer(void *process, uintptr_t address, std::size_t size, uint8_t *buffer) { + return ReadProcessMemory(process, reinterpret_cast(address), buffer, size, 0) == 1; +} + +std::vector memory::query_regions(void *process) { + std::vector regions; + + 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) { + continue; + } + + if (info.Protect == PAGE_EXECUTE_READWRITE || info.Protect == PAGE_READWRITE) { + regions.push_back(MemoryRegion{reinterpret_cast(info.BaseAddress), info.RegionSize}); + } + } + + return regions; +} + +std::vector memory::find_processes(const std::string_view process_name) { + PROCESSENTRY32 processEntry; + processEntry.dwSize = sizeof(PROCESSENTRY32); + + std::vector processes; + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + if (Process32First(snapshot, &processEntry)) { + do { + if (process_name == processEntry.szExeFile) { + processes.push_back(processEntry.th32ProcessID); + } + } while (Process32Next(snapshot, &processEntry)); + } + + CloseHandle(snapshot); + + return processes; +} + +void *memory::open_process(uint32_t id) { + return OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); +} + +bool memory::is_process_exist(void *handle) { + DWORD returnCode{}; + if (GetExitCodeProcess(handle, &returnCode)) { + return returnCode == STILL_ACTIVE; + } + return false; +} + +std::string memory::get_process_path(void *handle) { + char filePath[MAX_PATH]; + GetModuleFileNameExA(handle, NULL, filePath, MAX_PATH); + + return filePath; +} + +std::string memory::get_process_command_line(void *process) { + std::string commandLine; + + PROCESS_BASIC_INFORMATION pbi = {}; + NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &pbi, sizeof(pbi), NULL); + if (status != 0) { + std::cerr << "failed to query the process, error: " << status << std::endl; + } else { + PEB peb = {}; + if (!ReadProcessMemory(process, pbi.PebBaseAddress, &peb, sizeof(peb), NULL)) { + DWORD err = GetLastError(); + std::cerr << "failed to read the process PEB, error: " << err << std::endl; + } else { + RTL_USER_PROCESS_PARAMETERS params = {}; + if (!ReadProcessMemory(process, peb.ProcessParameters, ¶ms, sizeof(params), NULL)) { + DWORD err = GetLastError(); + std::cerr << "failed to read the process params, error: " << err << std::endl; + } else { + UNICODE_STRING &commandLineArgs = params.CommandLine; + std::vector buffer(commandLineArgs.Length / sizeof(WCHAR)); + if (!ReadProcessMemory(process, commandLineArgs.Buffer, buffer.data(), commandLineArgs.Length, NULL)) { + DWORD err = GetLastError(); + std::cerr << "failed to read the process command line, error: " << err << std::endl; + } else { + commandLine = std::string(buffer.begin(), buffer.end()); + } + } + } + } + + return commandLine; +} + +#endif diff --git a/packages/tsprocess/lib/process.h b/packages/tsprocess/lib/process.h deleted file mode 100644 index 2e3517f9..00000000 --- a/packages/tsprocess/lib/process.h +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once - -// clang-format off -#include -#include -// clang-format on -#include -#include -#include -#include - -#include -#include - -#pragma comment(lib, "Psapi.lib") -#pragma comment(lib, "ntdll.lib") - -namespace memory { - -template -inline std::tuple read(HANDLE hProcess, uintptr_t address) { - T value; - auto result = ReadProcessMemory(hProcess, reinterpret_cast(address), - &value, sizeof(T), 0); - return {value, result}; -} - -inline bool read_buffer(HANDLE hProcess, uintptr_t address, size_t size, - char *dstBuffer) { - return ReadProcessMemory(hProcess, reinterpret_cast(address), - dstBuffer, size, 0) == 1; -} - -inline uintptr_t get_base_address(HANDLE process) { - MODULEINFO mi; - if (GetModuleInformation(process, nullptr, &mi, sizeof(mi))) - return (uintptr_t)mi.EntryPoint; - return 0; -} - -inline uint32_t scan(const std::vector buffer, - const std::vector signature) { - for (int i = 0; i + signature.size() <= buffer.size(); i++) { - bool found = true; - for (int j = 0; j < signature.size(); j++) { - if (buffer[i + j] != signature[j] && signature[j] != 0x00) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - - return -1; -} - -static std::vector regions; -static bool regionFlag; - -inline void cache_regions(HANDLE process) { - regions.clear(); - uintptr_t address = 0; - MEMORY_BASIC_INFORMATION mbi; - while (VirtualQueryEx(process, reinterpret_cast(address), &mbi, - sizeof(mbi))) { - if (mbi.Protect == PAGE_EXECUTE_READWRITE || - mbi.Protect == PAGE_READWRITE) { - regions.push_back(mbi); - } - - address = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; - } -} - -inline DWORD find_pattern(HANDLE process, const std::vector signature, - bool refresh = false, int baseAddress = 0) { - if (refresh) - regionFlag = false; - - if (!regionFlag) { - cache_regions(process); - regionFlag = true; - } - - MEMORY_BASIC_INFORMATION mbi; - auto size = sizeof(mbi); - - for (auto ®ion : regions) { - auto regionSize = region.RegionSize; - auto regionAddress = (uintptr_t)region.BaseAddress; - - if (regionAddress < baseAddress) - continue; - - auto buffer = std::vector(regionSize); - ReadProcessMemory(process, (void *)regionAddress, buffer.data(), regionSize, - 0); - - auto offset = scan(buffer, signature); - if (offset != -1) { - return regionAddress + offset; - } - } - - return 0; -} - -inline std::vector find_processes(const std::string &process_name) { - PROCESSENTRY32 processEntry; - processEntry.dwSize = sizeof(PROCESSENTRY32); - - std::vector processes; - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - if (Process32First(snapshot, &processEntry)) { - do { - if (process_name == processEntry.szExeFile) { - processes.push_back(processEntry.th32ProcessID); - } - } while (Process32Next(snapshot, &processEntry)); - } - - CloseHandle(snapshot); - - return processes; -} - -inline HANDLE open_process(uint32_t id) { - return OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); -} - -inline bool is_process_exist(HANDLE handle) { - DWORD returnCode{}; - if (GetExitCodeProcess(handle, &returnCode)) { - return returnCode == STILL_ACTIVE; - } - return false; -} - -inline std::string get_process_path(HANDLE handle) { - char filePath[MAX_PATH]; - GetModuleFileNameExA(handle, NULL, filePath, MAX_PATH); - - return filePath; -} - -inline std::wstring get_proc_command_line(HANDLE process) -{ - std::wstring commandLine; - - // Get the address of the PEB - PROCESS_BASIC_INFORMATION pbi = {}; - NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &pbi, sizeof(pbi), NULL); - if (status != 0) - { - std::cerr << "failed to query the process, error: " << status << std::endl; - } - else - { - // Get the address of the process parameters in the PEB - PEB peb = {}; - if (!ReadProcessMemory(process, pbi.PebBaseAddress, &peb, sizeof(peb), NULL)) - { - DWORD err = GetLastError(); - std::cerr << "failed to read the process PEB, error: " << err << std::endl; - } - else - { - // Get the command line arguments from the process parameters - RTL_USER_PROCESS_PARAMETERS params = {}; - if (!ReadProcessMemory(process, peb.ProcessParameters, ¶ms, sizeof(params), NULL)) - { - DWORD err = GetLastError(); - std::cerr << "failed to read the process params, error: " << err << std::endl; - } - else - { - UNICODE_STRING &commandLineArgs = params.CommandLine; - std::vector buffer(commandLineArgs.Length / sizeof(WCHAR)); - if (!ReadProcessMemory(process, commandLineArgs.Buffer, buffer.data(), commandLineArgs.Length, NULL)) - { - DWORD err = GetLastError(); - std::cerr << "failed to read the process command line, error: " << err << std::endl; - } - else - { - commandLine.assign(buffer.data(), buffer.size()); - } - } - } - } - - return commandLine; -} - -} // namespace memory \ No newline at end of file diff --git a/packages/tsprocess/package.json b/packages/tsprocess/package.json index b855d3f1..8a6ece6d 100644 --- a/packages/tsprocess/package.json +++ b/packages/tsprocess/package.json @@ -16,5 +16,8 @@ "lib", "dist", "binding.gyp" - ] + ], + "devDependencies": { + "node-api-headers": "^1.2.0" + } } diff --git a/packages/tsprocess/src/process.ts b/packages/tsprocess/src/process.ts index ba82a648..864015a8 100644 --- a/packages/tsprocess/src/process.ts +++ b/packages/tsprocess/src/process.ts @@ -29,7 +29,11 @@ export class Process { } get path(): string { - return ProcessUtils.getProcessPath(this.handle); + if (process.platform === 'win32') { + return ProcessUtils.getProcessPath(this.handle); + } + + return this.getProcessCommandLine().split(':')[1].replace(/\\/g, '/'); } getProcessCommandLine(): string { @@ -90,68 +94,50 @@ export class Process { return ProcessUtils.readBuffer(this.handle, address, size); } - scanSync( - pattern: string, - refresh: boolean = false, - baseAddress: number = 0 - ): number { - const buffer = Buffer.from( - pattern - .split(' ') - .map((x) => (x === '??' ? '00' : x)) - .join(''), + scanSync(pattern: string): number { + const bytes = pattern.split(' '); + const signature = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : x)).join(''), + 'hex' + ); + const mask = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : '01')).join(''), 'hex' ); - return ProcessUtils.scanSync(this.handle, baseAddress, buffer, refresh); + return ProcessUtils.scanSync(this.handle, signature, mask); } - scan( - pattern: string, - callback: (address: number) => void, - refresh: boolean = false, - baseAddress: number = 0 - ): void { - const buffer = Buffer.from( - pattern - .split(' ') - .map((x) => (x === '??' ? '00' : x)) - .join(''), + scan(pattern: string, callback: (address: number) => void): void { + const bytes = pattern.split(' '); + const signature = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : x)).join(''), + 'hex' + ); + const mask = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : '01')).join(''), 'hex' ); - ProcessUtils.scan(this.handle, baseAddress, buffer, refresh, callback); + ProcessUtils.scan(this.handle, signature, mask, callback); } - scanAsync( - pattern: string, - refresh: boolean = false, - baseAddress: number = 0 - ): Promise { - const buffer = Buffer.from( - pattern - .split(' ') - .map((x) => (x === '??' ? '00' : x)) - .join(''), + scanAsync(pattern: string): Promise { + const bytes = pattern.split(' '); + const signature = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : x)).join(''), + 'hex' + ); + const mask = Buffer.from( + bytes.map((x) => (x === '??' ? '00' : '01')).join(''), 'hex' ); - return new Promise((resolve, reject) => { try { - ProcessUtils.scan( - this.handle, - baseAddress, - buffer, - refresh, - resolve - ); + ProcessUtils.scan(this.handle, signature, mask, resolve); } catch (e) { reject(e); } }); } - - static getProcesses(): Array { - return ProcessUtils.getProcesses(); - } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c4f27a2..2d43a064 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,6 +160,10 @@ importers: node-gyp: specifier: 10.0.1 version: 10.0.1 + devDependencies: + node-api-headers: + specifier: ^1.2.0 + version: 1.2.0 packages/updater: dependencies: @@ -3300,6 +3304,10 @@ packages: resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==} dev: false + /node-api-headers@1.2.0: + resolution: {integrity: sha512-L9AiEkBfgupC0D/LsudLPOhzy/EdObsp+FHyL1zSK0kKv5FDA9rJMoRz8xd+ojxzlqfg0tTZm2h8ot2nS7bgRA==} + dev: true + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} From d374f01da5dc7071eb3ebe784b24fed0a2333653 Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Thu, 1 Aug 2024 11:08:36 +0300 Subject: [PATCH 02/17] ci: enable ubuntu --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3c907413..eaab0c9c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,7 +27,7 @@ jobs: build: strategy: matrix: - os: [ windows-latest ] + os: [ windows-latest, ubuntu-latest ] runs-on: ${{ matrix.os }} From aadae9a61e7399566e9ce568e06364c510af9341 Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Thu, 1 Aug 2024 11:09:34 +0300 Subject: [PATCH 03/17] ci: upload artifact --- .github/workflows/deploy.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index eaab0c9c..df28260e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -67,13 +67,13 @@ jobs: path: packages/tosu/dist/tosu.exe } - # - name: 🚀 - Upload artifacts for linux - # if: matrix.os == 'ubuntu-latest' - # uses: actions/upload-artifact@v3 - # with: { - # name: "${{ steps.set-artifact-name.outputs.ARTIFACT_NAME }}", - # path: packages/tosu/dist/tosu.exe - # } + - name: 🚀 - Upload artifacts for linux + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v3 + with: { + name: "${{ steps.set-artifact-name.outputs.ARTIFACT_NAME }}", + path: packages/tosu/dist/tosu.exe + } # - name: 🚀 - Upload artifacts for mac # if: matrix.os == 'macos-latest' From ea815319963851eac1991574c47c34947619af80 Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Thu, 1 Aug 2024 12:00:47 +0300 Subject: [PATCH 04/17] ci: improve linux ci --- .github/workflows/deploy.yml | 19 ++++++++++++------- package.json | 3 ++- packages/tosu/package.json | 3 ++- packages/tosu/{pkg.json => pkg.linux.json} | 4 ++-- packages/tosu/pkg.win.json | 14 ++++++++++++++ 5 files changed, 32 insertions(+), 11 deletions(-) rename packages/tosu/{pkg.json => pkg.linux.json} (89%) create mode 100644 packages/tosu/pkg.win.json diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df28260e..09ea3a82 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -56,14 +56,19 @@ jobs: - name: 🛠️ - Install Deps run: npm install -g pnpm@^8 && pnpm install --frozen-lockfile - - name: 📦 - Build - run: pnpm build + - name: 📦 - Build (windows) + if: matrix.os == "windows-latest" + run: pnpm build:win + + - name: 📦 - Build (linux) + if: matrix.os == "ubuntu-latest" + run: pnpm build:linux - name: 🚀 - Upload artifacts for windows if: matrix.os == 'windows-latest' uses: actions/upload-artifact@v4 with: { - name: "tosu-${{ matrix.os }}-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", + name: "tosu-windows-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", path: packages/tosu/dist/tosu.exe } @@ -71,9 +76,9 @@ jobs: if: matrix.os == 'ubuntu-latest' uses: actions/upload-artifact@v3 with: { - name: "${{ steps.set-artifact-name.outputs.ARTIFACT_NAME }}", - path: packages/tosu/dist/tosu.exe - } + name: "tosu-linux-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", + path: packages/tosu/dist/tosu + } # - name: 🚀 - Upload artifacts for mac # if: matrix.os == 'macos-latest' @@ -81,4 +86,4 @@ jobs: # with: { # name: "${{ steps.set-artifact-name.outputs.ARTIFACT_NAME }}", # path: packages/tosu/dist/tosu.exe - # } \ No newline at end of file + # } diff --git a/package.json b/package.json index 6988335f..b8e6566b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "prepare": "husky install", "start": "pnpm run -C packages/tosu run:dev", - "build": "pnpm run -C packages/tosu compile", + "build:win": "pnpm run -C packages/tosu compile:win", + "build:linux": "pnpm run -C packages/tosu compile:linux", "release": "standard-version", "prettier:fix": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\"", "prettier:ci": "prettier --check \"**/*.{js,jsx,ts,tsx,css}\"", diff --git a/packages/tosu/package.json b/packages/tosu/package.json index c7cf0f1d..06f06601 100644 --- a/packages/tosu/package.json +++ b/packages/tosu/package.json @@ -9,7 +9,8 @@ "ts:compile": "ncc build src/index.ts -o dist -m -d", "run:dev": "pnpm run genver && pnpm run ts:run src/index.ts", "compile:prepare-htmls": "cp -rf node_modules/@tosu/server/assets ./dist", - "compile": "pnpm run genver && pnpm run ts:compile && pnpm run compile:prepare-htmls && pkg --output dist/tosu.exe --debug --config pkg.json --compress brotli dist/index.js && pnpm run ts:run src/postBuild.ts" + "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" }, "dependencies": { "@tosu/common": "workspace:*", diff --git a/packages/tosu/pkg.json b/packages/tosu/pkg.linux.json similarity index 89% rename from packages/tosu/pkg.json rename to packages/tosu/pkg.linux.json index e536bcae..42add4b7 100644 --- a/packages/tosu/pkg.json +++ b/packages/tosu/pkg.linux.json @@ -8,7 +8,7 @@ "dist/assets/**/*" ], "targets": [ - "node20-win-x64" + "node20-linux-x64" ], "outputPath": "dist" -} \ No newline at end of file +} diff --git a/packages/tosu/pkg.win.json b/packages/tosu/pkg.win.json new file mode 100644 index 00000000..7ba0c535 --- /dev/null +++ b/packages/tosu/pkg.win.json @@ -0,0 +1,14 @@ +{ + "scripts": "dist/**/*.js", + "assets": [ + "dist/**/*.node", + "dist/**/*.wasm", + "dist/target/**/*", + "dist/_version.js", + "dist/assets/**/*" + ], + "targets": [ + "node20-win-x64" + ], + "outputPath": "dist" +} From 739d0815047d1f757ff541add8592f3e4a264996 Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Thu, 1 Aug 2024 12:04:51 +0300 Subject: [PATCH 05/17] feat: semicolon --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 09ea3a82..258bbe46 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -65,7 +65,7 @@ jobs: run: pnpm build:linux - name: 🚀 - Upload artifacts for windows - if: matrix.os == 'windows-latest' + if: matrix.os == "windows-latest" uses: actions/upload-artifact@v4 with: { name: "tosu-windows-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", @@ -73,7 +73,7 @@ jobs: } - name: 🚀 - Upload artifacts for linux - if: matrix.os == 'ubuntu-latest' + if: matrix.os == "ubuntu-latest" uses: actions/upload-artifact@v3 with: { name: "tosu-linux-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", From b71561a316d2d37546cd1e77ffd85ef92b9fb6c2 Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Thu, 1 Aug 2024 12:18:28 +0300 Subject: [PATCH 06/17] ci: edit if --- .github/workflows/deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 258bbe46..3e2f329e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -57,15 +57,15 @@ jobs: run: npm install -g pnpm@^8 && pnpm install --frozen-lockfile - name: 📦 - Build (windows) - if: matrix.os == "windows-latest" + if: ${{ matrix.os == 'windows-latest' }} run: pnpm build:win - name: 📦 - Build (linux) - if: matrix.os == "ubuntu-latest" + if: ${{ matrix.os == 'ubuntu-latest' }} run: pnpm build:linux - name: 🚀 - Upload artifacts for windows - if: matrix.os == "windows-latest" + if: ${{ matrix.os == 'windows-latest' }} uses: actions/upload-artifact@v4 with: { name: "tosu-windows-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", @@ -73,7 +73,7 @@ jobs: } - name: 🚀 - Upload artifacts for linux - if: matrix.os == "ubuntu-latest" + if: ${{ matrix.os == 'ubuntu-latest' }} uses: actions/upload-artifact@v3 with: { name: "tosu-linux-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", From 6c083e5f8af6dc665fa28cf55056225c1707802b Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Thu, 1 Aug 2024 14:44:45 +0300 Subject: [PATCH 07/17] ci: bump upload-artifact to v4 --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3e2f329e..a582b60e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -74,7 +74,7 @@ jobs: - name: 🚀 - Upload artifacts for linux if: ${{ matrix.os == 'ubuntu-latest' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: { name: "tosu-linux-${{ steps.set-pr-sha.outputs.SHORT_PR_SHA || github.ref_name }}", path: packages/tosu/dist/tosu From 5b952e41d0725b1bd0278c09fb44dd472c411b87 Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:01:51 +0300 Subject: [PATCH 08/17] fix: Check sudo permissions on linux --- packages/tosu/src/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/tosu/src/index.ts b/packages/tosu/src/index.ts index 1630c085..48ef89e1 100644 --- a/packages/tosu/src/index.ts +++ b/packages/tosu/src/index.ts @@ -9,6 +9,15 @@ import { InstanceManager } from './objects/instanceManager/instanceManager'; const currentVersion = require(process.cwd() + '/_version.js'); (async () => { + if ( + process.platform === 'linux' && + process.getuid && + process.getuid() !== 0 + ) { + wLogger.error(`Hello, please run tosu with sudo`); + process.exit(1); + } + wLogger.info(`Starting tosu v${currentVersion}`); Process.disablePowerThrottling(); From 06fd65b09b47888e889851b51402b4f030ee1e48 Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:02:09 +0300 Subject: [PATCH 09/17] chore: linux process path debug --- packages/tsprocess/src/process.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/tsprocess/src/process.ts b/packages/tsprocess/src/process.ts index 864015a8..14004262 100644 --- a/packages/tsprocess/src/process.ts +++ b/packages/tsprocess/src/process.ts @@ -30,10 +30,17 @@ export class Process { get path(): string { if (process.platform === 'win32') { + console.log('PATH-win', ProcessUtils.getProcessPath(this.handle)); return ProcessUtils.getProcessPath(this.handle); } - return this.getProcessCommandLine().split(':')[1].replace(/\\/g, '/'); + const commandLine = this.getProcessCommandLine(); + console.log('PATH-linux', commandLine); + + if (commandLine.includes(':')) { + return commandLine.split(':')[1].replace(/\\/g, '/'); + } + return commandLine; } getProcessCommandLine(): string { From 3fdd6205033092aac0cd63ffe45b818635236bdb Mon Sep 17 00:00:00 2001 From: Cherry Date: Fri, 2 Aug 2024 04:10:03 +0300 Subject: [PATCH 10/17] add more debug for linux --- packages/tsprocess/lib/functions.cc | 13 +++++++++++++ packages/tsprocess/lib/memory/memory.h | 1 + packages/tsprocess/lib/memory/memory_linux.cc | 12 ++++++++++++ packages/tsprocess/lib/memory/memory_windows.cc | 4 ++++ packages/tsprocess/src/process.ts | 6 ++++++ 5 files changed, 36 insertions(+) diff --git a/packages/tsprocess/lib/functions.cc b/packages/tsprocess/lib/functions.cc index 82e1525f..e29b1c85 100644 --- a/packages/tsprocess/lib/functions.cc +++ b/packages/tsprocess/lib/functions.cc @@ -394,6 +394,18 @@ Napi::Value disable_power_throttling(const Napi::CallbackInfo &args) { #endif } +Napi::Value get_process_cwd(const Napi::CallbackInfo &args) { + Napi::Env env = args.Env(); + if (args.Length() > 1) { + Napi::TypeError::New(env, "Wrong number of arguments").ThrowAsJavaScriptException(); + return env.Null(); + } + + void *handle = reinterpret_cast(args[0].As().Int64Value()); + + return Napi::Number::From(env, memory::get_process_cwd(handle)); +} + Napi::Object init(Napi::Env env, Napi::Object exports) { exports["readByte"] = Napi::Function::New(env, read_byte); exports["readShort"] = Napi::Function::New(env, read_short); @@ -411,6 +423,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) { exports["isProcessExist"] = Napi::Function::New(env, is_process_exist); exports["getProcessPath"] = Napi::Function::New(env, get_process_path); exports["getProcessCommandLine"] = Napi::Function::New(env, get_process_command_line); + exports["getProcessCwd"] = Napi::Function::New(env, get_process_cwd); exports["disablePowerThrottling"] = Napi::Function::New(env, disable_power_throttling); return exports; diff --git a/packages/tsprocess/lib/memory/memory.h b/packages/tsprocess/lib/memory/memory.h index 2bfec495..273e7e58 100644 --- a/packages/tsprocess/lib/memory/memory.h +++ b/packages/tsprocess/lib/memory/memory.h @@ -21,6 +21,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); bool read_buffer(void *process, uintptr_t address, std::size_t size, uint8_t *buffer); diff --git a/packages/tsprocess/lib/memory/memory_linux.cc b/packages/tsprocess/lib/memory/memory_linux.cc index 111f8f80..01fca103 100644 --- a/packages/tsprocess/lib/memory/memory_linux.cc +++ b/packages/tsprocess/lib/memory/memory_linux.cc @@ -77,6 +77,18 @@ std::string memory::get_process_command_line(void *process) { return read_file("/proc/" + std::to_string(pid) + "/cmdline"); } +std::string memory::get_process_cwd(void *process) { + const auto pid = reinterpret_cast(process); + const auto path = "/proc/" + std::to_string(pid) + "/cwd"; + char buf[PATH_MAX]; + const auto len = readlink(path.c_str(), buf, sizeof(buf) - 1); + if (len != -1) { + buf[len] = '\0'; + return std::string(buf); + } + return ""; +} + bool memory::read_buffer(void *process, uintptr_t address, std::size_t size, uint8_t *buffer) { const auto pid = reinterpret_cast(process); diff --git a/packages/tsprocess/lib/memory/memory_windows.cc b/packages/tsprocess/lib/memory/memory_windows.cc index 444b5094..f5ec8c31 100644 --- a/packages/tsprocess/lib/memory/memory_windows.cc +++ b/packages/tsprocess/lib/memory/memory_windows.cc @@ -76,6 +76,10 @@ std::string memory::get_process_path(void *handle) { return filePath; } +std::string memory::get_process_cwd(void* process) { + return ""; +} + std::string memory::get_process_command_line(void *process) { std::string commandLine; diff --git a/packages/tsprocess/src/process.ts b/packages/tsprocess/src/process.ts index 14004262..f26a0368 100644 --- a/packages/tsprocess/src/process.ts +++ b/packages/tsprocess/src/process.ts @@ -35,7 +35,9 @@ export class Process { } const commandLine = this.getProcessCommandLine(); + console.log('PATH-linux', commandLine); + console.log('cwd-linux', this.getProcessCwd()); if (commandLine.includes(':')) { return commandLine.split(':')[1].replace(/\\/g, '/'); @@ -47,6 +49,10 @@ export class Process { return ProcessUtils.getProcessCommandLine(this.handle); } + getProcessCwd(): string { + return ProcessUtils.getProcessCwd(this.handle); + } + readByte(address: number): number { return ProcessUtils.readByte(this.handle, address); } From 2146ad8efb800b7ded40dbd5bf9e7239b14cd3f8 Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:56:52 +0300 Subject: [PATCH 11/17] fix: Fix path to osu!.exe for linux --- packages/tsprocess/src/process.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/tsprocess/src/process.ts b/packages/tsprocess/src/process.ts index f26a0368..019d6a56 100644 --- a/packages/tsprocess/src/process.ts +++ b/packages/tsprocess/src/process.ts @@ -1,3 +1,5 @@ +import path from 'path'; + import ProcessUtils from '.'; export interface ProcessInfo { @@ -30,19 +32,10 @@ export class Process { get path(): string { if (process.platform === 'win32') { - console.log('PATH-win', ProcessUtils.getProcessPath(this.handle)); return ProcessUtils.getProcessPath(this.handle); } - const commandLine = this.getProcessCommandLine(); - - console.log('PATH-linux', commandLine); - console.log('cwd-linux', this.getProcessCwd()); - - if (commandLine.includes(':')) { - return commandLine.split(':')[1].replace(/\\/g, '/'); - } - return commandLine; + return path.join(this.getProcessCwd(), 'osu!.exe'); } getProcessCommandLine(): string { From 39373bdba5d289d00738b13cb0ec9549776e4800 Mon Sep 17 00:00:00 2001 From: Cherry Date: Sat, 3 Aug 2024 14:17:45 +0300 Subject: [PATCH 12/17] fix: remove sudo enforcement --- .husky/pre-commit | 0 packages/tosu/src/index.ts | 9 ------ .../objects/instanceManager/osuInstance.ts | 8 ++--- packages/tsprocess/binding.gyp | 6 ---- packages/tsprocess/lib/functions.cc | 30 ++++++------------- packages/tsprocess/lib/logger.h | 23 ++++++++++++++ packages/tsprocess/lib/memory/memory_linux.cc | 12 ++++++-- packages/tsprocess/src/process.ts | 6 ++-- 8 files changed, 47 insertions(+), 47 deletions(-) mode change 100644 => 100755 .husky/pre-commit create mode 100644 packages/tsprocess/lib/logger.h diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/packages/tosu/src/index.ts b/packages/tosu/src/index.ts index 48ef89e1..1630c085 100644 --- a/packages/tosu/src/index.ts +++ b/packages/tosu/src/index.ts @@ -9,15 +9,6 @@ import { InstanceManager } from './objects/instanceManager/instanceManager'; const currentVersion = require(process.cwd() + '/_version.js'); (async () => { - if ( - process.platform === 'linux' && - process.getuid && - process.getuid() !== 0 - ) { - wLogger.error(`Hello, please run tosu with sudo`); - process.exit(1); - } - wLogger.info(`Starting tosu v${currentVersion}`); Process.disablePowerThrottling(); diff --git a/packages/tosu/src/objects/instanceManager/osuInstance.ts b/packages/tosu/src/objects/instanceManager/osuInstance.ts index 32a41e16..5ecba106 100644 --- a/packages/tosu/src/objects/instanceManager/osuInstance.ts +++ b/packages/tosu/src/objects/instanceManager/osuInstance.ts @@ -260,7 +260,7 @@ export class OsuInstance { } if (!allTimesData.GameFolder) { - allTimesData.setGameFolder(path.join(this.path, '..')); + allTimesData.setGameFolder(this.path); // condition when user have different BeatmapDirectory in osu! config if (fs.existsSync(allTimesData.MemorySongsFolder)) { @@ -269,11 +269,7 @@ export class OsuInstance { ); } else { allTimesData.setSongsFolder( - path.join( - this.path, - '../', - allTimesData.MemorySongsFolder - ) + path.join(this.path, allTimesData.MemorySongsFolder) ); } } diff --git a/packages/tsprocess/binding.gyp b/packages/tsprocess/binding.gyp index b4c4fab7..38254fb7 100644 --- a/packages/tsprocess/binding.gyp +++ b/packages/tsprocess/binding.gyp @@ -7,12 +7,6 @@ 'dependencies': [" #include #include +#include "logger.h" #include "memory/memory.h" #if defined(WIN32) || defined(_WIN32) #include #endif -// https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf -template -std::string string_format(const std::string &format, Args... args) { - int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; - if (size_s <= 0) { - return std::string(); - } - auto size = static_cast(size_s); - std::unique_ptr buf(new char[size]); - std::snprintf(buf.get(), size, format.c_str(), args...); - return std::string(buf.get(), buf.get() + size - 1); -} - Napi::Value read_byte(const Napi::CallbackInfo &args) { Napi::Env env = args.Env(); if (args.Length() < 2) { @@ -31,7 +19,7 @@ Napi::Value read_byte(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read byte at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read byte at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -48,7 +36,7 @@ Napi::Value read_short(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read short at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read short at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -65,7 +53,7 @@ Napi::Value read_int(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read int at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read int at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -82,7 +70,7 @@ Napi::Value read_uint(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read uint at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read uint at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -99,7 +87,7 @@ Napi::Value read_float(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read float at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read float at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -116,7 +104,7 @@ Napi::Value read_long(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read long at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read long at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -133,7 +121,7 @@ Napi::Value read_double(const Napi::CallbackInfo &args) { auto address = args[1].As().Int64Value(); auto result = memory::read(handle, address); if (!std::get<1>(result)) { - Napi::TypeError::New(env, string_format("Couldn't read double at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read double at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } return Napi::Number::New(env, std::get<0>(result)); @@ -184,7 +172,7 @@ Napi::Value read_buffer(const Napi::CallbackInfo &args) { if (!result) { free(data); delete[] buffer; - Napi::TypeError::New(env, string_format("Couldn't read buffer at %x", address)).ThrowAsJavaScriptException(); + Napi::TypeError::New(env, logger::format("Couldn't read buffer at %x", address)).ThrowAsJavaScriptException(); return env.Null(); } diff --git a/packages/tsprocess/lib/logger.h b/packages/tsprocess/lib/logger.h new file mode 100644 index 00000000..e12bd115 --- /dev/null +++ b/packages/tsprocess/lib/logger.h @@ -0,0 +1,23 @@ +#include +#include + +namespace logger { + +template +std::string format(const std::string_view format, Args... args) { + const auto size = std::snprintf(nullptr, 0, format.data(), args...) + 1; + if (size <= 0) { + return {}; + } + auto buffer = std::make_unique(size); + std::snprintf(buffer.get(), size, format.data(), args...); + return std::string(buffer.get(), size - 1); +} + +template +void println(const std::string_view format, Args... args) { + std::printf(logger::format(format, args...).c_str()); + std::printf("\n"); +} + +} // namespace logger diff --git a/packages/tsprocess/lib/memory/memory_linux.cc b/packages/tsprocess/lib/memory/memory_linux.cc index 01fca103..cd9e12dc 100644 --- a/packages/tsprocess/lib/memory/memory_linux.cc +++ b/packages/tsprocess/lib/memory/memory_linux.cc @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include "../logger.h" #include "memory.h" namespace { @@ -95,7 +95,15 @@ bool memory::read_buffer(void *process, uintptr_t address, std::size_t size, uin iovec local_iov{buffer, size}; iovec remote_iov{reinterpret_cast(address), size}; - return process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0) == size; + const auto result_size = process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0); + + const auto success = result_size == size; + + if (!success && errno == EPERM) { + logger::println("failed to read address %x of size %x (consider trying to run with sudo)", address, size); + } + + return success; } std::vector memory::query_regions(void *process) { diff --git a/packages/tsprocess/src/process.ts b/packages/tsprocess/src/process.ts index 019d6a56..d1d42ee3 100644 --- a/packages/tsprocess/src/process.ts +++ b/packages/tsprocess/src/process.ts @@ -1,4 +1,4 @@ -import path from 'path'; +import { dirname as pathDirname } from 'path'; import ProcessUtils from '.'; @@ -32,10 +32,10 @@ export class Process { get path(): string { if (process.platform === 'win32') { - return ProcessUtils.getProcessPath(this.handle); + return pathDirname(ProcessUtils.getProcessPath(this.handle)); } - return path.join(this.getProcessCwd(), 'osu!.exe'); + return this.getProcessCwd(); } getProcessCommandLine(): string { From 35e9826e6f48a82172a9de0a5a1900a20d02beca Mon Sep 17 00:00:00 2001 From: Cherry Date: Mon, 5 Aug 2024 08:53:00 +0300 Subject: [PATCH 13/17] chore: filter out unnecessery regions --- packages/tsprocess/lib/memory/memory_linux.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/tsprocess/lib/memory/memory_linux.cc b/packages/tsprocess/lib/memory/memory_linux.cc index cd9e12dc..30190fde 100644 --- a/packages/tsprocess/lib/memory/memory_linux.cc +++ b/packages/tsprocess/lib/memory/memory_linux.cc @@ -127,8 +127,11 @@ std::vector memory::query_regions(void *process) { region.address = std::stoull(address_range.substr(0, dash_pos), nullptr, 16); const auto end_address = std::stoull(address_range.substr(dash_pos + 1), nullptr, 16); region.size = end_address - region.address; + const auto protections = line.substr(first_space_pos + 1, 5); - regions.push_back(region); + if (protections[0] == 'r' && protections[1] == 'w') { + regions.push_back(region); + } } return regions; From cf6d7584307b6df0cf10acaf05c43b79d70df67e Mon Sep 17 00:00:00 2001 From: Cherry Date: Tue, 6 Aug 2024 19:55:17 +0300 Subject: [PATCH 14/17] fix: update game time pattern --- .../tosu/src/entities/AllTimesData/index.ts | 32 +++---------------- .../objects/instanceManager/osuInstance.ts | 4 +++ packages/tosu/src/objects/memoryPatterns.ts | 4 ++- 3 files changed, 12 insertions(+), 28 deletions(-) diff --git a/packages/tosu/src/entities/AllTimesData/index.ts b/packages/tosu/src/entities/AllTimesData/index.ts index 9723fe01..28801c44 100644 --- a/packages/tosu/src/entities/AllTimesData/index.ts +++ b/packages/tosu/src/entities/AllTimesData/index.ts @@ -2,15 +2,7 @@ import { wLogger } from '@tosu/common'; import { AbstractEntity } from '@/entities/AbstractEntity'; -// NOTE: NOT AVAILABLE IN TOURNAMENT MODE!!!! -const GAME_TIME_PTR = { - pattern: '8B 35 ?? ?? ?? ?? 8B C6 B9', - offset: 0x2 -}; - export class AllTimesData extends AbstractEntity { - gameTimePtr: number = 0; - IsWatchingReplay: number = 0; isReplayUiHidden: boolean = false; ShowInterface: boolean = false; @@ -53,7 +45,8 @@ export class AllTimesData extends AbstractEntity { skinDataAddr, settingsClassAddr, canRunSlowlyAddr, - rulesetsAddr + rulesetsAddr, + gameTimePtr } = patterns.getPatterns([ 'statusPtr', 'menuModsPtr', @@ -61,7 +54,8 @@ export class AllTimesData extends AbstractEntity { 'skinDataAddr', 'settingsClassAddr', 'canRunSlowlyAddr', - 'rulesetsAddr' + 'rulesetsAddr', + 'gameTimePtr' ]); // [Status - 0x4] @@ -73,6 +67,7 @@ export class AllTimesData extends AbstractEntity { this.IsWatchingReplay = process.readByte( process.readInt(canRunSlowlyAddr + 0x46) ); + this.GameTime = process.readPointer(gameTimePtr); this.MemorySongsFolder = process.readSharpString( process.readInt( process.readInt( @@ -117,23 +112,6 @@ export class AllTimesData extends AbstractEntity { return; } - if ( - !this.osuInstance.isTourneyManager && - !this.osuInstance.isTourneySpectator - ) { - if (this.gameTimePtr === 0) { - this.gameTimePtr = await process.scanAsync( - GAME_TIME_PTR.pattern - ); - wLogger.debug('ATD(updateState) gameTimePtr area found'); - return; - } else { - this.GameTime = process.readPointer( - this.gameTimePtr + GAME_TIME_PTR.offset - ); - } - } - this.resetReportCount('ATD(updateState)'); } catch (exc) { this.reportError( diff --git a/packages/tosu/src/objects/instanceManager/osuInstance.ts b/packages/tosu/src/objects/instanceManager/osuInstance.ts index 5ecba106..e84652d0 100644 --- a/packages/tosu/src/objects/instanceManager/osuInstance.ts +++ b/packages/tosu/src/objects/instanceManager/osuInstance.ts @@ -82,6 +82,10 @@ const SCAN_PATTERNS: { spectatingUserPtr: { pattern: '8B 0D ?? ?? ?? ?? 85 C0 74 05 8B 50 30', offset: -0x4 + }, + gameTimePtr: { + pattern: 'A1 ?? ?? ?? ?? 89 46 04 8B D6 E8', + offset: 0x1 } }; diff --git a/packages/tosu/src/objects/memoryPatterns.ts b/packages/tosu/src/objects/memoryPatterns.ts index 9b90fb0a..b8c650f5 100644 --- a/packages/tosu/src/objects/memoryPatterns.ts +++ b/packages/tosu/src/objects/memoryPatterns.ts @@ -16,6 +16,7 @@ export interface PatternData { userProfilePtr: number; rawLoginStatusPtr: number; spectatingUserPtr: number; + gameTimePtr: number; } export class MemoryPatterns { @@ -40,7 +41,8 @@ export class MemoryPatterns { getAudioLengthPtr: 0, userProfilePtr: 0, rawLoginStatusPtr: 0, - spectatingUserPtr: 0 + spectatingUserPtr: 0, + gameTimePtr: 0 }; if (process.platform !== 'win32') { From 05219118734842acf5b0160f422471cd1a2e1241 Mon Sep 17 00:00:00 2001 From: Cherry Date: Wed, 7 Aug 2024 09:03:52 +0300 Subject: [PATCH 15/17] 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}`); From 057b33ca1973b1f5d7e779e80b953373efe45acb Mon Sep 17 00:00:00 2001 From: ck <21735205+cyperdark@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:28:33 +0300 Subject: [PATCH 16/17] chore: Fix update edgecase --- packages/updater/index.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/updater/index.ts b/packages/updater/index.ts index dac8f81a..10e578af 100644 --- a/packages/updater/index.ts +++ b/packages/updater/index.ts @@ -64,7 +64,7 @@ export const checkUpdates = async () => { config.currentVersion = currentVersion; config.updateVersion = versionName || currentVersion; - if (versionName === null) { + if (versionName === null || versionName === undefined) { wLogger.info(`Failed to check updates v${currentVersion}`); return new Error('Version the same'); @@ -90,12 +90,6 @@ 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}`); From 50243f8a7a50ad43281b807746aa17467a3c91a7 Mon Sep 17 00:00:00 2001 From: Mikhail Babynichev Date: Wed, 7 Aug 2024 11:58:48 +0300 Subject: [PATCH 17/17] chore: fix tab --- packages/tosu/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tosu/package.json b/packages/tosu/package.json index f6e267c7..06f06601 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:*",