Добавить в корзинуПозвонить
Найти в Дзене
Adr Asd

пишем трейнер на Delphi_11 часть 3

-------------------
Ссылки на части: _часть 1 _часть 2
_получаем патч-байты для SCUM.v0.9.113.75065
------------------- Привет. Продолжаем кодить, создадим необходимые нам структуры record, вверху, сразу после type sections = record ... end; пишем код:
type //входит в dataForPatchBytes
patchBytesRec = record
offsetAdresPatch:DWORD64; //смещение от найденного адреса сигнатуры
//отсчёт смещения (true - вычитанием, то есть смещение вверх, иначе в низ) от offsetAdresPatch
offsetMinus:boolean;
bytesPatch: ArrayOfByte; //хранит байты для записи патча
end;
type //для массива с данными для патча
//один индекс может содержать несколько фрагментов кода патча
dataForPatchBytes = record
//массив, потому - что части патча могут записываться по разным смещениям, в свободные места между кодом
bytesPatchArr: Array of patchBytesRec;
end;
type //для массива сохранения оригинальных байт перед патчем
savedOriginalBytes = record
Оглавление

-------------------
Ссылки на части:
_часть 1 _часть 2
_получаем патч-байты для SCUM.v0.9.113.75065
-------------------

Привет.

Продолжаем кодить, создадим необходимые нам структуры record, вверху, сразу после type sections = record ... end; пишем код:

type //входит в dataForPatchBytes
patchBytesRec = record
offsetAdresPatch:DWORD64;
//смещение от найденного адреса сигнатуры
//отсчёт смещения (true - вычитанием, то есть смещение вверх, иначе в низ) от offsetAdresPatch
offsetMinus:boolean;
bytesPatch: ArrayOfByte;
//хранит байты для записи патча
end;


type //для массива с данными для патча
//один индекс может содержать несколько фрагментов кода патча
dataForPatchBytes = record
//массив, потому - что части патча могут записываться по разным смещениям, в свободные места между кодом
bytesPatchArr: Array of patchBytesRec;
end;


type //для массива сохранения оригинальных байт перед патчем
savedOriginalBytes = record
patchNumber:Cardinal;
// последовательный номер патча (начинается с 1)
saveBytesArr: Array of patchBytesRec; //хранит оригинальные байты перед патчем
end;

а в блоке
private объявим переменные для этих структур и ещё одну:
//массив record с данными для патчей (здоровье,патроны и т.д.)
arrDataForPatchBytes: Array of dataForPatchBytes;
//массив сохранения оригинальных байт перед патчем
savedOriginalBytesArr :Array of savedOriginalBytes;
//хранит атрибуты защиты
Protect региона памяти, для его восстановления при патче страниц памяти
OldProtect:Cardinal;

OldProtect используется в функции VirtualProtectEx - установка прав защиты на страницы (секции) памяти , константы защиты памяти.

*******
А теперь о получении адресов и данных патчей
, которые собственно и будет изменять в памяти процесса игры наш трейнер. А вот здесь дилемма, я могу растянуть это ещё на одну статью с описанием поиска и анализа кода в отладчике, или дать ссылки на мои прошлые кряки некоторых игр. Night of the Dead v2.0.5.7 , dayZ 1.21 - (offline мод) , Star Defender 3 Где вполне показан процесс патча патронов, здоровья и прочего. Используя их в качестве примера можно получить все нужные адреса и данные для SCUM. А могу дополнить статьёй по взлому SCUM.v0.9.113.75065 и её результаты будут использованы в этой статье. Я ещё думаю, а пока делаем тестовый патч для блокнота.
*******

Создадим тестовый патч для notepad и проверим в действии. В FormCreate инициализируем переменную OldProtect:=80; , а тестовый патч сделаем в x64Dbg, запускаем его и открываем в нём блокнот. Перейдём к бряку c комментом sign x.

Наша тестовая сигнатура, у меня находится по адресу 0x7FF7419A1300.

-2

Выберем пару отрезков кода - которые пропатчим, у меня - это JE переход по адресу 0x7FF7419A1314 на 0x14 байт ниже адреса сигнатуры и пара строк под ним, а второй отрезок тоже JE по адресу 0x7FF7419A131F на 0x1F байт ниже адреса сигнатуры + одна строка ниже. Скопируем оригинальный код и hex-байты выделенных фрагментов куда нибудь в текстовый файл. Теперь заменим JE например на JMP а строки после занупаем.

