• Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

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

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

Union PATCH-скрипты • PATCH Scripts

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.428
Благодарности
4.766
Баллы
625

RU EN


  • Структура PATCH-файла​

    Что такое PATCH-файл​

    PATCH-файл — это сценарий, который изменяет участки памяти процесса без компиляции.
    Все файлы с расширением .patch, находящиеся в директориях игры или в VDF-томах, автоматически выполняются при запуске.

    Скрипт регистронезависим. Каждая строка содержит один оператор или блок.

    Отключение патчей: #disable

    Инструкция всегда имеет наивысший приоритет — перечисленные патчи не выполнятся нигде.
    Union:
    #disable [Reference]
    #disable [Cast]

    Блоки движков: #engine

    Код внутри блока выполняется только для указанных exe.
    Поддерживаются:
    • CRC32 исполняемого файла
    • теги: G1, G1A, G2, G2A
      Можно перечислять несколько значений через запятую или не указывать ничего (выполняется всегда).
    Union:
    #engine [0x2BCD7E30, G2A]
    ...
    #/engine

    Блоки патчей: #patch

    Каждый блок выполняется автоматически.
    Имя GLOBAL DATA делает переменные видимыми во всех патчах.
    Union:
    #patch [GLOBAL DATA]
        INT GlobalInteger = 100
    #/patch

    Типы данных​

    Базовые типы:
    • INT → 12345, 0x12345
    • FLOAT → 123.45
    • BOOL → True / False
    • HEX → "строка" или 'байты через пробел'
    HEX может быть строкой и массивом байтов.
    Размер — через GetHexSize, SetHexSize, SetHexAutoSize.
    HEX + HEX работает как конкатенация.

    Ссылочные типы (@)​

    Ссылка указывает на адрес памяти (или параметр INI) и автоматически снимает защиту на запись.
    Union:
    INT @IntegerRef = 0x12345678
    HEX @HexRef     = 0x87654321
    
    IntegerRef = 800
    HexRef     = '12 34 56'

    Адрес можно задать выражением или напрямую:
    Union:
    FLOAT @0x12345678         = 50.5
    FLOAT @(0x12345678 + 400) = 35.3

    Ссылки на INI:
    Union:
    INT @SystemPack:Core:ShowDebugWindow = true

    Приведение типов (Cast)​

    Бывает автоматическим и явным:
    Union:
    INT   Value        = 5
    FLOAT AutoCast     = 10.0 + Value * 5.3
    FLOAT ExplicitCast = 10.0 + FLOAT Value * 5.3

    HEX снимает тип и превращает значение в байты:
    Union:
    INT SourceInt   = 65535
    HEX CastedHex   = HEX SourceInt
    INT RestoredInt = INT CastedHex

    Условия: IF / ELSE / END

    Выражение должно приводиться к BOOL:
    Union:
    IF BOOL @SystemPack:Core:ShowDebugWindow != true
        MessageBox("Debug window disabled")
    ELSE
        MessageBox("Debug window enabled")
    END

    Страницы памяти​

    Выделение и работа с выделенной страницей:
    Union:
    AllocPage(15, 1024)
    
    HEX @15x00000000 = ...
    HEX @15x00000111 = ...

    Статические патчи: #patch static

    Для патчей без условий и динамики — загружаются быстрее и кешируются.
    Union:
    #patch static [Example]
        HEX @0x007524A6 = 'E9 EB C2 F1 FF'
    #/patch

    Встроенные функции​

    Математика и значения​

    Sqrt(value)
    Возвращает квадратный корень числа (INT или FLOAT).

    Min(value1, value2)
    Возвращает минимальное значение.

    Max(value1, value2)
    Возвращает максимальное значение.

    Lim(minValue, currentValue, maxValue)
    Ограничивает значение диапазоном.

    Работа с HEX / памятью объектов​

    GetHexSize(value)
    Возвращает текущий размер HEX.

    SetHexSize(value, size)
    Устанавливает размер HEX вручную.

    SetHexAutoSize(value)
    Автоматически подбирает размер (строки выравниваются по \0).

    HexViewBox(bytePtr, size = auto)
    Показывает содержимое памяти как последовательность байтов.

    Сообщения и вывод​

    MessageBox(args …)
    Отображает диалоговое сообщение.

    PrintScreen(args …)
    Печатает текст в игровую консоль.

    Страницы памяти​

    AllocPage(index, size)
    Выделяет страницу памяти (начиная с 1).

    FreePage(index)
    Освобождает ранее выделенную страницу.

    Библиотеки и модули​

    LoadLibrary(libName)
    Загружает библиотеку в память.

    ModuleBase(moduleName)
    Базовый адрес модуля или -1.

    ModuleSize(moduleName)
    Размер модуля или -1.

    Поиск/замена и операции с памятью​

    FindAndReplace(from, to, startAddress, length)
    Ищет последовательность байтов и заменяет.

    MemSet(startAddress, bytePtr, length)
    Заполняет память шаблоном.

    MemCopy(startAddress, placeAddress, length)
    Копирует память.

    JMP(addressFrom, memTo)
    Создаёт переход (заменяет 5 байт).

    CALL(addressFrom, memTo)
    Создаёт вызов (заменяет 5 байт).

    Работа с INI и опциями​

    OptionDef(iniParameterRef, value)
    Создаёт параметр в INI и задаёт значение, если его ещё нет.

    Файловые операции​

    RenameFile(oldName, newName)
    Переименовывает файл.

    CopyFile(oldName, newName, replace = true)
    Копирует файл (с заменой по умолчанию).

    MoveFile(oldName, newName)
    Перемещает файл.

    DeleteFile(fileName)
    Удаляет файл.

    FileExists(fileName)
    Проверяет существование файла.

    Работа с плагинами и списками​

    LoadPlugins(pluginNames)
    Загружает перечисленные плагины, возвращает число успешных загрузок.

    ShowFunctionList()
    Печатает список всех патч-функций.

    Concat(args …)
    Объединяет аргументы в одну строку.

    Окна и процессы​

    ShowCmd() / HideCmd()
    Показывает / скрывает консоль.

    FindWindowHandle(className, windowName)
    Возвращает хэндл окна.

    GetProcessID(procName)
    ID процесса по имени.

    StartProcess(procName, cmdLine)
    Создаёт процесс и возвращает его ID (или -1).

    Система и игра​

    GetUnionVersion()
    Возвращает версию Union.

    FindSteamDirectory()
    Находит путь к Steam.

    Restart()
    Перезапускает игру.

    GetScreenSizeX() / GetScreenSizeY()
    Текущее разрешение экрана.

    GetLanguage()
    Язык Union (1–8: Rus/Eng/Deu/Pol/Rou/Ita/Cze/Esp).

    Ссылки, адреса и исполнение кода​

    GetRefAddress(reference)
    Адрес по ссылке или значению.

    ImportSymbol(moduleName, symName)
    Адрес символа в модуле (иначе -1).

    ExecAsm(code)
    Исполняет ассемблерный байткод и возвращает результат.

    Ассемблерные вставки​

    Общие сведения​

    Встроенные ассемблерные вставки создают участок байткода в памяти с правами чтения, записи и исполнения.
    Каждая вставка автоматически создаёт:
    • Переход к своей области памяти.
    • Обратный переход в исходную функцию (если адрес возврата не указан вручную, возвращаемся к следующей действительной инструкции после джампа).
    • Сохранение затёртых или частично затёртых инструкций в orgcode с пересчётом всех смещений.
    • Байткод вставки дополнен nop на конце для удобного дизассемблирования и отладки.

    Определение вставки​

    В квадратных скобках указывается адрес места, где будет вставлен 5-байтный jmp к байткоду вставки.
    Все инструкции, которые будут затёрты полностью или частично этим джампом, сохраняются в orgcode с корректным пересчётом смещений.

    Пример:
    Union:
    #assembler [0x0068CF02]
        ; байткод вставки
    #/assembler

    Если не указывать адрес возврата вручную, вставка автоматически возвращается к следующей действительной инструкции после джампа:
    Union:
    #/assembler [0x0068CF08]

    ВАЖНО!

    Переход к ассемблерной вставке должен полностью находиться внутри базового блока, то есть 5-байтовый джамп не должен частично пересекать действующую метку перехода.
    Пример: мы не можем создать вставку в диапазоне адресов 0x006C8767-0x006C876A, так как длина инструкций add + retn равна 4 байтам. Попытка вставить джамп здесь приведёт к пересечению с меткой перехода loc_6C876B:
    Union:
    .text:006C875B                 pop     ebx
    .text:006C875C                 mov     ecx, [esp+3Ch+var_C]
    .text:006C8760                 mov     large fs:0, ecx
    .text:006C8767                 add     esp, 3Ch
    .text:006C876A                 retn
    .text:006C876B ; ---------------------------------------------------------------------------
    .text:006C876B
    .text:006C876B loc_6C876B:                             ; CODE XREF: oCGame::Render(void)+28↑j
    .text:006C876B                                         ; oCGame::Render(void)+34↑j
    .text:006C876B                 mov     ecx, ?zrenderer@@3PAVzCRenderer@@A ; zCRenderer * zrenderer
    .text:006C8771                 mov     eax, [ecx]
    .text:006C8773                 call    dword ptr [eax+4]

    По той же причине ассемблерная вставка должна начинаться с начала инструкции, а не с её середины.

    Orgcode​

    Команда orgcode вставляет ранее затёртые или частично затёртые инструкции обратно в байткод.
    • Если orgcode не вызывается, затёртые инструкции никогда не выполняются.
    • Оператор + используется, если затёрто несколько инструкций. Он позволяет пропускать ненужные инструкции и выполнять только те, которые нужны.

    Пример использования​

    Исходный код:
    Union:
    0068CF02    imul    eax, [edi+40h]        4 bytes
    0068CF06    add     ecx, eax              2 bytes
    0068CF08    mov     [esp+24h+var_8], ecx  4 bytes
    0068CF0C    lea     esp, [esp+0]          4 bytes

    Вставка:
    Union:
    #patch [Close alpha-lines on multipage documents]
        #assembler [0x0068CF02]
            imul eax, [$multiplier]
            mov ebx, [edi+48h]
            imul ebx, -2
            add eax, ebx
            orgcode +1
        #/assembler
    #/patch
    Здесь:
    • orgcode +1 выполняет все инструкции, кроме первой затёртой (imul eax, [edi+40h]), пропуская её.
    • Остальные затёртые инструкции выполняются с пересчётом всех смещений.

    Передача переменных​

    Переменные, передаваемые во вставку, автоматически остаются в памяти до конца выполнения патча, если используются как хранилище данных.
    • Для HEX и переменных, участвующих как хранилище данных, берётся адрес.
    • Для остальных типов берётся значение.

    Пример практического патча​

    Выравнивание NPC относительно лестницы и использование переменной vector:
    Union:
    #engine [G2A]
        #patch [Aligning npc by ladder]
            HEX vector = '00 00 00 00 00 00 00 00 00 00 00 00' ; хранилище данных для вставки
    
            #assembler [0x00727E45]
                cmp dword ptr [ebp], 83CF2Ch ; проверка класса oCMobLadder
                jnz return
    
                mov ecx, ebp
                lea eax, [$vector]            ; передача переменной vector
                push eax
                call 52DCB0h                  ; GetAtVectorWorld
    
                mov ecx, edi
                test ecx, ecx
                jz return
    
                push eax
                call 61CBC0h                  ; SetHeadingAtWorld
    
            return:
                orgcode
            #/assembler [0x00727E4A]
        #/patch
    #/engine
    • Переменная vector служит хранилищем данных, сохраняется на протяжении всего патча.
    • Ассемблерная вставка использует её адрес для передачи данных внутрь байткода.

  • PATCH File Structure​

    What is a PATCH file​

    A PATCH file is a script that modifies process memory without compilation.
    All .patch files found in game directories or VDF archives run automatically on startup.

    Case-insensitive. One operator/block per line.

    Disabling patches: #disable

    Highest priority: listed patches never execute.
    Union:
    #disable [Reference]
    #disable [Cast]

    Engine blocks: #engine

    Code runs only for specified executables.

    Supports:
    • executable CRC32
    • tags: G1, G1A, G2, G2A
      Multiple values allowed, or none (runs always).
    Union:
    #engine [0x2BCD7E30, G2A]
    ...
    #/engine

    Patch blocks: #patch

    Executed automatically.
    GLOBAL DATA shares variables across patches.
    Union:
    #patch [GLOBAL DATA]
        INT GlobalInteger = 100
    #/patch

    Data types​

    Built-ins:
    • INT → 12345, 0x12345
    • FLOAT → 123.45
    • BOOL → True / False
    • HEX → "string" or 'bytes separated by spaces'
    HEX may act as text or byte array.
    Size via GetHexSize, SetHexSize, SetHexAutoSize.
    HEX + HEX concatenates.

    References (@)​

    Point to memory (or INI parameter) and unlock write automatically.
    Union:
    INT @IntegerRef = 0x12345678
    HEX @HexRef     = 0x87654321

    Direct addressing:
    Union:
    FLOAT @0x12345678         = 50.5
    FLOAT @(0x12345678 + 400) = 35.3

    INI references:
    Union:
    INT @SystemPack:Core:ShowDebugWindow = true

    Casting​

    Automatic and explicit:
    Union:
    INT   Value        = 5
    FLOAT AutoCast     = 10.0 + Value * 5.3
    FLOAT ExplicitCast = 10.0 + FLOAT Value * 5.3

    HEX converts to/from raw bytes:
    Union:
    INT SourceInt   = 65535
    HEX CastedHex   = HEX SourceInt
    INT RestoredInt = INT CastedHex

    Conditionals: IF / ELSE / END

    Union:
    IF BOOL @SystemPack:Core:ShowDebugWindow != true
        MessageBox("Debug window disabled")
    ELSE
        MessageBox("Debug window enabled")
    END

    Memory pages​

    Union:
    AllocPage(15, 1024)
    
    HEX @15x00000000 = ...
    HEX @15x00000111 = ...

    Static patches: #patch static

    Optimized for pure bytecode patches (no conditions).
    Union:
    #patch static [Example]
        HEX @0x007524A6 = 'E9 EB C2 F1 FF'
    #/patch

    Built-in Functions​

    Math​

    Sqrt(value)
    square root.

    Min(a, b)
    minimum.

    Max(a, b)
    maximum.

    Lim(min, value, max)
    clamp to range.

    HEX / object memory​

    GetHexSize(value)
    get size.

    SetHexSize(value, size)
    set size.

    SetHexAutoSize(value)
    auto size (null-terminated).

    HexViewBox(bytePtr, size = auto)
    show bytes as text.

    Messages & output​

    MessageBox(args …)
    popup message.

    PrintScreen(args …)
    print to console.

    Memory pages​

    AllocPage(index, size)
    allocate.

    FreePage(index)
    free.

    Libraries & modules​

    LoadLibrary(libName)
    load library.

    ModuleBase(moduleName)
    base address or -1.

    ModuleSize(moduleName)
    module size or -1.

    Search/replace & memory ops​

    FindAndReplace(from, to, startAddress, length)
    search & replace bytes.

    MemSet(startAddress, bytePtr, length)
    fill memory.

    MemCopy(startAddress, placeAddress, length)
    copy memory.

    JMP(addressFrom, memTo)
    insert jump (5 bytes).

    CALL(addressFrom, memTo)
    insert call (5 bytes).

    INI options​

    OptionDef(iniParameterRef, value)
    create parameter if missing and set value.

    Files​

    RenameFile(oldName, newName)
    rename.

    CopyFile(oldName, newName, replace = true)
    сopy.

    MoveFile(oldName, newName)
    move.

    DeleteFile(fileName)
    delete.

    FileExists(fileName)
    check existence.

    Plugins & utilities​

    LoadPlugins(pluginNames)
    load plugins, return count.

    ShowFunctionList()
    print available functions.

    Concat(args …)
    concatenate to string.

    Windows & processes​

    ShowCmd() / HideCmd()
    show/hide console.

    FindWindowHandle(className, windowName)
    window handle.

    GetProcessID(procName)
    process ID.

    StartProcess(procName, cmdLine)
    start process, return ID or -1.

    System & game​

    GetUnionVersion()
    Union version.

    FindSteamDirectory()
    Steam path.

    Restart()
    restart game.

    GetScreenSizeX() / GetScreenSizeY()
    resolution.

    GetLanguage()
    language (1–8).

    References, addresses, execution​

    GetRefAddress(reference)
    pointer address.

    ImportSymbol(moduleName, symName)
    symbol address or -1.

    ExecAsm(code)
    execute assembly and return result.

    Assembler Inserts​

    Overview​

    Built-in assembler inserts create a block of bytecode in memory with read, write, and execute permissions.
    Each insert automatically provides:
    • A jump to its bytecode area.
    • A return to the original function (if the return address is not specified manually, it returns to the next valid instruction after the jump).
    • Preservation of overwritten or partially overwritten instructions in orgcode, with all offsets recalculated.
    • The insert’s bytecode is padded with nop at the end for easier disassembly and debugging.

    Insert Definition​

    Inside the square brackets, specify the address where a 5-byte jmp to the insert will be placed.
    All instructions fully or partially overwritten by this jump are saved in orgcode with offsets recalculated.

    Example:
    Union:
    #assembler [0x0068CF02]
        ; insert bytecode
    #/assembler

    If no return address is provided manually, the insert automatically returns to the next valid instruction after the jump:
    Union:
    #/assembler [0x0068CF08]

    IMPORTANT!

    A jump to assembler insert must be fully contained within a basic block, meaning the 5-byte jump must not partially overlap an existing label.
    Example: you cannot create an insert in the address range 0x006C8767-0x006C876A, because the length of the instructions add + retn is 4 bytes. Attempting to insert a jump here would overlap the jump label loc_6C876B:
    Union:
    .text:006C875B                 pop     ebx
    .text:006C875C                 mov     ecx, [esp+3Ch+var_C]
    .text:006C8760                 mov     large fs:0, ecx
    .text:006C8767                 add     esp, 3Ch
    .text:006C876A                 retn
    .text:006C876B ; ---------------------------------------------------------------------------
    .text:006C876B
    .text:006C876B loc_6C876B:                             ; CODE XREF: oCGame::Render(void)+28↑j
    .text:006C876B                                         ; oCGame::Render(void)+34↑j
    .text:006C876B                 mov     ecx, ?zrenderer@@3PAVzCRenderer@@A ; zCRenderer * zrenderer
    .text:006C8771                 mov     eax, [ecx]
    .text:006C8773                 call    dword ptr [eax+4]

    For the same reason, an assembler insert must start at the beginning of an instruction, not in the middle of it.

    Orgcode​

    The orgcode command reinserts previously overwritten or partially overwritten instructions into the bytecode.
    • If orgcode is not called, the overwritten instructions are never executed.
    • The + operator is used if multiple instructions were overwritten. It allows skipping unnecessary instructions and executing only the ones needed.

    Example​

    Original code:
    Union:
    0068CF02    imul    eax, [edi+40h]        4 bytes
    0068CF06    add     ecx, eax              2 bytes
    0068CF08    mov     [esp+24h+var_8], ecx  4 bytes
    0068CF0C    lea     esp, [esp+0]          4 bytes

    Insert:
    Union:
    #patch [Close alpha-lines on multipage documents]
        #assembler [0x0068CF02]
            imul eax, [$multiplier]
            mov ebx, [edi+48h]
            imul ebx, -2
            add eax, ebx
            orgcode +1
        #/assembler
    #/patch
    Here:
    • orgcode +1 executes all overwritten instructions except the first one (imul eax, [edi+40h]), skipping it.
    • Other overwritten instructions are executed with offsets recalculated.

    Variable Passing​

    Variables passed into the insert automatically remain in memory until the end of the patch if used as a data storage.
    • For HEX and variables used as data storage, their address is passed.
    • For other types, the value is passed.

    Practical Patch Example​

    Aligning an NPC to a ladder and using the vector variable:
    Union:
    #engine [G2A]
        #patch [Aligning npc by ladder]
            HEX vector = '00 00 00 00 00 00 00 00 00 00 00 00' ; data storage for insert
    
            #assembler [0x00727E45]
                cmp dword ptr [ebp], 83CF2Ch ; check for oCMobLadder class
                jnz return
    
                mov ecx, ebp
                lea eax, [$vector]            ; pass the vector variable
                push eax
                call 52DCB0h                  ; GetAtVectorWorld
    
                mov ecx, edi
                test ecx, ecx
                jz return
    
                push eax
                call 61CBC0h                  ; SetHeadingAtWorld
    
            return:
                orgcode
            #/assembler [0x00727E4A]
        #/patch
    #/engine
    • The vector variable acts as data storage and remains valid for the entire patch.
    • The assembler insert uses its address to pass data into the bytecode.
 
Последнее редактирование:
Сверху Снизу