|
lua, readmem, unicode, Считать из памяти строку в формате UTF-16 |
|
|
Aqualon |
20.2.2024, 2:34
|
Neophyte
Сообщений: 23
Регистрация: 21.3.2023 Группа: Пользователи Наличность: 0
Пользователь №: 20.503
Возраст: 22
|
Если вам просто надо спарсить utf8 строку из памяти, я для такого пользовался примерно таким ужасом, но сетапится этот ужас относительно просто Код -- либу с утф возьмите отсюда -- https://github.com/Stepets/utf8.lua utf8 = require('lua/utf8') -- Я могу ошибаться, но лично у меня этот файлик лежал по этому пути. -- Тут лежат функции для конвертации из utf8 в win кодировки и обратно require('lua/win-125x')
function utf8_from(t) local bytearr = {} for _, v in ipairs(t) do local utf8byte = v < 0 and (0xff + v + 1) or v table.insert(bytearr, utf8.char(utf8byte)) end return table.concat(bytearr) end
function parseUnicodeStringFromMemory(startAddr) -- максимальное количество символов, поставьте нужное вам, это чисто оградка. -- Если строка ваша null-terminated, вернёт он именно ту что вам нужна. local UPPER_BOUND = 20 local result = {}
for i = 1, UPPER_BOUND do local step = 0x2 * (i - 1)
local char = readmem(startAddr + step, 'w')
if char == 0 then break end
table.insert(result, char) end
return utf8_to_win(utf8_from(result)) end -- Пример использования в вашем случае, адрес взял у вас из топика local name = parseUnicodeStringFromMemory(0x07CCFFD8) log(name)
Безусловно, вышеописанный ужас не заменит нормальную поддержку ридмема с utf8, но это рабочее решение с минимумом дополнительных действий. Я со временем вообще ушёл от работы со строками, особенно с utf строками, для тех мест где нужна человеческая читаемость я замапил айдишники с вручную выписанными пояснениями в отдельном файлике с константами, это убирает кучу геморроя.
|
|
|
|
DarkMaster |
22.2.2024, 16:42
|
Модератор UOPilot
Сообщений: 9.568
Регистрация: 2.12.2008 Группа: Супермодераторы Наличность: 28530
Пользователь №: 11.279
|
Цитата один код поинт юникода может быть разложен на условные 3-4 байта Можно этот момент поподробнее? Там есть какие-то лимиты по страндарту? Я прямо _очень_ не хочу делать два буфера. Можно использовать один, раздавать непересекающиеся указатели, все кошерно. Но мне нужно astring закидывать в начало буфера. Соответственно, если я считаю кусок памяти в некоторый оффсет буфера, а потом у меня не влезет astring, то ресайз делать вообще не хорошо. Или может можно посчтитать len до копирования памяти из другого процесса (без инжектов)? Вообще идейно человек может считать n элементов любого типа, функция вернет первый элемент, в случае строки - вернет всю строку типа lua string. Но вот буфер я хочу оставить открытым для пользователя. Т.е. будет свободный доступ в виде массива к каждому считанному элементу. Соответственно будет удобно искать некторые структуры для которых сложно найти статичную цепочку указателей, возможность посимвольной переборки строк. Вобщем красота. Сообщение отредактировал DarkMaster - 22.2.2024, 16:46
--------------------
Скрипты UOPilot под заказ. Консультации по UOpilot 15$/час. Услуги Lua разработчика (не пилот, проекты, постоянка) Disсоrd: Kov____
|
|
|
|
Cockney |
22.2.2024, 20:40
|
Master
Сообщений: 1.402
Регистрация: 22.6.2013 Группа: Пользователи Наличность: 21878
Пользователь №: 16.156
|
Цитата Можно этот момент поподробнее? Там есть какие-то лимиты по страндарту? Ну, а какие там стандарты...из описания функция конвертит utf16 -> multi byte (до 4 байтов включительно на 1 символ в случае если конвертация в utf8 происходит, до 2 байт если конвертация в соотв. кодовую таблицу). Цитата Можно использовать один, раздавать непересекающиеся указатели, все кошерно. Но мне нужно astring закидывать в начало буфера. Соответственно, если я считаю кусок памяти в некоторый оффсет буфера, а потом у меня не влезет astring, то ресайз делать вообще не хорошо. Или может можно посчтитать len до копирования памяти из другого процесса (без инжектов)? Не осилил, особенно когда речь пошла про другой процесс. Похоже на реальный быдлокод. Цитата Вообще идейно человек может считать n элементов любого типа, функция вернет первый элемент, в случае строки - вернет всю строку типа lua string. Но вот буфер я хочу оставить открытым для пользователя. Т.е. будет свободный доступ в виде массива к каждому считанному элементу. Соответственно будет удобно искать некторые структуры для которых сложно найти статичную цепочку указателей, возможность посимвольной переборки строк. Вобщем красота. абстрактный конь в вакууме. какие элементы, какого типа, какие структуры с произвольным поиском. а как это еще упаковывается в MANAGED луа строку еще интереснее. В нормальных системах/языках/библиотеках принято оптимизировать за счет всяких пулов строк/буферов, различных кешей. А городить какие то там подбуферы без четкого понимания что куда и как ляжет и как это все сожрет компилятор/интерпретатор - себе дороже, Лучше сделать с реаллокациями но предсказуемым поведением + найти вектор оптимизации другой
|
|
|
|
DarkMaster |
13.3.2024, 18:12
|
Модератор UOPilot
Сообщений: 9.568
Регистрация: 2.12.2008 Группа: Супермодераторы Наличность: 28530
Пользователь №: 11.279
|
Цитата пока я только понял что ты просто не хочешь выделять несколько раз память и используешь один буффер под разные данные. опыт с финдимиджем показал, что malloc столько жрет за вызов, что я теперь готов на любые извращения лишь бы не выделять память лишний раз) Вопрос правда уже не в этом был =) Цитата чем копировать - не знаю, вряд ли есть что-то быстрее memcpy в терминах си. наверняка у луа есть такой аналог вообще вроде как можно через string.dump, но чет сомнительно, сделал через ffi.copy. Там спецом под строки есть вариант синтаксиса, но советовали точно не через него. Правда не думаю, что там будет какая-то фатальная разница. ffi.copy насколько я понимаю чуток обернутый memcopy и strcopy. Собственно поделка. mem = require"memory" mem.read(address, data_type[[, lenght], codepage] - чтение памяти процесса mem.write(data, address, data_type[, codepage]) - запись в память процесса mem.set_buffer_size(size) - установить размер буфера. mem.buffer_raw - указатель C на данные. read(address, data_type[[, lenght], codepage]) Считывает данные из памяти процесса. Возвращает первый считаный элемент. Если считывалася string или unicode - вернет полную строку. Для поэлементного/посимвольного доступа используйте указатель buffer_raw. address - адрес откуда производить чтение.. data_type - тип данных. Си тип, указывается в виде строки с учетом регистра (INT и int - два разных типа). Если вы объявили какой-либо пользовательский тип, то тоже будет будет его читать. Со структурами могут быть проблемы. Из коробки знает:
char short int long и т.д. допускается использование unsigned
Объявленные типы: BOOL BYTE WORD DWORD LPVOID LPCVOID LPDWORD PVOID HANDLE HWND LONG_PTR SIZE_T UINT WCHAR LPWSTR LPCCH LPCWCH CHAR LPSTR LPBOOL // must be "typedef BOOL far *LPBOOL;". far = error.
Так же отдельно создана возможность читать строки. Для этого необходимо указать тип: "string" - для ASCII строки. "unicode" - для строки в UTF-16. Строка будет преобразована в стандартный lua тип string (по сути ASCII). lenght - длина в количестве элементов (не в байтах). Необязательный параметр. Значение по умолчанию 1. codepage - кодовая страница для преобразования unicode строки. Необязательный параметр. Значение по умолчанию nil. write(data, address, data_type[, codepage]) data - данные для записи. Допускается использование массивов (кроме data_type string и unicode) address - адрес куда производится запись. data_type - тип данных. Си тип, указывается в виде строки с учетом регистра (INT и int - два разных типа). Если вы объявили какой-либо пользовательский тип, то тоже будет будет его читать. Со структурами могут быть проблемы. Из коробки знает:
char short int long и т.д. допускается использование unsigned
Объявленные типы: BOOL BYTE WORD DWORD LPVOID LPCVOID LPDWORD PVOID HANDLE HWND LONG_PTR SIZE_T UINT WCHAR LPWSTR LPCCH LPCWCH CHAR LPSTR LPBOOL // must be "typedef BOOL far *LPBOOL;". far = error.
Так же отдельно создана возможность читать строки. Для этого необходимо указать тип: "string" - для ASCII строки. "unicode" - для строки в UTF-16. Cтандартный lua тип string (по сути ASCII) будет преобразован в UTF-16 и записан. codepage - кодовая страница для преобразования unicode строки. set_buffer_size([size]) Функция устанавливает буфер для чтения и записи (он общий) размером size. Если size не указан, то просто вернет текущий размер буфера. buffer_rawУказатель на Си массив со считанными данными согласно типу. В частности может быть полезен для посимвольного перебора строк, где log(mem.buffer_raw[0]) - выведет первый символ строки. code
Код --lua
local ffi = require("ffi") local C = ffi.C
ffi.cdef [[ typedef int BOOL; typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef void * LPVOID; typedef const void * LPCVOID; typedef DWORD * LPDWORD; typedef void * PVOID; typedef PVOID HANDLE; typedef HANDLE HWND; typedef long LONG_PTR; typedef LONG_PTR SIZE_T; typedef unsigned int UINT; typedef wchar_t WCHAR; typedef WCHAR* LPWSTR; typedef const char* LPCCH; typedef const WCHAR* LPCWCH; typedef char CHAR; typedef CHAR* LPSTR; typedef BOOL* LPBOOL; // must be "typedef BOOL far *LPBOOL;". far = error.
void* malloc( size_t size);
void free( void* ptr);
DWORD __stdcall GetWindowThreadProcessId( HWND hWnd, LPDWORD lpdwProcessId);
HANDLE __stdcall OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
BOOL __stdcall CloseHandle( HANDLE hObject);
BOOL __stdcall ReadProcessMemory( HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead);
int __stdcall WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten);
int __stdcall MultiByteToWideChar( UINT CodePage, DWORD dwFlags, LPCCH lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);
int __stdcall WideCharToMultiByte( UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
DWORD __stdcall GetLastError(); ]] local kernel = ffi.load"kernel32"
local PROCESS_VM_READ = 0x0010 local PROCESS_VM_WRITE = 0x0020
local export = {}
do local buffer_size = 0 export.set_buffer_size = function(size) if size then export.buffer_raw = nil collectgarbage() export.buffer_raw = ffi.gc(C.malloc(size), C.free) buffer_size = size end return buffer_size end end export.set_buffer_size(128)
local read_memory = function (address, size) end do local PID = ffi.new("DWORD[1]") read_memory = function (address, size)
C.GetWindowThreadProcessId(ffi.cast("HWND", workwindow()), PID) local process = C.OpenProcess(PROCESS_VM_READ, true, PID[0]);
if process and ffi.cast("int", process) > 0 then local result = C.ReadProcessMemory( process, ffi.cast("const void *", address), export.buffer_raw, size, nil) C.CloseHandle(process) if result ~= 0 then return true end end log('Process not opened') return nil, -2 end end
export.read = function (address, data_type, lenght, codepage) lenght = lenght or 1 codepage = codepage or 0
if data_type == "string" then if lenght > export.set_buffer_size() then export.set_buffer_size(lenght) end if read_memory(address, lenght) then export.buffer = ffi.cast("char *", export.buffer_raw) return ffi.string(export.buffer, lenght) end
elseif data_type == "unicode" then if lenght*4 > export.set_buffer_size() then export.set_buffer_size(lenght*4) end
if export.read(address, "string", lenght*4) then
local wString = ffi.cast("short *", export.buffer_raw) local aString = ffi.new("char *", ffi.cast("char *", export.buffer_raw) + math.floor(export.set_buffer_size()/2))
ffi.C.WideCharToMultiByte(codepage, 0, wString, lenght, aString, lenght, nil, nil) return ffi.string(aString, lenght) end return nil else if read_memory(address, lenght*ffi.sizeof(data_type)) then export.buffer = ffi.cast(data_type.." *", export.buffer_raw) return export.buffer[0] end return nil end end
local write_memory = function (address, size) end do local PID = ffi.new("DWORD[1]") write_memory = function (address, size) if size > export.set_buffer_size() then export.set_buffer_size(size) end
C.GetWindowThreadProcessId(ffi.cast("HWND", workwindow()), PID) local process = C.OpenProcess(0x20+0x8, true, PID[0]);
local result = C.ReadProcessMemory( process, ffi.cast("const void *", address), export.buffer_raw, size, nil)
if process and ffi.cast("int", process) > 0 then local result = C.WriteProcessMemory( process, ffi.cast("void*", address), ffi.cast("const void*", export.buffer_raw), size, wrote)
C.CloseHandle(process) if result ~= 0 then return true else return false, "no data wrote" end end log('Process not opened') return nil, -2 end end
export.write = function (data, address, data_type, codepage) if data_type == "string" then if #data + 1 > export.set_buffer_size() then export.set_buffer_size(#data + 1) end ffi.copy(export.buffer_raw, data)
if write_memory(address, #data) then export.buffer = ffi.cast("char *", export.buffer_raw) return true end
elseif data_type == "unicode" then codepage = codepage or 0 local wString = ffi.cast("short *", export.buffer_raw)
C.MultiByteToWideChar(codepage, 0, data, #data, wString, #data); if write_memory(address, #data*2) then export.buffer = ffi.cast("char *", export.buffer_raw) return true end else data = type(data) == table and data or {data} local type_size = ffi.sizeof(data_type) local data_size = #data*type_size
if data_size > export.set_buffer_size() then export.set_buffer_size(data_size) end
-- Put data to buffer local pointer = ffi.cast(data_type .. " *", export.buffer_raw) for i = 1, #data do pointer[i-1] = ffi.cast(data_type, data[i]) end
export.buffer = ffi.cast(data_type.." *", export.buffer_raw)
if write_memory(address, data_size) then return true end end
return nil end
return export Оно сыровато, но вроде работает. Времени катастрофически не хватает. Сообщение отредактировал DarkMaster - 16.3.2024, 5:13
--------------------
Скрипты UOPilot под заказ. Консультации по UOpilot 15$/час. Услуги Lua разработчика (не пилот, проекты, постоянка) Disсоrd: Kov____
|
|
|
|
2 чел. читают эту тему (гостей: 2, скрытых пользователей: 0)
Пользователей: 0
|
|