-3

Так же скопируем код и hex-байты пропатченных отрезков в текстовый файл. Цепочка байт первого пача выглядит так EB5C90909090909090 , второго EB23909090 Закроем блокнот и отладчик. Данные для патча есть.

Идём в TForm1.FormCreate и дописываем в ней блок переменных var где объявляем hg:integer; :

procedure TForm1.FormCreate(Sender: TObject);
var
hg:integer;
begin ...

а сразу после MaskOne:=PWideChar('xxxxxx????????xxxxxx'); пишем:

//тестовый патч для notepad
setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);
//1 часть патча
setLength(arrDataForPatchBytes[hg].bytesPatchArr,2);
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetAdresPatch:=$14;
//смещение патча от адреса сигнатуры
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetMinus:=false;
arrDataForPatchBytes[hg].bytesPatchArr[0].bytesPatch:=getArrayBytesFromString('EB5C90909090909090');
//патчим 9 байта
//2 часть патча
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetAdresPatch:=$1F;
//смещение патча от адреса сигнатуры
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetMinus:=false;
arrDataForPatchBytes[hg].bytesPatchArr[1].bytesPatch:=getArrayBytesFromString('EB23909090');
//патчим 5 байта
hg:=-1;

Проверяем, компилируем, нет ошибок - продолжаем.

И так, сам патч у нас происходит по клику одного из слайдеров, а обрабатывать все слайдеры будет одна функция - sSliderSClick, в которой и будет происходить вся магия. Но сначала напишем функцию - возвращающую точный адрес патча с учётом направления смещения:

//возвращает высчитанный адрес для патча baseAddress + offsetAddres если flagMinus = false иначе baseAddress- offsetAddres
function retAccumulateAddress(baseAddress:DWORD64; offsetAddres:DWORD64; flagMinus:boolean=false):DWORD64;
begin
if not flagMinus then result:= baseAddress + offsetAddres
else result:= baseAddress - offsetAddres;
end;

и где нибудь в верху, можно первой в юните, функцию bintostr конвертирующую двоичные байты в строку:

//преобразует массив байт в строку (без потери нулей)
function bintostr(const bin: array of byte): string;
const HexSymbols = '0123456789ABCDEF';
var i: Cardinal;
begin
SetLength(Result, 2*Length(bin));
for i := 0 to Length(bin)-1 do begin
Result[1 + 2*i + 0] := HexSymbols[1 + bin[i] shr 4];
Result[1 + 2*i + 1] := HexSymbols[1 + bin[i] and $0F];
end;
end;

а теперь главная процедура нашего трейнера TForm1.sSliderSClick , объявим её в блоке type юнита:

procedure sSliderSClick(Sender: TObject);

и напишем реализацию ниже функции retAccumulateAddress:

procedure TForm1.sSliderSClick(Sender: TObject);
var curSlider:TsSlider; ii,xx:integer;
progHandle,hSnapShot:THandle; ProcInfo: TProcessEntry32;
indexArrayOfPatches:integer;
//индекс массива arrDataForPatchBytes из которого читаем данные патча (здоровье,патроны и т.д.)
checkSaveOriginalBytes:array of DWORD64;
// массив ошибок сохранения, запоминает адреса для которых не удалось сохранить оригинальные байты
iNcheckSaveOriginalBytes:boolean;
//флаг для проверки наличия адреса в массиве
bytesWritten:SIZE_T; accumulateAddress:DWORD64;

begin
curSlider :=TsSlider(Sender);
indexArrayOfPatches:=-1;
//индекс([0]-здоровье,[1]-патроны, ..)для обхода массива патчей arrDataForPatchBytes

if (progID > 0) and (patchAdresOne > 0) then begin
accumulateAddress:=0; iNcheckSaveOriginalBytes:=false; progHandle:=0;
hSnapShot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
ProcInfo.dwSize := SizeOf(ProcInfo);

