Перейти к содержанию

srg91

Администраторы
  • Постов

    289
  • Зарегистрирован

  • Победитель дней

    22

Сообщения, опубликованные srg91

  1. 17 minutes ago, partoftheworlD said:

    немного бросается в глаза

     

    Исправил, спасибо.

     

    17 minutes ago, partoftheworlD said:

    и еще

     

    Я тоже так думаю, но предыдущие статьи показали, что это продвинутый уровень :)

  2. @Garik66 спросил - как использовать createthread, чтобы он не крашился. 

    Начал отвечать и это слегка вышло за рамки простого ответа в тему, поэтому решил выделить это в отдельный топик.

     

    Я не очень хорошо знаю assembler, но попробую описать возможные причины.

     

    createthread создает поток и просит его выполнить call с адресом переданной функции. 

    Например в данном случае createthread(my_functionв отдельном потоке выполнит call my_function:

    Spoiler
    
    alloc(my_function, 64)
    
    my_function:
    ret
    
    createthread(my_function)

     

     

    И тут вступает в дело стек, да.

    Вот эта штука (в Memory View вызывается через ПКМ на правом нижнем окне и выборе пункта Full stack):

    Spoiler

    p01.thumb.png.c660ef3dac09db453942530485

     

    После выполнения call my_function мы попадаем в нашу функцию, а в стек записывается адрес, куда нужно вернуться после выполнения этой функции (my_function). Собственно за возврат куда нужно и отвечает ret. Он берет первую запись из стека и делает jmp в правильное место. Собственно сам адрес возврата виден в стеке на скриншоте - это KERNEL32.BaseThreadInitThunk+24

     

    Без ret ассемблер вывалится за пределы функции и попытается выполнить команду - add [eax],al , хотя для нас это просто участок пустой памяти - 00 00. Ну а так как в EAX ничего приличного нет, то программа выдает ошибку - сорян, не могу записать в данный участок памяти и падает. Поэтому ret обязателен.

     

    Из этого вытекает то, что при подходе к ret в последней записи в стеке (адрес в регистр ESP) должна быть с правильным адресом возврата, который был нам передан из call my_function. И основная причина крашей в том, что регистр ESP указывает не на тот адрес при выполнении команды ret. Программа выполняет jmp на неизвестный нам адрес и крешится из-за невозможности выполнить команды, на которые она попала (как с add [eax],al).

     

    А что собственно двигает стек и адрес регистра ESP?

     

    А двигают стек наши любимые команды push и pop. Собственно, когда мы пытаемся вызывать функцию и передаем параметры через push #100, на самом деле выполняются две команды - sub esp,4 и mov [esp],#100. Первая уменьшает адрес вершины стека на 4 байта, а вторая записывает на вершину стека значение 100. А pop eax сдвигает вершину в другую сторону - делает mov eax, [esp] и дальше сдвигает вершину стека назад add esp,4.

     

    Поэтому после того, как мы сдвинули стек с помощью push при передаче параметров в функцию - после её выполнения нужно сдвинуть стек обратно! 

     

    Собственно следующий пример почти 100% скрешится:

    Spoiler
    
    [ENABLE]
    //code from here to '[DISABLE]' will be used to enable the cheat
    globalalloc(my_function, 64)
    alloc(second_function, 64)
    
    my_function:
    push #100
    call second_function
    ret
    
    second_function:
    mov eax,[esp+4]
    ret
    
    createthread(my_function)
     
     
    [DISABLE]
    //code from here till the end of the code will be used to disable the cheat
    dealloc(my_function)
    unregistersymbol(my_function)
    
    dealloc(second_function)

     

     

    Мы ввели новую функцию second_function, которая берет переданный аргумент и записывает его в EAX. Вроде бы всё просто, передали в функцию 100, получили в EAX ответ, а всё равно креш. Почему? А это видно на следующем скриншоте - на вершине стека теперь хранится значение 0x64 (=100):

    Spoiler

    p02.thumb.png.339b4b233aa5b808f383436c30

     

    Теперь когда выполнится ret он попытается прыгнуть на адрес 0x00000064, а не на 0x760E8744 как должен был.

     

    Как же этого избежать? Есть несколько путей:

    1. сохраняем значение регистра esp и возвращаем его перед ret
    2. внимательно следим за стеком по мере выполнения нашей функции и к концу программы он сам будет в правильном месте

     

    В целом первый способ очень неплох. Мы можем сохранить адрес регистра esp в памяти и вернуть его прямо перед вызовом ret. Например:

    Spoiler
    
    [ENABLE]
    //code from here to '[DISABLE]' will be used to enable the cheat
    globalalloc(my_function, 64)
    alloc(second_function, 64)
    alloc(esp_addr, 4)
    
    esp_addr:
    dd 0
    
    my_function:
    // сохраняем текущий адрес вершины стека
    mov [esp_addr],esp
    
    push #100
    call second_function
    
    // загружаем обратно адрес вершины стека
    mov esp,[esp_addr]
    ret
    
    second_function:
    mov eax,[esp+4]
    ret
    
    createthread(my_function)
     
     
    [DISABLE]
    //code from here till the end of the code will be used to disable the cheat
    dealloc(my_function)
    unregistersymbol(my_function)
    
    dealloc(second_function)
    
    dealloc(esp_addr)

     

     

    Но это не совсем assembler-way, поэтому я предпочитаю следить за стеком самостоятельно.

    Для этого нужно обращать внимание на то, как со стеком работает вызываемая функция.

     

    Например, наша функция second_function не следит за стеком. Поэтому если мы делаем push для передачи параметров, то мы так же должны сдвинуть его назад. Это можно сделать через pop указав неиспользуемый регистр или просто добавить к адресу ESP 4 байта (размер адреса в 32 битной системе, в 64-битной - это 8 байт):

    Spoiler
    
    // через pop - eax используется, выполним pop в неиспользуемый ebx
    push #100
    call second_function
    pop ebx
    
    // через сдвиг esp
    push #100
    call second_function
    add esp,4

     

     

    Собственно таким образом мы двигаем стек за функцией. И в зависимости от количества переданных push - на такое же количество нужно сдвинуть и регистр ESP. Например для 3-х аргументов нужно выполнить 3 pop или добавить к esp 3 * 4 (3 - количество сдвигов, 4 - размер инструкции):

    Spoiler
    
    // через pop
    push #100
    push #200
    push #300
    call third_function
    pop ebx
    pop ebx
    pop ebx
    
    // через сдвиг esp
    push #100
    push #200
    push #300
    call third_function
    add esp,c // 0xC = 4 * 3 = 12

     

     

    В результате мы получим следующий код:

    Spoiler
    
    [ENABLE]
    //code from here to '[DISABLE]' will be used to enable the cheat
    globalalloc(my_function, 64)
    alloc(second_function, 64)
    
    my_function:
    push #100
    call second_function
    add esp,4
    ret
    
    second_function:
    mov eax,[esp+4]
    ret
    
    createthread(my_function)
     
     
    [DISABLE]
    //code from here till the end of the code will be used to disable the cheat
    dealloc(my_function)
    unregistersymbol(my_function)
    
    dealloc(second_function)
    

     

     

    Единственное, есть небольшое исключение - функции, которые сами заботятся о стеке, после их вызова не нужно сдвигать регистр esp, просто делаем push и call. Их довольно просто отличить, посмотрим на примере следующего кода:

    Spoiler
    
    [ENABLE]
    //code from here to '[DISABLE]' will be used to enable the cheat
    globalalloc(my_function, 64)
    globalalloc(second_function, 64)
    globalalloc(third_function, 64)
    
    my_function:
    // функция, не сдвигающая стек за собой
    push #100
    call second_function
    add esp,4
    
    // функция, сдвигающая стек за собой
    push #100
    push #200
    call third_function
    
    ret
    
    second_function:
    mov eax,[esp+4]
    ret
    
    third_function:
    mov eax,[esp+4]
    add eax,[esp+8]
    ret 8
    
    createthread(my_function)
     
    [DISABLE]
    //code from here till the end of the code will be used to disable the cheat
    dealloc(my_function)
    unregistersymbol(my_function)
    
    dealloc(second_function)
    unregistersymbol(second_function)
    
    dealloc(third_function)
    unregistersymbol(third_function)

     

     

    Основное отличие - после call функции которая сама двигает стек не идет ни каких add esp,8 или не выполняются pop:

    Spoiler

    p03.thumb.png.0b1f6cd53dd660f0b8cb51e1e4

     

    Так же можно обратить внимание на код самой функции, а точнее на ret 8. Параметр 8 указывает программе, что нужно не просто взять адрес из вершины стека и прыгнуть на него, а так же после этого сдвинуть стек на 8 байт, т.е. выполнить ret и add esp,8. Собственно и получается, что add делаем не мы, а сама функция.

     

    Вот и получается, что следя за стеком и тем, как вызываемые функции заботятся о стеке - мы не получим крашей в вызове createthread.

    • Плюс 12
  3. Привет. Ты можешь открыть окно AutoAssembler и нажать F1 - откроется справка по командам AutoAssembler с примерами.

    Минимальная информация так же есть на Wiki.

     

    По самому ассемблеру наверное ничего не могу сказать (максимум что это Intel-синтаксис), но форуме есть пара тем про асм: 

    Spoiler

     

     

    и в целом весь раздел:

    https://gamehacklab.ru/forum/20-быстрая-информация/

     

  4. Второе задание - готово. Не открывал не IDA, ничем. Использовал небольшой, как со вторым TrainMe и датой )

     

    Под спойлером напишу как взламывал.

    Spoiler

    Ну, так как мы знаем, что Taiwan пишет на нашем любимом Delphi и ничего не запаковано - значит мы вооружены :)

    Первым путем попробовал сделал так - нашел операцию сравнения - в Delphi это отдельный call. Чтобы выяснить как он выглядит просто компилим минимальный проект с if A  = B then и смотрим что внутри этого call. Найдя эту функцию в Unlock Me ставим бряк и аккуратно отфильтровываем все использования этой функции. Я умудрился профукать её вызов в проверке, поэтому пошел по другому.

     

    Я нашел компоненты на форме через поиск TLabel в памяти, увидел что используется IdEncoderXXE. Это стандартный Indy компонент, а значит можно предположить что использован IdEncodeXXE.Encode :) Собственно, ищем его так же как и функцию сравнения и ставим бряк. Таким образом и попадаем на OnClick кнопки. А дальше видим, что вызывается та же функция сравнения, где в случае не выполнения условия Edit.Text == IdEncodeXXE.Encode(0xA0) приложение завершает работу. Ну и в аргументах функции сравнения видим наш пароль :)

     

    Пароль (шифранул в base64, чтобы не спойлерить): LWMrKys=

     

  5. 5 hours ago, Taiwan said:

    Можете выкладывать - что нарыли)

     

    ну тут и так уже всё рассказали, я просто дампнул процесс через PETools и открыл через ILSpy => cкопировал строку с результатом и проверил.

    После этого прочитал, что дампить нельзя ))

    Собственно, если код не обфусцирован - то после ILSpy всё как на ладони. Да и после обфусцирования тоже -  Knock-knock более менее ломается даже так:

    Spoiler

     

     

     

  6. Just now, Taiwan said:

    Бывает)) пароль и цвет лампочки после успешной авторизации - можешь в теме выложить.

     

    Ну, раз задание с не дампить - подождем решения ) Могу скинуть в личку. 

  7. 20 minutes ago, MasterGH said:

    От всего форума, от всех пользователей большой респект за видео. Супер :);)

     

    Спасибо :) Надеюсь следующее будет чуть более удачным.

    Будем пробовать встроить получившийся скрипт в LUA форму с выбором машинки из выпадающего списка.

  8.  

    Записал видео со спавном машин рядом с игроком. Получилось ужасно долго, тихо и в конце похоже пропал звук. 

    Я решил всё равно выложить, я вряд ли смогу переснять, но если получится исправить - исправлю ссылку на видео.

    Плюс если что есть текстовая статья - можно выложить её (но она как и видео - огромна).

     

    В видео мы узнаем какие функции вызываются при создании автомобиля через чит-коды и набросаем небольшой скрипт, который будет создавать требуемый автомобиль перед игроком:

    Spoiler

     

     

     

    Итоговый скрипт:

    Spoiler
    
    [ENABLE]
    //code from here to '[DISABLE]' will be used to enable the cheat
    aobscan(RequestStream, 8B 4C 24 04 53 56 57 55 89 CD 8D 6C AD 00 8B 5C 24 18 8D 2C AD 00 00 00 00 8A 85 ?? ?? ?? ?? 3C 02)
    aobscan(loadRequestedStreams, 53 56 57 55 83 EC 10 80 ?? ?? ?? ?? 00 00 75 0E C6 ?? ?? ?? ?? 00 01 C6 ?? ?? ?? ?? 00 00)
    aobscan(CStreaming_releaseModel, 8B 4C 24 04 89 CA 8D 14 92 8D 14 95 00 00 00 00 80 A2 ?? ?? ?? ?? FE 81 F9 64 19 00 00 7C 08 81 F9 CD 1E 00 00)
    aobscan(releaseModel, 8B 4C 24 04 BA 64 19 00 00 8B 04 8D ?? ?? ?? ?? 0F BF 48 26 8D 81 64 19 00 00 01 CA 8D 04 80)
    aobscan(CVehicle_new, 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? C3 00 00 00 00 53 89 CB C7 03 ?? ?? ?? ?? 66 C7 83 ?? ?? ?? ?? 00 00 8B 43 64 56 85 C0 55 7C 12 B9)
    aobscan(CAutomobile_constructor, 53 56 57 55 81 EC A8 00 00 00 8B 84 24 C0 00 00 00 50 8B B4 24 C0 00 00 00 89 4C 24 0C)
    aobscan(CBike_constructor, 53 56 55 83 EC 10 8B 44 24 24 50 8B 6C 24 24 89 4C 24 10 E8 ?? ?? ?? ?? 8B 44 24 0C)
    aobscan(CBoat_constructor, 53 56 57 83 EC 08 8B 44 24 1C 50 8B 74 24 1C 89 4C 24 08 E8 ?? ?? ?? ?? 8B 44 24 04 B9 09 00 00 00)
    aobscan(CPlacable_setRotation, 53 89 CB 83 EC 18 D9 44 24 24 D9 44 24 20 D9 EE D9 EE D9 EE D9 C3 D9 FF DF E0 A9 00 04 00 00 74 07)
    aobscan(CWorld_add, 53 8B 5C 24 08 8A 43 50 56 24 07 0F B6 D0 83 FA 02 74 05 83 FA 03 75 10 8B 43 64 B9 ?? ?? ?? ?? 6A 01)
    aobscan(GetPlayerPed, 0F B6 05 ?? ?? ?? ?? 6B C0 2E 8B 04 C5 ?? ?? ?? ?? C3)
    
    globalalloc(create_vehicle_func, 2048)
    globalalloc(model_index, 4)
    
    label(exit)
    label(create_vehicle)
    
    model_index:
    // sabre turbo
    dd #206
    
    create_vehicle_func:
    // modelIndex = 206
    mov esi,[model_index]
    
    // v3 = loadedModelInfo[modelIndex].flags
    mov edi,esi
    lea edi,[edi+edi*4]
    lea edi,[edi*4]
    movzx ebx,byte ptr [edi+gta-vc.exe+54CDE1]
    
    push 1
    push esi
    call RequestStream
    add esp,8
    
    push 0
    call loadRequestedStreams
    pop ecx
    
    // loadedModelInfo[modelIndex].available == 1
    cmp byte ptr [edi+gta-vc.exe+54CDE0],1
    jnz exit
    
    // v5 = v3 & 1;
    and ebx,01 { 1 }
    jnz @f
    
    push esi
    call CStreaming_releaseModel
    pop ecx
    
    push esi
    call releaseModel
    pop ecx
    
    @@:
    
    // check model flags for correct constructor
    mov edi,esi
    mov edi,[edi*4+"gta-vc.exe"+52C4D0]
    // get model handling id
    movsx eax,word ptr [edi+46]
    // DC - size of handling struct
    imul eax,eax,000000DC
    // calc model offset in handling array
    add eax,gta-vc.exe+577E60
    add eax,14
    
    // get model flags
    mov eax,[eax+cc]
    and eax,000f0000
    ror eax,4*4
    
    // cause Skimmer is a boat, not a plane O_o
    cmp esi,#190
    jne @f
    mov eax,8
    
    @@:
    // get correct constructor
    // == IS_BIKE
    cmp eax,1
    jne @f
    push CBike_constructor
    push #1260
    jmp create_vehicle
    
    @@:
    // IS_PLANE
    cmp eax,4
    jne @f
    jmp exit
    
    @@:
    // IS_BOAT
    cmp eax,8
    jne @f
    push CBoat_constructor
    push #1216
    jmp create_vehicle
    
    @@:
    // else if is a car
    push CAutomobile_constructor
    push #1500
    
    create_vehicle:
    // size param was been pushed above
    call CVehicle_new
    add esp,04
    
    mov ebx,eax
    test ebx,ebx
    jz exit
    
    // mov constructor func to eax
    pop eax
    // vehicle::
    mov ecx,ebx
    push 1
    push esi
    // call vehicle::constructor
    call eax
    mov ebx,eax
    test ebx,ebx
    jz exit
    
    // mov to eax player struct
    call GetPlayerPed
    
    // rotate car as player
    push 0
    fld dword ptr [eax+8]
    fld dword ptr [eax+4]
    fpatan
    fstp dword ptr [esp]
    push 0
    push 0
    
    // x into eax
    lea eax,[ebx+34]
    // placeble to ecx
    lea ecx,[ebx+04]
    
    call CPlacable_setRotation
    
    // move car to player
    call GetPlayerPed
    
    // small shift relatively to player
    push (float)5
    
    // x
    fld dword ptr [eax+34]
    fld dword ptr [esp]
    fld dword ptr [eax+8]
    fmulp
    fsubp
    fstp dword ptr [ebx+34]
    
    // y
    fld dword ptr [esp]
    fld dword ptr [eax+4]
    fmulp
    fld dword ptr [eax+38]
    faddp
    fstp dword ptr [ebx+38]
    
    // z
    push [eax+3c]
    pop [ebx+3c]
    
    // remove our shift from stack
    add esp,4
    
    // setup flags
    mov al,[ebx+50]
    and al,07
    or al,20
    mov [ebx+50],al
    
    // open the doors
    mov [ebx+00000230],1
    
    push ebx
    call CWorld_add
    add esp,04
    
    exit:
    ret
    
    
    [DISABLE]
    //code from here till the end of the code will be used to disable the cheat
    dealloc(create_vehicle_func)
    dealloc(model_index)
    
    unregistersymbol(create_vehicle_func)
    unregistersymbol(model_index)

     

     

    • Плюс 8
  9. [offtop]Это было далекие года назад, вряд-ли :) Плюс в основном писал не я, я больше по скриптам - зеркало там забабахать, сохранение инвентаря, etc. У меня упорства никогда не хватает - закончить хоть одну игру )[/offtop]

  10. 7 hours ago, Vasabist said:

    спасиб тебе, а не мне) буду разбираться. но все-таки, в чем там проблема то была. одним CE взломать нельзя, нужен раби? на самом деле нельзя найти базовый адрес? открыл твою новую тему, пока темный лес. да и первое видео, разрешение маленькое. пока недосмотрел.

     

    у меня что-то не получается :) может кто еще подскажет. в целом нужно выйти на move_speed, но это всё ruby-объекты и я не уверен как они выглядят в памяти. 

     

    Опять же, получилось сделать небольшой скрипт, который можно использовать в любом месте игры. Просто добавь его через Memory Viewer - Tools - AutoAssembler, вставляешь текст и жмешь - File - Assign to current cheat table.

    Spoiler
    
    [ENABLE]
    //code from here to '[DISABLE]' will be used to enable the cheat
    {$lua}
    if timer then
      timer.destroy()
      timer = nil
    end
    is_player_running = false
    
    function speedUp(...)
      if isKeyPressed(VK_SHIFT) then
        if not is_player_running then
            is_player_running = true
            autoAssemble([[
              cur_command:
              dd turn_on
    
              createthread(run_script)
            ]])
        end
      else
        if is_player_running then
          is_player_running = false
            autoAssemble([[
              cur_command:
              dd turn_off
    
              createthread(run_script)
            ]])
        end
      end
    end
    
    timer = createTimer()
    timer.setInterval(200)
    timer.setOnTimer(speedUp)
    timer.setEnabled(true)
    
    {$asm}
    globalalloc(run_script, 64)
    globalalloc(cur_command, 4)
    globalalloc(turn_on, 256)
    globalalloc(turn_off, 256)
    
    turn_on:
    db '$game_player.instance_variable_set(:@move_speed, 5)' #13 #10 0
    
    turn_off:
    db '$game_player.instance_variable_set(:@move_speed, 4)' #13 #10 0
    
    cur_command:
    dd turn_off
    
    run_script:
    push [cur_command]
    call RGSSEval
    pop eax
    ret
    
    [DISABLE]
    //code from here till the end of the code will be used to disable the cheat
    {$lua}
    if timer then
      timer.destroy()
      timer = nil
      is_player_running = nil
    end
    
    {$asm}
    dealloc(run_script)
    unregistersymbol(run_script)
    
    dealloc(turn_on)
    unregistersymbol(turn_on)
    
    dealloc(turn_off)
    unregistersymbol(turn_off)
    
    dealloc(cur_command)
    unregistersymbol(cur_command)

     

     

  11. 39 minutes ago, Garik66 said:

    srg91, круто!!! Очень круто!!!

    Только:

    1. я бы перенёс твои статьи в раздел "Статьи для продвинутых". - тут уже нужен более продвинутый уровень в GH и программировании ИМХО.

    2. может всё-таки уроки попробуешь в видео формате (с голосом конечно) - лучше 1 раз увидеть, чем много раз прочитать.

     

    Спасибо :) 

     

    1. Да, конечно, можно и перенести. Просто мне кажется, такое ощущение из-за того, что это всё текстом. В целом ничего продвинутого вроде бы не происходит, но возможно я не правильно оцениваю. Поэтому мне кажется нет проблем, если перенесем - главное, что информация где-то есть )

    2. В целом я структурировал текст, возможно попробую на досуге еще раз записать видео. Если получится - дозалью в "Видео" и добавлю ссылочку в начало статьи.

  12. Spoiler
    10 hours ago, Vasabist said:

    srg91, Серег, спасибо, но мне пока рано с луа. видимо у меня, просто непонимание самого перемещения по оси х и по оси у. раньше рассказывал, что на данной локации, по оси х нашел 2 разных адреса. один при заморозке пытается идти в сторону и возвращается на место. прибавляется +2 и отнимается. 

    второй адрес при заморозке. перемещается циклично при единичном нажатии на кнопку. прибавляется +256 и отнимается циклично

     

    при перемещении на другую локацию, эти адреса меняют свои данные,.. то есть было [31,31]. (правая вертикальная грань прямоугольника, середина) при перемещение стало [1,51]. (ось оХ, координата левой грани, сторона прямоугольника по оси ОУ увеличились и поэтому значение середины оси оУ стало больше), но это все локальные значения,.. хотя мне и нужно увеличение скорости на локальных значениях.. там в игре кстати есть телепорт, перемещение с одной локации на другую, тоже интересно.

     

    сделать скрипт на увеличение координаты не получается, вылетает.

    push edx

    mov edx,eax

    cmp edx, адрес

    pop edx

    je returnhere

     

    вот так скрипт работает, но при написании дополнительной строчки, просто еще раз там pop edx или inc edx - вылет

     

    найти вроде скорость ГГ возможно, в cheat engine есть спидхак. но не факт, что  не будет опять вылета. и инструкция анверно будет общей( с телепортом. вроде как можно 

     

    при нахождении базового адреса перемещения по оси ОХ, не получается найти... первый раз со смещением, значение перемещения оси ОХ совпадает, все нормально. второй раз без смещения, новый ардес нашел, нормально. третий же раз, значение перемещения по оси ОХ нового адреса не совпадает, заморозка не работает.. наверное руки не оттуда растут( 

     

    игра - легенда о царице опале

     

    Привет. Получилось сделать через Ruby, лови скрипт:

    Spoiler
    
    [ENABLE]
    // выделяем память на вызов функции
    globalalloc(run_script, 64)
    // выделяем память для текстовой команды на руби
    globalalloc(text, 1024)
    
    // Дописываем исходный класс
    text:
    // $game_system.se_play($data_system.escape_se)
    db 'class Game_Player < Game_Player' #13 #10
    db '  alias orig_initialize initialize' #13 #10
    db '  alias orig_update update' #13 #10
    db #13 #10
    db '  attr_accessor :move_speed' #13 #10
    db '  attr_accessor :walk_speed' #13 #10
    db '  attr_accessor :run_speed' #13 #10
    db #13 #10
    db '  def initialize' #13 #10
    db '    orig_initialize' #13 #10
    db #13 #10
    db '    @walk_speed = @move_speed' #13 #10
    db '    @run_speed = 5' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def running?' #13 #10
    db '    return (@move_speed == @run_speed)' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def start_run' #13 #10
    db '    $game_system.se_play($data_system.escape_se)' #13 #10
    db '    @move_speed = @run_speed' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def start_walk' #13 #10
    db '    @move_speed = @walk_speed' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def update' #13 #10
    db '    if Input.press?(Input::SHIFT)' #13 #10
    db '      if !running?' #13 #10
    db '        start_run' #13 #10
    db '      end' #13 #10
    db '    else' #13 #10
    db '      if running?' #13 #10
    db '        start_walk' #13 #10
    db '      end' #13 #10
    db '    end' #13 #10
    db #13 #10
    db '    orig_update' #13 #10
    db '  end' #13 #10
    db 'end' #13 #10
    db 0
    
    // наш вызов функции
    run_script:
    // передаем нашу команду
    push text
    // вызываем Eval
    call RGSSEval
    // двигаем стек за собой
    pop eax
    ret
    
    createthread(run_script)
    
    [DISABLE]
    dealloc(run_script)
    unregistersymbol(run_script)
    
    dealloc(text)
    unregistersymbol(text)

     

     

    Собственно пример использования:

    Spoiler

     

     

     

    Сделал через переопределение реальных классов, поэтому сработает только при включении чита в главном меню и только с новой игрой.

    А так же будущие сохранения можно будет загрузить только предварительно включив чит.

    Я думаю можно это обойти, но увы, на это у меня не было времени и скрипт превращается в кракозябры. Возможно немного позже :) 

    Если надоест звук - просто убери строчку:

    db '    $game_system.se_play($data_system.escape_se)' #13 #10

     

    Плюс расписал подробно, что делал - в статье.

    Спасибо тебе, что поднял эту тему - было интересно.

  13. Всем привет.

     

    Спасибо Vasabist, который поднял тему RPG Maker. Он попросил сделать возможность ускорения персонажа при нажатии клавиши Shift.

    Мы как-то давно с другом создавали игры на RPG Maker XP и я даже писал какие-то скрипты, расширяющие возможности персонажа.

    Тогда уже был VX, но он нам не нравился из-за мелких моделей персонажей. В общем увидел тему, нахлынуло :)

     

    Сами игры на RPG Maker XP - по сути большой набор Ruby-скриптов, которые выполняются движком RPG Maker с помощью Ruby-интерпретатора.

     

    Сначала я предложил Vasabist использовать фильтры, так как повторяется ситуация с lua-играми - один код работает со множеством адресов.

    Но потом вспомнил, что  RPG Maker есть такой класс объектов как Events, который так же позволяет выполнить произвольный код при срабатывании определенных условий. Решил по-исследовать - думал выйти на функцию eval, которой скорее всего и выполняется пользовательский код.

    Но всё оказалось даже проще, движок RPG Maker XP предоставляет функцию RGSSEval, которая выполняет произвольный код переданный в аргументах с доступом к глобальным переменным игры. 

     

    Собственно на RGSSEval выйти оказалось очень просто. Я скачал RPG Maker XP, создал на карте Event и попросил при взаимодействии с ним вывести строку "Hello, 1234444567":

    Spoiler

    R1.thumb.png.4b3660e559c2d906835ffc3bd8b

     

    После запустил игру и попробовал найти данную строку. И она нашлась! Ставим бряк на access и активируем Event. И в памяти мы видим следующее:

    Spoiler

    R2.thumb.png.aad079a98da7e88a536910116ee

     

    Да, ребят, это оказался memcpy. Но, это не главное. Главное что мы видим смещения адресов как RGSS104E.regex_error_code_to_str+52018, а это значит в движке RGSS104E присутствуют публичные функции, которые он любезно предоставляет нам. И открыв Memory View - View - Enumerate DLL's and Symbols мы видим RGSSEval:

    Spoiler

    R3.thumb.png.630f4ad2356bcd9f03dc5879a1d

     

    Осталось понять как её использовать. В целом на это довольно просто выйти, жмем два раза на функцию данном окне или просто прыгаем на неё и выполняем Memory View - Tools - Dissect Code. В появившемся окне жмем старт и видим всех, кто использует RGSSEval:

    Spoiler

    R4.thumb.png.7df87336914322b474ea8d7e1fa

     

    Двойным нажатием на один из (Call) под RGSSEval прыгаем на место использования и видим, что у данной функции один аргумент (один push с выполняемой строкой перед вызовом) и она не двигает за собой стек (add esp,8 - восемь из-за того, чтобы не двигать стек два раза):

    Spoiler

    R5.thumb.png.9c9f4866fd1fa367b6f6b6b76c9

     

    Собственно, можем копировать эти call-ы в наш AutoAssembler и пробовать вызывать. Для тестирования я набросал следующий скрипт:

    Spoiler
    
    [ENABLE]
    // выделяем память на вызов функции
    globalalloc(run_script, 64)
    // выделяем память для текстовой команды на руби
    globalalloc(text, 1024)
    
    // Собственно просто просим вывести Hello, World
    text:
    db 'print "Hello, world!"'
    
    // наш вызов функции
    run_script:
    // передаем нашу команду 
    push text
    // вызываем Eval
    call RGSSEval
    // двигаем стек за собой
    pop eax
    ret
    
    createthread(run_script)
    
    [DISABLE]
    dealloc(run_script)
    unregistersymbol(run_script)
    
    dealloc(text)
    unregistersymbol(text)

     

     

    Запускаем и видим следующее:

    Spoiler

    R6.thumb.png.fdf410a427ade034d317427fcc8

     

    Ура, функция работает как мы и ожидали. Проверить доступные глобальные переменные мы можем через print global_variables, либо обратившись к какой-нибудь из известных нам (или пока только мне..) переменных напрямую, например print $game_player. Можем творить!

     

    Собственно весь код игры на Ruby можно увидеть в меню Tools - Script Editor в RPG Maker. И скорее всего код в вашем текущем проекте будет совпадать в большом количестве игр на данном движке. Поэтому можем попытаться написать обертки для стандартных функций для выполнения наших хитрых задач:

    Spoiler

    R7.thumb.png.c9d928996d033c391097607f9e2

     

    Для начала рекомендую пробовать написать обертку в своем проекте, а после внедрять её в Cheat Engine (еще лучше для начала прочитать краткий мануал по Ruby, но я так рвался закончить код что пропустил этот пункт). И сразу оговорюсь, обертки над классами будут работать только до того, как эти классы станут объектами. Поэтому наш будущий чит можно будет активировать лишь раз - до начала игры. Это поправимо, Ruby позволяет определять функции у инстансов на лету, но код становится менее лаконичным, поэтому я пошел по простому пути.

     

    Собственно для того, чтобы ускорить нашего персонажа мы должны воздействовать на атрибут move_speed класса Game_Player. Он является приватным, поэтому для взаимодействия с ним лучше сделать его видимым для всех. Опять же нам повезло и у Game_Player есть метод update, в котором мы сможем проверять нажата ли клавиша Shift, но если бы его не было, пришлось бы менять move_speed из вне.

     

    Приступим. Проще всего будет исправить Game_Player создав класс Game_Player унаследовав его от оригинала:

    class Game_Player < Game_Player

    Для изменения move_speed нам понадобится сохранять его оригинальное значение и назначать скорость бега, выделим для этого две переменные - walk_speed и run_speed. Ну и заодно сделаем move_speed публичным:

      attr_accessor :move_speed
      attr_accessor :walk_speed
      attr_accessor :run_speed

    Добавляем функции переключения скоростей и простую проверку на то, бежит ли персонаж:

      def start_run
        @move_speed = @run_speed
      end
    
      def start_walk
        @move_speed = @walk_speed
      end
    
      def running?
        return (@move_speed == @run_speed)
      end

    Осталось установить начальные значения переменным и добавить проверку на нажатый SHIFT. Для этого обернем функции initialize и update класса Game_Player с помощью функции alias:

      alias orig_initialize initialize
    
      def initialize
        orig_initialize
    
        @walk_speed = @move_speed
        @run_speed = 5
      end

    Видите? Мы попросили обозначить родительский initialize как orig_initialize и вызывали его в нашей функции initialize.

    Скорость бега я установил на 5 (оригинальная скорость 4). Если увеличивать больше - персонаж просто летает и им неудобно управлять.

     

    Тоже самое сделаем с update. На нажатый SHIFT проверить очень просто - движок предоставляет объект Input, который знает нажата требуемая клавиша или нет (её я нашел в оригинальных скриптах игры):

      def update
        if Input.press?(Input::SHIFT)
          if !running?
            start_run
          end
        else
          if running?
            start_walk
          end
        end
    
        orig_update
      end

    Собственно и всё - скрипт готов. Если вы добавите его в оригинальные скрипты проекта, при нажатии клавиши SHIFT персонаж будет идти быстрее.

     

    Осталось просто заменить наш print "Hello, World" на получившийся скрипт.

    Выглядеть это будет следующим образом - каждую строку обрамляем в db 'xxxxx', где xxxx - строчка с кодом. В конце добавляем #13 #10 (каждое число через пробел) - это обычный Enter в конце строки (\n\r - если так привычнее). Я использовал для этого Sublime Text и его мультикурсоры, но можно воспользоваться простой заменой. Ну и не забываем 0 в конце, как конец текста. В итоге мы получим:

    Spoiler
    
    db 'class Game_Player < Game_Player' #13 #10
    db '  alias orig_initialize initialize' #13 #10
    db '  alias orig_update update' #13 #10
    db #13 #10
    db '  attr_accessor :move_speed' #13 #10
    db '  attr_accessor :walk_speed' #13 #10
    db '  attr_accessor :run_speed' #13 #10
    db #13 #10
    db '  def initialize' #13 #10
    db '    orig_initialize' #13 #10
    db #13 #10
    db '    @walk_speed = @move_speed' #13 #10
    db '    @run_speed = 5' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def running?' #13 #10
    db '    return (@move_speed == @run_speed)' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def start_run' #13 #10
    db '    @move_speed = @run_speed' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def start_walk' #13 #10
    db '    @move_speed = @walk_speed' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def update' #13 #10
    db '    if Input.press?(Input::SHIFT)' #13 #10
    db '      if !running?' #13 #10
    db '        start_run' #13 #10
    db '      end' #13 #10
    db '    else' #13 #10
    db '      if running?' #13 #10
    db '        start_walk' #13 #10
    db '      end' #13 #10
    db '    end' #13 #10
    db #13 #10
    db '    orig_update' #13 #10
    db '  end' #13 #10
    db 'end' #13 #10
    db 0

     

     

    Теперь просто заменяем db 'print "Hello, world!"' на получившийся код и можем запускать в главном меню игры :)

     

    Итоговый скрипт:

    Spoiler
    
    [ENABLE]
    // выделяем память на вызов функции
    globalalloc(run_script, 64)
    // выделяем память для текстовой команды на руби
    globalalloc(text, 1024)
    
    // Дописываем исходный класс
    text:
    db 'class Game_Player < Game_Player' #13 #10
    db '  alias orig_initialize initialize' #13 #10
    db '  alias orig_update update' #13 #10
    db #13 #10
    db '  attr_accessor :move_speed' #13 #10
    db '  attr_accessor :walk_speed' #13 #10
    db '  attr_accessor :run_speed' #13 #10
    db #13 #10
    db '  def initialize' #13 #10
    db '    orig_initialize' #13 #10
    db #13 #10
    db '    @walk_speed = @move_speed' #13 #10
    db '    @run_speed = 5' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def running?' #13 #10
    db '    return (@move_speed == @run_speed)' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def start_run' #13 #10
    db '    @move_speed = @run_speed' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def start_walk' #13 #10
    db '    @move_speed = @walk_speed' #13 #10
    db '  end' #13 #10
    db #13 #10
    db '  def update' #13 #10
    db '    if Input.press?(Input::SHIFT)' #13 #10
    db '      if !running?' #13 #10
    db '        start_run' #13 #10
    db '      end' #13 #10
    db '    else' #13 #10
    db '      if running?' #13 #10
    db '        start_walk' #13 #10
    db '      end' #13 #10
    db '    end' #13 #10
    db #13 #10
    db '    orig_update' #13 #10
    db '  end' #13 #10
    db 'end' #13 #10
    db 0
    
    // наш вызов функции
    run_script:
    // передаем нашу команду
    push text
    // вызываем Eval
    call RGSSEval
    // двигаем стек за собой
    pop eax
    ret
    
    createthread(run_script)
    
    [DISABLE]
    dealloc(run_script)
    unregistersymbol(run_script)
    
    dealloc(text)
    unregistersymbol(text)

     

     

    Итоговый скрипт на Ruby:

    Spoiler
    
    class Game_Player < Game_Player
      alias orig_initialize initialize
      alias orig_update update
    
      attr_accessor :move_speed
      attr_accessor :walk_speed
      attr_accessor :run_speed
    
      def initialize
        orig_initialize
    
        @walk_speed = @move_speed
        @run_speed = 5
      end
    
      def running?
        return (@move_speed == @run_speed)
      end
    
      def start_run
        @move_speed = @run_speed
      end
    
      def start_walk
        @move_speed = @walk_speed
      end
    
      def update
        if Input.press?(Input::SHIFT)
          if !running?
            start_run
          end
        else
          if running?
            start_walk
          end
        end
    
        orig_update
      end
    end

     

     

    Видео работы скрипта:

    Spoiler

    Для видео дополнительно добавил звук ускорения - $game_system.se_play($data_system.escape_se)

     

     

     

     

    Итого (или  рубрика "О проблемах"):

    • Скрипт прекрасно работает если включать его в меню, до запуска игры. Но не получится загрузить уже существующие сохранения, так как оно выполняет через дампы и нашему коду там просто не откуда взяться :) С этим можно бороться подключая код на-лету, но это уже в следующей серии.
    • Так же скрипт ломает будущие сохранения - игра не сможет загрузиться, если чит не был включен. Увы, это частая проблема игр использующих моды, а мы фактически этим и занимались.

     

    Вот и всё, народ )

      • Плюс 5
    • 23 minutes ago, saiberpro said:

      Я думаю тут проще отталкиваться от самого RPG Maker в виду того что все классы и модули доступны для редактирования и изучения, но единственный минус это язык на котором написан Редактор это Ruby.

       

      Собственно это я и хотел предложить :) 

      Для затравки кину данный скрипт, ну и пока сам попробую накидать изменение move_speed скриптом:

      Spoiler
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      globalalloc(run_script, 64)
      globalalloc(text, 1024)
      
      text:
      db 'print "Hello, World!"'
      
      run_script:
      push text
      call RGSSEval
      pop eax
      ret
      
      createthread(run_script)
      
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      dealloc(run_script)
      unregistersymbol(run_script)
      
      dealloc(text)
      unregistersymbol(text)

       

       

      • Плюс 1
    • 35 minutes ago, Vasabist said:

       

       

      а как в будущем, сделать комбинацию стрелка+шифт. если шифт вообще не задействован в игре. и чтобы не только по оси Х, но и У. и что все-таки за два адреса отвечающие за перемещение по оси Х? так же и с осью У. там вообще разные координаты. одна как бы отвечает за перемещение на локальнйо карте, там значение небольшое, второе измеряется в тысячах, но не пойму, почему при заморозке адреса, ГГ циклично перемещается туда сюда(

      я пробовал найти базовый адрес, вообще не получилось, первый адрес со смещением, значение адресов совпадает. второй - значения отличаются.

       

       

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

      Сначала думал написать про хоткеи на скрипт, но они переключают скрипт каждые сколько-то миллисекунд.

       

      В общем если ты сделаешь правильную фильтрацию, предлагаю тебе вынести значение скорости в отдельную "переменную" (в скрипт speedUp).

      После чего по таймеру проверять - нажат ли SHIFT:

      Spoiler
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      {$lua}
      if timer then
        timer.destroy()
        timer = nil
      end
      
      function speedUp(...)
        if isKeyPressed(VK_SHIFT) then
          writeInteger('char_speed', 10)
        else
          writeInteger('char_speed', 1)
        end
      end
      
      timer = createTimer()
      timer.setInterval(200)
      timer.setOnTimer(speedUp)
      timer.setEnabled(true)
      
      {$asm}
      alloc(char_speed, 4)
      registersymbol(char_speed)
      
      char_speed:
      dd #1
       
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      {$lua}
      if timer then
        timer.destroy()
        timer = nil
      end
      
      {$asm}
      
      char_speed:
      dd #1
      
      dealloc(char_speed)
      unregistersymbol(char_speed)

       

       

      • Плюс 1
    • Привет. Я думаю ты прав и эта инструкция работает с несколькими адресами.

      Возможно тебе поможет видео от Garik66, в котором он объясняет как можно написать фильтр для такой инструкции: 

      Spoiler

       

       

       

    • Всем привет.

      Сегодня я расскажу о том, как создавать нужный автомобиль с помощью CE в GTA Vice City.

      Как и говорил partoftheworlD, это очень просто, но раз возникают вопросы, я решил написать небольшой гайд.

       

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

      Поэтому выйдя на функцию обработки чита - мы найдем функции для создания автомобиля. Приступим.

       

      Для начала вспомним несколько читов для создания автомобилей.

      Я выбрал чит-код создания катафалка - THELASTRIDE , но так же можно использовать наш любимый чит создания танка - PANZER.

       

      Собственно ввод кодов - это операция по сохранению текущего введенного символа в строку и последующая проверка получившейся строки на соответствие.

      Каждый введенный символ записывается как в стек, поэтому в реальности эти читы выглядят как REZNAP и EDIRTSALEHT.

       

      Но если поискать данный текст, мы его не найдем. Как же так спросите вы? На самом деле функция обработки чит-кодов срабатывает на вводе последней буквы, поэтому проще всего просто искать часть введенного кода.

       

      Выйдем в меню игры, введем чит-код THELASTRIDE и попробуем поискать подстроку TSALEHT (не забываем переворачивать чит-код) в игре:

      Spoiler

      P1.thumb.png.37bb0683b0f54fad934bbcc0412

       

      Ура, мы нашли текст по адресу 00A0F94F. Не будем его запоминать, он нам не пригодится, а сразу посмотрим, что же творится в памяти.

      Нажмем на адрес правой кнопкой мыши и выбираем Browse this memory Region.

      Spoiler

      P2.thumb.png.42815bada58dad30680497a38f2

       

      В памяти сразу поднимемся на пару строк вверх, обычно строки длиннее чем мы искали:

      Spoiler

      P3.thumb.png.23caf0e48a82fad41a881ebfc64

       

      Ура, видим часть введенного нами кода. Так же могут присутствовать и другие куски текста, например на скришоте я немного побегал перед вводом чит-кода.

      Теперь мы видим, что в реальности строка ввода чит-кода больше чем мы ожидали, а её начало находится там, где присутсвуют первые символы DW DIRTSAL, т.е. по адресу 00A0F94A. Добавим этот адрес в табличку (размер строки вычислен эмпирическим путем - посчитал не нулевые байты):

      Spoiler

      P4.thumb.png.6d1ab64b9e78933bfca169a445b

       

      P5.png.4513c8c2e4a3a233dc46696b00f60c67.

       

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

       

      Посмотрим же, что использует этот буфер - попросим CE найти всех, кто использует данный адрес при вводе чит-кода

      Сначала жмем ПКМ на адресе в главном окне CE и выбираем Find out what accesses this address:

      Spoiler

      P6.thumb.png.3ecb741dd5b9fa98bec1e27e165

       

       

      Теперь переключимся в игру и введем любое слово, например HELLOGHL. Этим мы отсеим инструкции, которые используют наш адрес при обычном вводе. В окне появились следующие инструкции, мы будем их игнорировать:

      Spoiler

      P8.png.530d681785c6192145cb334badd1cdd4.

       

      Теперь введем чит-код на создание автомобиля - THELASTRIDE. Внимательно следим за окном с инструкциями, т.к. в процессе ввода у меня появились еще несколько инструкций, которые используют адрес, но не влияют на появление машины. После ввода последней буквы видим следующую картину:

      Spoiler

      P9.thumb.png.8e5684919d3da16feec9a18a6cb

       

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

      Spoiler

      P10.thumb.png.74f5b245595a506a5c10353594

       

      Ура, из предыдущих инструкций у нас осталось одна - инструкция mov byte ptr [00A0F94A] по адресу 004AC84A. Давайте выделим её и нажмем Show disassembler, чтобы увидеть, где она выполняется:

      Spoiler

      P11.thumb.png.bd128596e8f18b2ba9932fecf5

       

      Собственно мы видим некое условие, после которого в наш буфер записывает 0x20, он же 32 он же пробел и вызывается некая функция gta-vc.exe+AE7C0 с аргументом AC. Заочно можно нас поздравить, потому что мы нашли нужную нам функцию, осталось только убедиться в этом.

       

      Попробуйте тем же способом найти чит-функцию для создания танка. Я её уже нашел, давайте посмотрим:

      Spoiler

      P12.thumb.png.d96a6f6ce880e4bba3448185cd

       

      Видно, что функция находится немного в другом месте, но отличие состоит только в том, какой аргумент передается в функцию - push записывает в стек A2 , вместо AC. В остальном код условия идентичен и я предлагаю попробовать просто вызывать функцию gta-vc.exe+AE7C0 с разными параметрами.

       

      Для вызова функции мы будем использовать AutoAssembe и функцию createthread.

      Зайдем в AutoAssembler (откроем Memory Viewer, меню Tools - AutoAssemble) и набросаем следующий скрипт:

      Spoiler
      
      [ENABLE]
      // код, который выполнится при включении скрипта
      
      // выделяем память под нашу функцию, хватит и 64 байта
      alloc(our_func, 64)
      
      // начало нашей функции
      // фактически просто копируем код из игры
      our_func:
      // записываем в стек AC, он же 172
      push 000000AC
      // вызываем некую функцию
      call gta-vc.exe+AE7C0
      // убираем из стека наш AC
      pop ecx
      // конец функции
      ret
      
      
      // создаем поток, который прыгнет на нашу our_func и выполнит код
      createthread(our_func)
      
      [DISABLE]
      // код, который выполнится при выключении скрипта
      
      // просто освобождаем память от нашей функции
      dealloc(our_func)

       

       

      Добавим его в таблицу через File - Assign to current cheat table и можно закрывать окно Auto assemble.

      Скрипт выделяет в памяти игры 64 байта для себя, после чего записывает туда инструкции, которые мы скопировали из оригинальной игры и просит createthread выполнить их.

       

      Обратите внимание, что аргументы в данную функцию передаются как push AC. В оригинальной игре после call мы можем заметить инструкцию pop ecx. Это значит, что функция не трогает стек и мы сам должны двигать его за ним (т.е. откатить push AC). Поэтому после функции выполняется pop и мы обязательно должны его скопировать, иначе ret будет пытаться вернуть поток не куда-то по адресу 0xAA123456, а по адресу 0x000000AC. Почему pop использует ecx? В данном случае - потому что левая пятка компилятора так решила и это ни на что не влияет (ecx спокойно можно заменить на eax и другие регистры).

       

      Ура, скрипт в нашей таблице, давайте же активируем его! Смотрим на результат и...:

      Spoiler

      P13.thumb.png.075cdb4a7aac2fae6375e20a09

       

      Наш скрипт вызвал чит-функцию создания катафалка! Наверное вы уже догадались, что если мы заменим push AC на push A2 мы увидим заспаунившися танк?

      Spoiler

      P14.thumb.png.2b903763a6c9e338b14c802ada

       

      Получается, что AC - это катафалк, а A2 - танк. Поискав GTA Vice City Vehicle IDs, мы найдем, что действительно, A2 = 162 - это Vehicle ID танка, а AC = 172 - катафалк. Попробуем указать свой ID, один из списка, например 168 (такси). Передача аргумента превратится в push #168:

      Spoiler

      P15.thumb.png.8ee5ba5d10c662ea6ca3ad09e4

       

      Можно сказать, что наш скрипт работает! Осталось вынести ID модели как переменную:

      Spoiler
      
      [ENABLE]
      // код, который выполнится при включении скрипта
      
      // выделяем память под нашу функцию, хватит и 64 байта
      alloc(our_func, 64)
      // выделяем память под переменную
      alloc(our_vehicle_id, 4)
      
      // объявляем нашу our_func как переменную,
      // чтобы можно было вызывать createthread из другого скрипта
      registersymbol(our_func)
      
      // объявляем нашу our_vehicle_id как переменную,
      // чтобы можно было обращаться из таблицы
      registersymbol(our_vehicle_id)
      
      // записываем начальное занчение
      our_vehicle_id:
      // sabre turbo
      dd #206
      
      // начало нашей функции
      // фактически просто копируем код из игры
      our_func:
      // записываем в стек наш ID машины
      push [our_vehicle_id]
      // вызываем некую функцию
      call gta-vc.exe+AE7C0
      // убираем из стека наш AC
      pop ecx
      // конец функции
      ret
      
      [DISABLE]
      // код, который выполнится при выключении скрипта
      
      // удаляем информацию о функции
      dealloc(our_func)
      unregistersymbol(our_func)
      
      // удаляем информацию о переменной
      dealloc(our_vehicle_id)
      unregistersymbol(our_vehicle_id)

       

       

      Я удалил из скрипта функцию вызова потока, чтобы переместить её в отдельный скрипт:

      Spoiler
      
      [ENABLE]
      createthread(our_func)
      
      [DISABLE]

       

       

      Теперь можно добавить в таблицу адрес our_vehicle_id, в который после записи скрипта будет записано число 206 (ID наикрутейшего Sabre Turbo).

      Это число отвечает за то, что мы передаем в чит-функцию и меня его мы будем менять ID заспаунившегося автомобиля:

      Spoiler

      P16.png.983d25bd00d54824447b896b015d72f0

       

      Результат:

      Spoiler

      P17.thumb.png.fee85d7b679c07fcea0f81a33f

       

      И о проблемах :)

       

      Скрипт фактически вызывает чит-код, поэтому мы начинаем слыть читерами, рейтинг в игре уменьшается и т.д. Это довольно легко обходится, т.к. если мы провалимся в функцию gta-vc.exe+AE7C0, перед самым ret есть две инструкции - add [gta-vc.exe+5B4F94],000003E8 и mov byte ptr [gta-vc.exe+60FB37],01 . Первая накидывает очки читерства (они хранятся отдельно) и устанавливает флаг - "этот парень использовал коды". Если их занопить - игра никогда не узнает о ваших проделках.

       

      И вновь, потому что мы просто вызываем функцию чит-кода, она спаунит машины только на дорогу. Она находит ближайшую RoadPoint, прибавляет по оси Z несколько метров и спаунит авто. Поэтому создать авто прямо перед игроком так просто не получится.

       

      Ну и основная проблема - это то, что в данной функции использует конструктор только для автомобилей. Он так же используется для вертолетов, но вот байки и лодки с ним заспаунить не получится (заспаунится, но крешится при посадке и выглядит, кхм...):

      Spoiler

      P18.thumb.png.05628c7a7c78f64a8b9408bc74

       

      О том, как обойти эти проблемы и вызывать уже конструкторы классов мы узнаем в следующем уроке :)

      С видеоформатом гайдов мне сложно работать (довольно тяжело как смотреть, так и записывать их), поэтому уроки в текстовом формате.

       

       

      • Понравилось 1
      • Плюс 13
    • Всем привет. Я не особо опытный взломщик, поэтому пока взломал только новичковые пункты.

       

      Ссылка на таблицу: https://yadi.sk/d/ystXS4wv3EbhfB

       

      Код:

      Spoiler

      1. Do not increase difficult level

      Spoiler
      
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      "Train Me [v 2.0] By Taiwan.exe"+1FA6C5:
      db 90 90 90
      
       
       
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      "Train Me [v 2.0] By Taiwan.exe"+1FA6C5:
      inc [eax+0C]

       

       

      2. Set max score

      Spoiler
      
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      alloc(newmem,2048)
      globalalloc(max_score, 4)
      label(returnhere)
      label(originalcode)
      label(exit)
      
      max_score:
      dd #2147483647
      
      newmem: //this is allocated memory, you have read,write,execute access
      //place your code here
      
      originalcode:
      push [max_score]
      pop [edx+0C]
      lea edx,[ebp-04]
      
      exit:
      jmp returnhere
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA734:
      jmp newmem
      nop
      returnhere:
      
      
       
       
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      dealloc(newmem)
      "Train Me [v 2.0] By Taiwan.exe"+1FA734:
      add [edx+0C],eax
      lea edx,[ebp-04]
      //Alt: db 01 42 0C 8D 55 FC
      
      dealloc(max_score)
      unregistersymbol(max_score)

       

       

      3. Set max record score after game

      Spoiler
      
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      alloc(newmem,2048)
      globalalloc(record_score, 4)
      label(returnhere)
      label(originalcode)
      label(exit)
      
      record_score:
      dd #2147483647
      
      newmem: //this is allocated memory, you have read,write,execute access
      //place your code here
      mov edi,[record_score]
      
      originalcode:
      mov [esi+0C],edi
      lea edx,[ebp-5C]
      
      exit:
      jmp returnhere
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA50A:
      jmp newmem
      nop
      returnhere:
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA502:
      db 90 90 90 90 90 90
      
      
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      dealloc(newmem)
      "Train Me [v 2.0] By Taiwan.exe"+1FA50A:
      mov [esi+0C],edi
      lea edx,[ebp-5C]
      //Alt: db 89 7E 0C 8D 55 A4
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA502:
      jng "Train Me [v 2.0] By Taiwan.exe"+1FA594 { ->Train Me [v 2.0] By Taiwan.exe+1FA594 }
      
      dealloc(record_score)
      unregistersymbol(record_score)

       

       

      4. Beautiful date

      Spoiler
      
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      alloc(newmem,2048)
      globalalloc(beautiful_date, 8)
      
      label(returnhere)
      label(originalcode)
      label(exit)
      
      beautiful_date:
      dq (double)117955
      
      
      newmem: //this is allocated memory, you have read,write,execute access
      //place your code here
      
      originalcode:
      fstp qword ptr [ebp-20]
      fld qword ptr [beautiful_date]
      fstp qword ptr [ebp-20]
      wait 
      push [ebp-1C]
      
      exit:
      jmp returnhere
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA5ED:
      jmp newmem
      nop
      nop
      returnhere:
      
      
       
       
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      dealloc(newmem)
      "Train Me [v 2.0] By Taiwan.exe"+1FA5ED:
      fstp qword ptr [ebp-20]
      wait 
      push [ebp-1C]
      //Alt: db DD 5D E0 9B FF 75 E4
      
      dealloc(beautiful_date)
      unregistersymbol(beautiful_date)

      5. Beautiful time

      
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      alloc(newmem,2048)
      globalalloc(beautiful_time, 8)
      label(returnhere)
      label(originalcode)
      label(exit)
      
      beautiful_time:
      dq (double)0.52426
      
      newmem: //this is allocated memory, you have read,write,execute access
      //place your code here
      
      originalcode:
      fstp qword ptr [ebp-28]
      fld qword ptr [beautiful_time]
      fstp qword ptr [ebp-28]
      wait 
      push [ebp-24]
      
      exit:
      jmp returnhere
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA617:
      jmp newmem
      nop
      nop
      returnhere:
      
      
       
       
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      dealloc(newmem)
      "Train Me [v 2.0] By Taiwan.exe"+1FA617:
      fstp qword ptr [ebp-28]
      wait 
      push [ebp-24]
      //Alt: db DD 5D D8 9B FF 75 DC
      
      dealloc(beautiful_time)
      unregistersymbol(beautiful_time)

       

       

      5. Beautiful time

      Spoiler
      
      
      [ENABLE]
      //code from here to '[DISABLE]' will be used to enable the cheat
      alloc(newmem,2048)
      globalalloc(beautiful_time, 8)
      label(returnhere)
      label(originalcode)
      label(exit)
      
      beautiful_time:
      dq (double)0.52426
      
      newmem: //this is allocated memory, you have read,write,execute access
      //place your code here
      
      originalcode:
      fstp qword ptr [ebp-28]
      fld qword ptr [beautiful_time]
      fstp qword ptr [ebp-28]
      wait 
      push [ebp-24]
      
      exit:
      jmp returnhere
      
      "Train Me [v 2.0] By Taiwan.exe"+1FA617:
      jmp newmem
      nop
      nop
      returnhere:
      
      
       
       
      [DISABLE]
      //code from here till the end of the code will be used to disable the cheat
      dealloc(newmem)
      "Train Me [v 2.0] By Taiwan.exe"+1FA617:
      fstp qword ptr [ebp-28]
      wait 
      push [ebp-24]
      //Alt: db DD 5D D8 9B FF 75 DC
      
      dealloc(beautiful_time)
      unregistersymbol(beautiful_time)

       

       

       

      Видео:

      Spoiler

       

       

       

      С рандомными значениями не совсем понял, пока разбираюсь.

      Еще прошу дублировать задание текстом.

      • Плюс 1
    • Смотри, ты регистрируешь этот адрес как символ, после чего можешь узнать его адрес через функцию getAddress:

      Попробуй сделать так:

      function potokF(senderThread)
      autoAssemble([[
      aobscan(address3,90 5F 01 00 50 C3 00 01 00 50 C3 00 00)
      registersymbol(address3)
      ]])
      offset = 4
      someAddress = getAddress('address3') + offset
      end
      
      function CEButton6Click(sender)
      writeInteger(someAddress, 50)
      end

       

      • Плюс 1
    • 13 minutes ago, pachela said:

      Может кто подсказать как реализуется, а то, что то у меня не получается.

      Сначала при помощи AOBscaner мы находим адрес. Записываем его в переменную. Теперь мне нужно отталкиваясь от этого адреса найти новые адреса. Ну т.е. добавить смещение. Я пытался так

      
      someVAlue = "address + 4"

      Пробовал разные вариации. И с одинарной кавычкой и с квадратными скобками и т.п. Но результат один: В нужный мне адрес запись не происходит! Запись в АА не предлагать, я это умею делать, но мне нужно произвести либо writeInteger, а в АА я этого не умею делать. Либо пересчитывать значение с Интеджера в байты, а это мне кажется задачка посложнее, чем задать смещение уже имеющемуся адресу.

       

      Адрес в памяти, даже если и выглядит как страшные "0x12345678" - по сути число.

      Если ты делаешь это через LUA то просто возьми и прибавь к нему смещение без кавычек:

      offset = 4
      someAddress = mainAddress + offset

      Запись в память по этому адресу:

      writeInteger(someAddress, 666)

       

    ×
    ×
    • Создать...

    Важная информация

    Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.