if (Process32First(hSnapshot, ProcInfo)) then begin
while (Process32Next(hSnapShot, ProcInfo)) do begin
if ProcInfo.th32ProcessID = progID then begin
progHandle:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_ALL_ACCESS or PROCESS_VM_OPERATION, False, progID);
if progHandle <> 0 then begin
//если пуста структура для данных секции кода ( .text ) выход
if sectionCode.fullAddress <= 0 then begin closeHandle(hSnapShot); closeHandle(progHandle); progHandle:=0;
Form1.sStatusBar1.SimpleText:='ошибка: структура для данных секции кода пуста.';
exit;
end;

//изменяем права на секцию кода ( .text )
VirtualProtectEx(progHandle, pointer(sectionCode.fullAddress), sectionCode.fullSize, $40, OldProtect);
//sSlider1 управляет всеми сразу
if curSlider.Name = 'sSlider2' then indexArrayOfPatches:=0;
if curSlider.Name = 'sSlider3' then indexArrayOfPatches:=2;
if curSlider.Name = 'sSlider4' then indexArrayOfPatches:=1;
if curSlider.Name = 'sSlider5' then indexArrayOfPatches:=3;
if curSlider.Name = 'sSlider6' then indexArrayOfPatches:=4;

//слайдер включён - патчим
if curSlider.SliderOn = false then begin

if length(arrDataForPatchBytes) <= indexArrayOfPatches then begin
sStatusBar1.SimpleText:='Длинна массива патчей меньше выбранного индекса: ' + inttostr(indexArrayOfPatches);
exit;
end;

//массив ошибок сохранения
setlength(checkSaveOriginalBytes,0);

//размер массива сохранений = размеру массива патчей
setLength(savedOriginalBytesArr,length(arrDataForPatchBytes));

//порядковый номер сохранения (в savedOriginalBytesArr) оригинальных байт перед патчем
savedOriginalBytesArr[indexArrayOfPatches].patchNumber:= indexArrayOfPatches+1;

//размер массива сохранения оригинальных байт - равен размеру массива байт патча setLength(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr,length(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr));

//проходим массив с патчами arrDataForPatchBytes[].bytesPatchArr[]
//и заполняем в массив сохранений

ii:=0;
//// for ////
for ii := 0 to length(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr)-1 do begin

savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].offsetAdresPatch:= arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].offsetAdresPatch;
savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].offsetMinus:= arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].offsetMinus;

//задаём размер цепочки байтов в массиве сохранений равный размеру строки байтов патча
setlength(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch,length(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].bytesPatch));

accumulateAddress:=0;
//собираем целевой адрес для патча в accumulateAddress patchAdresOne +/- offsetAdresPatch
accumulateAddress:= retAccumulateAddress(patchAdresOne, arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].offsetAdresPatch , arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].offsetMinus );

//сохраняем оригинальные байты
if not ReadProcessMemory(progHandle, pointer(accumulateAddress) , @savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch[0]
//так берём указатель на массив байт - через первый ( [0] ) элемент
, length(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].bytesPatch) , bytesWritten )
then begin sStatusBar1.SimpleText:='Ошибка чтения ориг. байт: ' + Format('%x',[accumulateAddress]); end;

//проверка длинны сохранённых байт
if length(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch) <= 0 then
begin
// массив ошибок сохранения
//запишем в checkSaveOriginalBytes адрес - для которого не удалось сохранить оригинальные байты

setlength(checkSaveOriginalBytes,length(checkSaveOriginalBytes)+1);
checkSaveOriginalBytes[high(checkSaveOriginalBytes)]:= accumulateAddress;

end else begin
//// записываем байты патча ////

if not WriteProcessMemory(progHandle,pointer(accumulateAddress), PChar( arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].bytesPatch )
, length(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].bytesPatch), bytesWritten) then
sStatusBar1.SimpleText:='Ошибки записи байт патча. '; // + inttostr(accumulateAddress);

// тестовый вывод
Memo1.Lines.Add(inttostr(ii)
+ ' full Address: ' + Format('%x',[accumulateAddress])
+ #13#10 + ' размер bytesPatch ' + inttostr(length(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].bytesPatch)) + #13#10 + ' байты => ' + bintostr(arrDataForPatchBytes[indexArrayOfPatches].bytesPatchArr[ii].bytesPatch) );
end;

accumulateAddress:=0;
end;

Memo1.Lines.Add(' ************* ');
//// конец for ////

if length( checkSaveOriginalBytes) > 0 then sStatusBar1.SimpleText:='Ошибки при сохранении оригинальных байт.';

end else begin
//слайдер вЫкл - возвращаем оригинальные байты на место

if length(savedOriginalBytesArr) <= indexArrayOfPatches then begin
sStatusBar1.SimpleText:='Длинна резервного массива меньше выбранного индекса: ' + inttostr(indexArrayOfPatches);
exit;
end;

for ii := 0 to length(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr)-1 do
begin
iNcheckSaveOriginalBytes:=false;
accumulateAddress:=0;
//адрес для восстановления байт
accumulateAddress:=retAccumulateAddress(patchAdresOne ,savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].offsetAdresPatch
, savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].offsetMinus );

//проверка в массиве адресов - для которых не получилось скопировать оригинальные байты
if length(checkSaveOriginalBytes) > 0 then begin
for xx := 0 to length(checkSaveOriginalBytes) -1 do begin
if checkSaveOriginalBytes[xx]= accumulateAddress then
iNcheckSaveOriginalBytes:=true;
end;
end;

//восстановим оригинальные байты если адрес отсутствует в массиве ошибок сохранения
if not iNcheckSaveOriginalBytes then begin
if not WriteProcessMemory(progHandle,pointer(accumulateAddress), PChar( savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch)
, length(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch), bytesWritten) then
sStatusBar1.SimpleText:='Ошибки восстановления оригинальных байт. '
// + inttostr(accumulateAddress);
else
// тестовый вывод
Memo1.Lines.Add(inttostr(ii)
+ ' восстановление: ' + Format('%x',[accumulateAddress])
+ #13#10 + ' размер bytesPatch ' + inttostr(length(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch)) + #13#10 + ' байты => ' + bintostr(savedOriginalBytesArr[indexArrayOfPatches].saveBytesArr[ii].bytesPatch) );
end;
end; Memo1.Lines.Add(' ************* ');
end;
//восстановим права на секцию кода ( .text )
VirtualProtectEx( progHandle, pointer(sectionCode.fullAddress), sectionCode.fullSize, OldProtect, OldProtect);

closeHandle(progHandle);
end;

end;
end;
end;

closeHandle(hSnapShot);
end;
/////// end - if progID
end;
////////end - TForm1.sSlider1Click

Проверили, скомпилировали, нет ошибок - далее. В инспекторе объектов выберем sSlider2 , на вкладке Events в строке OnClick выберем sSliderSClick. Компилируем, проверяем - что наворотили ;-) Запускаем подопытный блокнот, присоединяемся к нему x64Dbg, переходим к бряку - установленному на адрес сигнатуры. Запускаем наш трейнер и после определения подопытного процесса, переключаем второй слайдер. Ну и как должно быть - видим в X64Dbg применение нашего патча, а если нет - ищем ошибки,

-4

а в поле Memo1 - видим вывод отладочной информации. Теперь отключим слайдер обратно и убедимся что оригинальные байты восстановлены.

-5

Сверяем с сохранёнными ранее оригинальными кодами в блокноте, вроде верно ;-)

И так, функционал уже вполне есть и на основе материала уже можно создавать трейнеры-патчеры для различных игр. Если нужно патчить не главный модуль, то в функции getModuleInfo выбираем нужный и дорабатываем код под свои задачи.

*****

Продолжаем. О получении байт-патчей для SCUM.v0.9.113.75065 - эта версия текущая на 20.10.2023. Можно перейти к статье - пишем трейнер на Delphi_11 - получаем патч-байты для SCUM.v0.9.113.75065 прочитать о получении патч-байтов и продолжить.

*****

И так - получили данные первого патча (или всех) и сигнатуру для отсчёта смещений. Первым идёт патч здоровья персонажа.

//строка байт для поиска сигнатуры
\x0F\x28\xC8\xF3\x41\x0F\x59\xCC\xF3\x00\x00\x00\x00\x00\x00\x00\xF3
//маска
xxxxxxxxx???????x

//байты патча 1 здоровья
//часть 1
5048C7C00000C84289838003000048C7C00000803F8983840300005890909090909090909090909090909090
//часть 2 находится через
0x52 байта после адреса сигнатуры( начала первой части).
9090909090909090

Запускаем дельфу и кодим. Закомментируем в FormCreate тестовые сигнатуру и код патча для блокнота и пишем патч здоровья.

-6

//строка для поиска сигнатуры в коде SCUM
SignOne:=getArrayBytesFromString('\x0F\x28\xC8\xF3\x41\x0F\x59\xCC\xF3\x00\x00\x00\x00\x00\x00\x00\xF3',true);
//маска
MaskOne:=PWideChar('xxxxxxxxx???????x');

setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);


//patch 1 Здоровье
//часть 1
setLength(arrDataForPatchBytes[hg].bytesPatchArr,2); //количество частей патча
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetAdresPatch:=0; //смещение патча от адреса сигнатуры
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetMinus:=false; //отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[0].bytesPatch:=getArrayBytesFromString('5048C7C00000C84289838003000048C7C00000803F8983840300005890909090909090909090909090909090');
//часть 2
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetAdresPatch:=$52; //смещение патча от адреса сигнатуры
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetMinus:=false; //флаг отрицательного смещения
arrDataForPatchBytes[hg].bytesPatchArr[1].bytesPatch:=getArrayBytesFromString('9090909090909090');
hg:=-1;

Смещение первой части патча от адреса сигнатуры = 0(
offsetAdresPatch:=0; ) так как сигнатура находится как раз по адресу первого патча, смещение второй части патча = 0x52 байта. Изменяем константу gameName = 'SCUM.exe';
Массив патчей у нас пока с одним элементом, с индексом 0, он формируется так:
hg:=high(arrDataForPatchBytes); -- максимальный элемент массива ( 0 - так как он ещё пуст )
//patch 1 Здоровье
//часть патча 1 индекс 0
arrDataForPatchBytes[hg].bytesPatchArr[0]...
//часть патча 2 индекс 1 и т.д.
arrDataForPatchBytes[hg].bytesPatchArr[1]...
...

Компилируем и испытываем первый патч в действии. Запускаем игру, присоединяемся x64dbg. Запускаем трейнер, видим в поле вывода информации трейнера - среди прочего ---
адрес сигнатуры:( у меня 7FF6268DACE6), в x64dbg для проверки переходим по этому адресу и попадаем на <-- начало патча здоровья часть 1, значит поиск сигнатуры верен. Применяем патч по щелчку слайдера "ЗДОРОВЬЕ" (второго с верху). В x64dbg крутанём колесом мыши, для обновления вида, если всё сделано верно - наблюдаем применение нашего патча в коде. В поле вывода трейнера видим информацию о применении патча. Проверяем работу в игре - повреждая подопечного. Теперь деактивируем слайдер "ЗДОРОВЬЕ" и оригинальные байты - сохранённые перед патчем должны восстановиться, что и видим в x64dbg. Файл юнита на текущий момент статьи Unit1_часть3_патчЗдоровья.zip
Теперь можно вернуться и дочитать статью о получении остальных патчей для
SCUM.v0.9.113.75065 и после доделать уже всё.

+++++++++++++++++++++++++++++++++++++++++

Ну что, все данные получены, реализуем всё остальное.

На этот момент в FormCreate реализован только патч здоровья, добавляем все остальные - добыча которых показана в статье "
получаем патч-байты для SCUM.v0.9.113.75065".

Добавляем после hg:=-1; первого патча такую портянку кода:

//заполняем массив с данными патчей, при отрицательном смещении -> offsetMinus = true (то есит - выше адреса сигнатуры, от которой отсчитываем смещения адресов патчей)
//patch 2 Выносливость
setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);

setLength(arrDataForPatchBytes[hg].bytesPatchArr,1); //количество частей патча 1
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetAdresPatch:=$D1D; //смещение патча от адреса сигнатуры
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetMinus:=false; //положительное смещение
arrDataForPatchBytes[hg].bytesPatchArr[0].bytesPatch:=getArrayBytesFromString('5048C7C00000803F8983B0030000589090909090909090909090'); //байты патча
hg:=-1;

//patch 3 Патроны, стрелы (в пяти частях)
//часть 1
setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);
setLength(arrDataForPatchBytes[hg].bytesPatchArr,5);
//количество частей патча 5
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetAdresPatch:=$33B01A;
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetMinus:=false;
arrDataForPatchBytes[hg].bytesPatchArr[0].bytesPatch:=getArrayBytesFromString('C3');

//часть 2
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetAdresPatch:=$3414DB;
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetMinus:=false;
arrDataForPatchBytes[hg].bytesPatchArr[1].bytesPatch:=getArrayBytesFromString('418D0090');

//часть 3
arrDataForPatchBytes[hg].bytesPatchArr[2].offsetAdresPatch:=$13924F6;
arrDataForPatchBytes[hg].bytesPatchArr[2].offsetMinus:=true
; //отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[2].bytesPatch:=getArrayBytesFromString('C3');
//добавим патч стрел (в двух частях)
//стрелы часть 1
arrDataForPatchBytes[hg].bytesPatchArr[3].offsetAdresPatch:=$32C548;
arrDataForPatchBytes[hg].bytesPatchArr[3].offsetMinus:=true
; //отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[3].bytesPatch:=getArrayBytesFromString('BF0C00000090');
//стрелы часть 2
arrDataForPatchBytes[hg].bytesPatchArr[4].offsetAdresPatch:=$32C540;
arrDataForPatchBytes[hg].bytesPatchArr[4].offsetMinus:=true;
//отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[4].bytesPatch:=getArrayBytesFromString('909090');
hg:=-1;

//patch 4 Износ предметов (в трёх частях)
//часть 1
setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);
setLength(arrDataForPatchBytes[hg].bytesPatchArr,3);
//количество частей патча 3
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetAdresPatch:=$42622B;
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetMinus:=true;
//отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[0].bytesPatch:=getArrayBytesFromString('50E9510100009090');
//часть 2
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetAdresPatch:=$4260D4;
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetMinus:=true;
//отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[1].bytesPatch:=getArrayBytesFromString('B80000C8428983A8010000EB42');
//часть 3
arrDataForPatchBytes[hg].bytesPatchArr[2].offsetAdresPatch:=$426085;
arrDataForPatchBytes[hg].bytesPatchArr[2].offsetMinus:=true;
//отрицательное смещение
arrDataForPatchBytes[hg].bytesPatchArr[2].bytesPatch:=getArrayBytesFromString('58E95CFEFFFF');
hg:=-1;

//patch 5 авто здоровье, топливо (в двух частях)
//часть 1 авто здоровье
setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);
setLength(arrDataForPatchBytes[hg].bytesPatchArr,2);
//количество частей патча 2
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetAdresPatch:=$2C7F03;
arrDataForPatchBytes[hg].bytesPatchArr[0].offsetMinus:=false;
arrDataForPatchBytes[hg].bytesPatchArr[0].bytesPatch:=getArrayBytesFromString('90909090');

//часть 2 топливо
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetAdresPatch:=$2FD40A;
arrDataForPatchBytes[hg].bytesPatchArr[1].offsetMinus:=false;

arrDataForPatchBytes[hg].bytesPatchArr[1].bytesPatch:=getArrayBytesFromString('50B80000484289813804000058909090909090909090909090909090');
hg:=-1;


Добавляем остальным слайдерам в
OnClick функцию sSliderSClick.

Проверим соответствие индексов в массиве патчей arrDataForPatchBytes[] и относящихся к ним слайдеров в sSliderSClick
(
ВНИМАНИЕ, в предыдущих версиях порядок был другой)
if curSlider.Name = 'sSlider2' then indexArrayOfPatches:=0; //здоровье
if curSlider.Name = 'sSlider3' then indexArrayOfPatches:=1; //выносливость
if curSlider.Name = 'sSlider4' then indexArrayOfPatches:=2; //патроны,стрелы
if curSlider.Name = 'sSlider5' then indexArrayOfPatches:=3; //износ
if curSlider.Name = 'sSlider6' then indexArrayOfPatches:=4; //авто

-7

Кстати - строки:
setlength(arrDataForPatchBytes,length(arrDataForPatchBytes)+1);
hg:=high(arrDataForPatchBytes);

можно заменить конкретными значениями, так как размерности массивов заранее известны, тем избежать нескольких вызовов setlength.

Компилируем, исправляем косяки, тестируем в деле.

Код юнита третьей части - Unit1_часть3_финал.zip , конечно многое можно и нужно дорабатывать, оптимизировать, всё в вашей творческой воле ;-)

Комментарии и критика приветствуются, найдутся ошибки - не стесняйтесь сообщайте в комментах, будем исправлять. я тоже учусь.

Просьба оставлять ссылку на оригинал при копипасте.

Всем удачи, пока.

-8