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

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

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

    Ознакомиться с ними можно здесь

Gothic ½ Union - формат .patch файла

Статус
В этой теме нельзя размещать новые ответы.

Gratt


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

Русский English


  • Общее описание формата патчей
    Рекомендуется использовать Notepad++ для редактирования файлов патчей, а также установить расширение для подсветки синтаксиса из Менеджера ресурсов в разделе 'Синтаксис .patch'. Желательно использовать только кодировки ANSI, либо UTF8 с BOM и Unicode с BOM. Остальные могут привести к неожиданным последствиям. Приоритет запуска определяется нижним подчеркиванием вначале имени файла. Чем больше подчеркиваний, тем выше приоритет. Если подчеркивания отсутствуют, то вначале запускается Union.patch, затем все остальные.
    Union:
    /*
     * Union PATCH
     * -----------
     * Файл патчей - это скрипт, позволяющий изменять любые участки
     * памяти процесса. Не требует предварительной компиляции, а
     * все .patch файлы, лежащие в директориях игры или VDF томах,
     * будут запущены игрой автоматически. Скрипт является
     * регистронезависимым, на каждый блок операторов выделяется
     * по одной строке.
     *
     * Ниже приведен краткий пример типичного файла
     * патчей и комментарии для каждой инструкции.
     */
    
    // Инструкция #disable [<PATCH NAME>] служит для запрета выполнения патчей.
    // Она имеет наивысший приоритет. Это значит, что в каком бы файле
    // не находились перечисленные патчи - они однозначно будут отключены.
    #disable [Reference]
    #disable [Cast]
    
    // Процедуры патчей выполняются внутри блоков #engine [<ENGINE>].
    // Это блоки, которые выполняются при запуске конкретных ехе файлов.
    // В качестве ехе могут указываться как crc32 от exe файла,
    // так и теги (G1, G1A, G2, G2A). Для выполнения патчей под несколько
    // платформ, следует перечислить их через запятую. Либо не указывать
    // аргументы вообще, тогда блок будет выполнен не зависимо от ехе.
    // Таких блоков #engine может быть несколько на один файл.
    #engine [0x2BCD7E30, G2A] // crc32, tag
    
      // В роли процедуры патча выступает блок #patch [<NAME>].
      // Все блоки патча выполняются автоматически поочереди.
      // Каждый блок патча может иметь собственный набор локальных переменных,
      // однако если блок называется `GLOBAL DATA`, то переменные внутри него
      // могут использоваться Глобально - всеми остальными блоками патчей.
      #patch [GLOBAL DATA]
    
        // Внутри блоков патчей могут объявляться и использоваться любые
        // описываемые далее типы. По умолчанию патчи имеют 4 базовых типа:
        // INT - целое, ассоциируется с 12345, 0x12345
        // FLOAT - вещественное, ассоциируется с 123.45
        // BOOL - логическое, ассоциируется с True, False
        // HEX - строка/байткод, ассоциируется с "12345", '12 34 05'
        //
        // На последнем типе остановимся подробнее. Этот тип может использоваться
        // и как строка, так и обычный массив байтов. При этом размерность такой
        // переменной можно вычислить функцией GetHexSize, либо задать через SetHexSize.
        // или SetHexAutoSize. Оператор HEX + HEX будет работать по принципу сложения
        // строк. Использование одинарной кавычки (') расценивается патчем как
        // последовательность байтов, разделенных пробелом.
        INT GlobalInteger = 100
        FLOAT GlobalFloat = 200.0
        HEX GlobalHex     = "300" + "400"
        BOOL GlobalDeclaration
      #/patch
    
    
      #patch [Reference]
        // Помимо обычных типов также существуют ссылочные типы. Это такие типы,
        // память который определена не программой патча, а некоторой областью в
        // процессе. Особенностью таких ссылок является не только то, что через
        // них можно присваивать значения в любую область памяти процесса, но и то,
        // что они самостоятельно занимаются разблокировкой защищенной от записи памяти.
        // В отличии от переменных, ссылочные переменные обязаны быть определены конкретным адресом.
        // Для этого после указания типа ссылки следует добавить символ @.
        // В качестве адреса указывается целое число.
        INT @IntegerRef = 0x12345678 + GlobalInteger
        HEX @HexRef     = 0x87654321
    
        // После определения ссылки, она может принимать участие в выражениях, а
        // ее значение будет строго привязано к адресу, которым она была определена.
        IntegerRef = GlobalIntegerRef + 800
        HexRef     = '12 34 56 78 9A BC'
    
    
        // Также при необходимости можно напрямую работать со ссылками не создавая никакие
        // переменные. Для этого вместо уникального идентификатора указывается либо
        // число, либо INT переменная с адресом. Будьте осторожны используя переменную
        // в качестве адреса такой ссылки, ведь синтаксически это идентично определению
        // ссылочной переменной (в отладочную консоль будет выведено предупреждение).
        FLOAT @0x12345678         = 50.5
        FLOAT @(0x12345678 + 400) = 35.3
        FLOAT @GlobalInteger      = 10.1 // Не переменная! GlobalInteger – это INT переменная из
                                         // блока GLOBAL DATA, которая определяет адрес ссылки!
    
    
        // Ссылки могут не только определять какую-то область памяти, но и параметры
        // INI файлов. Для этого вместо адреса следует указать файл:блок:параметр.
        // В качестве файла можно указывать SystemPack, Game (Gothic.ini), Mod (<Mod>.ini)
        // и Overrides_SP. Последний позволяет переопределять значения SystemPack, не
        // трогая его физическую подлинник, но имея над ним больший приоритет.
        INT @SystemPack:Core:ShowDebugWindow = true
      #/patch
    
    
      #patch [Cast]
        // Приведения типов в патчах может быть как автоматическим, так и явным.
        // Автоматическое приведение типов означает, что в выражении тип правого операнда
        // будет приведен к типу левого операнда. Чтобы произвести явное приведение типов,
        // укажите имя этого типа перед операндом.
        INT   Value        = 5
        FLOAT AutoCast     = 10.0 +       Value * 5.3 // = 35.0 – потеря данных, так как INT * FLOAT преобразуется в INT * INT
        FLOAT ExplicitCast = 10.0 + FLOAT Value * 5.3 // = 36.5 – точность не страдает, так как FLOAT INT * FLOAT преобразуется во FLOAT * FLOAT
    
    
        // Приведение к HEX и обратно работает иначе. HEX как бы снимает тип с объекта,
        // преобразуя его в набор байтов. По этому принципу работает и обратная операция,
        // когда набор байтов приводится к какому-то типу.
        INT SourceInt   = 65535
        HEX CastedHex   = HEX SourceInt // = '00 00 FF FF', размер данных 4 байта
        INT RestoredInt = INT CastedHex // = 65535
      #/patch
    
    
      #patch [Operetor IF]
        // Патчи поддерживают условные блоки IF. Передаваемое в них выражение должно
        // приводиться к BOOL. Если значение равно True, то блок будет выполнен.
        // Иначе - пропущен, а при наличии блока ELSE - будет выполнен последний.
        IF BOOL @SystemPack:Core:ShowDebugWindow != true
          MessageBox("Debug window disabled")
        ELSE
          MessageBox("Debug window enabled")
    
          // sub if
          IF INT @0x12345678 > 500
            MessageBox("Block works")
          END
        END
      #/patch
    
    
      #patch [Memory pages]
        // Иногда патчам требуется достаточное место в памяти для занесения туда каких-либо
        // байткодов, либо для хранения информации. В этом случае средствами патчей можно
        // выделять страницы памяти с доступом для чтения, записи и выполнения. Используйте
        // функцию AllocPage(INT index, INT size) для выделения страницы с индексом index
        // (любой свободный, но не меньше 1) и размером size.
        AllocPage(15, 1024)
    
        // Для обращения к странице используйте шестнадцатеричный формат записи адреса, где вместо
        // `0x` указывается индекс выделенной страницы в десятичном формате. Тогда адрес будет рассчитываться как
        // <адрес страницы> + <смещение>
        HEX @pageStart  = 15x00000000 // Создание ссылки, адрес которой соответствует началу 15-ой страницы памяти
        HEX @pageRandom = 15x00000111 // Создание ссылки, адрес которой соответствует началу 15-ой страницы памяти + 0x111 байтов
      #/patch
    
    
      #patch static [Keyword 'static']
        // Ключевое слово static применяется, когда необходимо оптимизировать загрузку
        // патчей, не имеющих условных операторов или динамических операций. Патч,
        // содержащий исключительно байткод, отлично подходит для такой цели. Union
        // записывает всю изменяемую статическими патчами память и сохраняет на диск
        // одним большим куском. При следующем запуске, если патч не изменялся,
        // бинарные данные сразу же будут помещены в память, а скриптовые блоки
        // статических патчей - исключены из синтаксического разбора.
     
        HEX @0x007524A6 = 'E9 EB C2 F1 FF'
        HEX @0x0066E796 = '8B 0D 50 16 8D 00 8B 11 6A 05 FF 52 04 3C 01 0F 85 73 40 0E 00 E8 E8 47 16 00 E9 F6 3C 0E 00'
     
        // Если патч выполняется из VDF тома, то для него не будет создаваться файл со
        // статической памятью, однако он может быть прочитан, если такой файл был
        // создан заранее и помещен рядом с исходным патчем.
      #/patch
    #/engine
    
    /*
     * Список встроенных функций
     */
    
    // Возвращает квадратный корень value числа
    INT or FLOAT Sqrt(INT or FLOAT value)
    
    // Выводит на экран сообщение со строкой
    // из перечисленных args аргументов
    VOID MessageBox(args ...)
    
    // Выводит в консоль сообщение со строкой
    // из перечисленных args аргументов
    VOID PrintScreen(args ...)
    
    // Выделяет страницу памяти с индексом index
    // (любой уникальный, но не меньше 1) и размером
    // size байтов.
    VOID AllocPage(INT index, INT size)
    
    // освобождает страницу памяти с индексом index
    VOID FreePage(INT index)
    
    // Возвращает используемый размер памяти HEX объекта
    INT GetHexSize(HEX value)
    
    // Задает используемый размер памяти HEX объекта
    VOID SetHexSize(HEX value, INT size)
    
    // Автоматически задает используемый размер памяти
    // HEX объекта, выравнивая его как строку по символу \0
    VOID SetHexAutoSize(HEX value)
    
    // Загружает в память библиотеку libName
    INT LoadLibrary(HEX libName)
    
    // Возвращает базовый адрес модуля moduleName, иначе -1
    INT ModuleBase(HEX moduleName)
    
    // Возвращает минимальное из двух значений value1 и value2
    INT or FLOAT Min(INT or FLOAT value1, INT or FLOAT value2)
    
    // Возвращает максимальное из двух значений value1 и value2
    INT or FLOAT Max(INT or FLOAT value1, INT or FLOAT value2)
    
    // Возвращает значение currentValue, но не
    // меньше minValue и не больше maxValue
    INT or FLOAT Lim(INT or FLOAT minValue, INT or FLOAT currentValue, INT or FLOAT maxValue)
    
    // Выводит на экран сообщение, в котором находится последовательность
    // из size байтов объекта bytePtr. При отсутствии аргумента size
    // длина строки будет взята автоматически по размеру объекта bytePtr.
    VOID HexViewBox(HEX bytePtr, INT size = auto)
    
    // Находит и заменяет from последовательность байтов на to
    // последовательность байтов, начиная со startAddress длиною length
    VOID FindAndReplace(HEX from, HEX to, INT startAddress, INT length)
    
    // Создаем ключ iniParameterRef в ini файле, если до этого
    // он не был создан и присваивает ему начальное значение value
    VOID OptionDef(INT or FLOAT or BOOL or HEX iniParameterRef, INT or FLOAT or BOOL or HEX value)
    
    // Возвращает размер модуля moduleName, иначе -1
    INT ModuleSize(HEX moduleName)
    
    // Создает консольное окно
    VOID ShowCmd()
    
    // Уничтожает консольное окно
    VOID HideCmd()
    
    // Возвращает адрес символа symName из
    // модуля moduleName, иначе -1
    INT ImportSymbol(HEX moduleName, HEX symName)
    
    // Заполняет память со startAddress
    // по шаблону bytePtr длиною length
    VOID MemSet(INT startAddress, HEX bytePtr, INT length)
    
    // Копирует память начиная со startAddress
    // в placeAddress длиною length
    VOID MemCopy(INT startAddress, INT placeAddress, INT length)
    
    // Возвращает хендлер окна windowName класса className
    INT FindWindowHandle(HEX className, HEX windowName)
    
    // Загружает плагины pluginNames, перечисленные через запятую
    // и возвращает количество успешных загрузок
    INT LoadPlugins(HEX pluginNames)
    
    // Возвращает версию Union в текстовом формате
    HEX GetUnionVersion()
    
    // Переименовывает файле oldName в newName
    VOID RenameFile(HEX oldName, HEX newName)
    
    // Копирует файл oldName в newName с условием замены replace (по умолчанию rue)
    VOID CopyFile(HEX oldName, HEX newName, BOOL replace = true)
    
    // Перемещает файл oldName в newName
    VOID MoveFile(HEX oldName, HEX newName)
    
    // Удаляет файл fileName
    VOID DeleteFile(HEX fileName)
    
    // Проверяет, существует ли файл fileName
    BOOL FileExists(HEX fileName)
    
    // Выводит в консоль списов всех патч-функций
    VOID ShowFunctionList()
    
    // Возвращает путь к директории Steam
    HEX FindSteamDirectory()
    
    // Возвращает ID процесса с именем procName
    INT GetProcessID(HEX procName)
    
    // Создает новый процесс procName и командной строкой cmdLine
    // и возвращает ID созданного процесса, иначе -1
    INT StartProcess(HEX procName, HEX cmdLine)
    
    // Соединяет все переданные в функцию аргументы args
    // в текстовую строку и возвращает ее
    HEX Concat(agrs ...)
    
    // Возвращает текущее разрешение экрана по X
    INT GetScreenSizeX()
    
    // Возвращает текущее разрешение экрана по Y
    INT GetScreenSizeY()
    
    // Перезагружает игру
    VOID Restart()
    
    // Создает инструкцию перехода по адресу addressFrom на память memTo
    // Заменяет 5 байт по адресу addressFrom
    VOID JMP(INT addressFrom, HEX memTo)
    
    // Создает инструкцию вызова по адресу addressFrom на память memTo
    // Заменяет 5 байт по адресу addressFrom
    VOID CALL(INT addressFrom, HEX memTo)
    
    // Возвращает язык используемый в Union
    // Rus = 1, Eng = 2, Deu = 3, Pol = 4
    // Rou = 5, Ita = 6, Cze = 7, Esp = 8
    INT GetLanguage()
    
    // Возвращает адрес ссылки reference
    INT GetRefAddress(HEX or INT or FLOAT reference)
    
    // Выполняет код ассемблерной вставки code (эквивалент API вызова)
    // Возвращает результат процедуры
    INT ExecAsm(HEX code)

  • General description of the PATCH format
    It is recommended to use Notepad ++ to edit patch files, and also install the syntax highlighting extension from the Resource manager under the 'Syntax .patch' section. It is advisable to use only ANSI encodings, or UNT8 with BOM and Unicode with BOM. The rest can lead to unexpected consequences. The startup priority is defined by the underscore at the prefix of the file name. The more underscores, the higher the priority. If there are no underscores, then Union.patch is run first, then all the others.
    Union:
    /*
     * Union PATCH
     * -----------
     * The patch file is a script that allows you to change any areas
     * process memory. Does not require pre-compilation, but
     * all .patch files located in game directories or VDF volumes,
     * will be launched by the game automatically. The script is
     * case-insensitive, for each block of operators allocated
     * one line at a time.
     *
     * Below is a short example of a typical file
     * patches and comments for each instruction.
     */
    
    
    
    
    // The #disable [<PATCH NAME>] instruction is used to disable the execution of patches.
    // It has the highest priority. This means that in whatever file
    // the listed patches were found - they will definitely be disabled.
    #disable [Reference]
    #disable [Cast]
    
    
    // Patch routines are executed inside #engine [<ENGINE>] blocks.
    // These are the blocks that are executed when specific exe files are run.
    // exe can be specified as crc32 from exe file,
    // and tags (G1, G1A, G2, G2A). To perform patches for multiple
    // platforms, separate them with commas. Or do not specify
    // arguments in general, then the block will be executed regardless of exe.
    // There can be several such #engine blocks per file.
    
      // The #patch [<NAME>] block works as a patch procedure.
      // All patch blocks are executed automatically one by one.
      // Each block can have its own set of local variables,
      // however, if the block is called `GLOBAL DATA`, then the variables inside it
      // can be used Globally - by all other patch blocks.
      #patch [GLOBAL DATA]
         // Any patch can be declared and used inside patch blocks.
         // types described below. By default, patches are of 4 basic types:
         // INT is an integer associated with 12345, 0x12345
         // FLOAT - real, associated with 123.45
         // BOOL - boolean, associated with True, False
         // HEX - string / bytecode, associated with "12345", '12 34 05 '
         //
         // Let's get deeper on the last type in more detail. This type can be used
         // both a string and a regular byte array. Moreover, the dimension is
         // variable can be calculated using the GetHexSize function, or set via SetHexSize.
         // or SetHexAutoSize. The HEX + HEX operator will work on the principle of addition
         // lines. The use of a single quote (') is interpreted by the patch as
    
         // a sequence of bytes separated by a space.
        INT GlobalInteger = 100
        FLOAT GlobalFloat = 200.0
        HEX GlobalHex     = "300" + "400"
        BOOL GlobalDeclaration
      #/patch
    
    
      #patch [Reference]
        // Besides regular types, there are also reference types. These are the types
        // memory which is not defined by the patch program, but by some area in
        // process. The peculiarity of such links is not only that through
        // they can be assigned values to any area of the process memory, but even then
        // that they are unlocking the write-protected memory on their own.
        // Unlike variables, reference variables must be defined with a specific address.
        // To do this, add the @ symbol after specifying the link type.
    
        // An integer is specified as the address.
        INT @IntegerRef = 0x12345678 + GlobalInteger
        HEX @HexRef     = 0x87654321
    
        // After defining the link, it can take part in expressions, and
        // its value will be strictly linked to the address it was defined by.
        IntegerRef = GlobalIntegerRef + 800
        HexRef     = '12 34 56 78 9A BC'
    
    
         // a reference variable (a warning will be displayed in the debug console).
         // Also, if necessary, you can directly work with links without creating any
         // variables. To do this, instead of a unique identifier, either
         // number, or INT variable with address. Be careful when using a variable
         // as the address of such a link, because syntactically this is identical to the definition
    
         // a reference variable (a warning will be displayed in the debug console).
        FLOAT @0x12345678         = 50.5
        FLOAT @(0x12345678 + 400) = 35.3
        FLOAT @GlobalInteger      = 10.1 // Not a variable! GlobalInteger is an INT variable from
                                          // GLOBAL DATA block, which defines the link address!
    
    
    
         // Links can not only define some memory area, but also parameters
         // INI files. To do this, instead of the address, you should specify the file: block: parameter.
         // SystemPack, Game (Gothic.ini), Mod (<Mod> .ini) and Overrides_SP. can be specified as a file.
        // The latter allows you to override the SystemPack values without editing its original object,
        // but having higher priority over it.
        INT @SystemPack:Core:ShowDebugWindow = true
      #/patch
    
    
      #patch [Cast]
        // Type casts in patches can be either automatic or explicit.
        // Automatic type casting means that the type of the right operand in the expression
        // will be cast to the type of the left operand. To make an explicit cast,
        // put the name of this type before the operand.
        INT   Value        = 5
    
       FLOAT AutoCast = 10.0 + Value * 5.3 // = 35.0 - data loss, as INT * FLOAT is converted to INT * INT
       FLOAT ExplicitCast = 10.0 + FLOAT Value * 5.3 // = 36.5 - accuracy does not suffer, since FLOAT INT * FLOAT is converted to FLOAT * FLOAT
    
    
    
         // Casting to HEX and back works differently. HEX, as it were, removes the type from the object,
         // converting it to a set of bytes. The reverse operation works according to this principle,
         // when a set of bytes is cast to some type.
        INT SourceInt   = 65535
        HEX CastedHex   = HEX SourceInt // = '00 00 FF FF', data size 4 bytes
        INT RestoredInt = INT CastedHex // = 65535
      #/patch
    
    
      #patch [Operetor IF]
        // Patches support conditional IF blocks. The expression passed to them must
         // cast to BOOL. If the value is True, then the block will be executed.
         // Otherwise - skipped, and if there is an ELSE block, the last one will be executed.
        IF BOOL @SystemPack:Core:ShowDebugWindow != true
          MessageBox("Debug window disabled")
        ELSE
          MessageBox("Debug window enabled")
    
          // sub if
          IF INT @0x12345678 > 500
            MessageBox("Block works")
          END
        END
      #/patch
    
    
      #patch [Memory pages]
         // Sometimes patches need enough space in memory to store any
         // bytecodes, or for storing information. In this case, using patches you can
         // allocate memory pages with read, write and execute access. Use
         // function AllocPage (INT index, INT size) to allocate the page with the index index
         // (any free, but not less than 1) and size.
        AllocPage(15, 1024)
    
    
         // To refer to the page, use the hexadecimal address format, where instead of
         // `0x` indicates the index of the selected page in decimal format. Then the address will be calculated as
         // <page address> + <offset>
         HEX @pageStart = 15x00000000 // Create a link, the address of which corresponds to the beginning of the 15th memory page
         HEX @pageRandom = 15x00000111 // Create a link whose address corresponds to the beginning of the 15th page of memory + 0x111 bytes
      #/patch
    
    
      #patch static [Keyword 'static']
        // The static keyword is used when it is necessary to optimize loading
        // patches that have no conditional statements or dynamic operations. Patch,
        // containing only bytecode is great for this purpose. Union
        // writes all static patched memory and saves it to disk
        // in one big piece. At the next start, if the patch has not changed,
        // binary data will be immediately put into memory, and static blocks
        // patches - excluded from parsing.
     
        HEX @ 0x007524A6 = 'E9 EB C2 F1 FF'
        HEX @ 0x0066E796 = '8B 0D 50 16 8D 00 8B 11 6A 05 FF 52 04 3C 01 0F 85 73 40 0E 00 E8 E8 47 16 00 E9 F6 3C 0E 00'
    
        // If the patch is executed from a VDF volume, a file for such patch
        // will not be created with  static memory, however it can be read if
        // such a file was  pre-created and placed next to the original patch.
      #/patch
    #/engine
    
    
    /*
     * A list of built-in functions
     */
    
    
    // Returns the square root of value of a number
    INT or FLOAT Sqrt(INT or FLOAT value)
    
    // Displays a message with a string
    // from the listed args arguments
    VOID MessageBox(args ...)
    
    // Print a message to the console with the string
    // from the listed args arguments
    VOID PrintScreen(args ...)
    
    // Allocates memory page with index index
    // (any unique, but not less than 1) and size bytes
    VOID AllocPage(INT index, INT size)
    
    // frees the memory page at index index
    VOID FreePage (INT index)
    
    // Returns the used memory size of the HEX object
    INT GetHexSize (HEX value)
    
    // Sets the used memory size of the HEX object
    VOID SetHexSize (HEX value, INT size)
    
    // Automatically sets the used memory size
    // HEX of the object, aligning it as a string at the \ 0 character
    VOID SetHexAutoSize (HEX value)
    
    // Loads the libName library into memory
    INT LoadLibrary (HEX libName)
    
    // Returns the base address of the module moduleName, otherwise -1
    INT ModuleBase (HEX moduleName)
    
    // Returns the minimum of the two values value1 and value2
    INT or FLOAT Min (INT or FLOAT value1, INT or FLOAT value2)
    
    // Returns the maximum of the two values value1 and value2
    INT or FLOAT Max (INT or FLOAT value1, INT or FLOAT value2)
    
    // Returns currentValue, but not
    // less than minValue and not more than maxValue
    INT or FLOAT Lim (INT or FLOAT minValue, INT or FLOAT currentValue, INT or FLOAT maxValue)
    
    // Displays a message containing the sequence
    // from size bytes of object bytePtr. With no size argument
    // the string length will be taken automatically according to the size of the bytePtr object.
    VOID HexViewBox (HEX bytePtr, INT size = auto)
    
    // Finds and replaces from byte sequence with to
    // sequence of bytes starting at startAddress and length
    VOID FindAndReplace (HEX from, HEX to, INT startAddress, INT length)
    
    // Create iniParameterRef key in ini file, if before
    // it was not created and assigns an initial value to it
    VOID OptionDef (INT or FLOAT or BOOL or HEX iniParameterRef, INT or FLOAT or BOOL or HEX value)
    
    // Returns the size of the module moduleName, otherwise -1
    INT ModuleSize (HEX moduleName)
    
    // Creates a console window
    VOID ShowCmd ()
    
    // Destroys the console window
    VOID HideCmd ()
    
    // Returns the address of the symbol symName from
    // module moduleName, otherwise -1
    INT ImportSymbol (HEX moduleName, HEX symName)
    
    // Fill memory with startAddress
    // bytePtr template with length length
    VOID MemSet (INT startAddress, HEX bytePtr, INT length)
    
    // Copies memory starting at startAddress
    // in placeAddress with length
    VOID MemCopy (INT startAddress, INT placeAddress, INT length)
    
    // Returns the window handler windowName class className
    INT FindWindowHandle (HEX className, HEX windowName)
    
    // Loads plugins pluginNames, separated by commas
    // and returns the number of successful downloads
    INT LoadPlugins (HEX pluginNames)
    
    // Returns the text version of Union
    HEX GetUnionVersion ()
    
    // Renames file oldName to newName
    VOID RenameFile (HEX oldName, HEX newName)
    
    // Copies file oldName to newName with replace condition (rue by default)
    VOID CopyFile (HEX oldName, HEX newName, BOOL replace = true)
    
    // Move file oldName to newName
    VOID MoveFile (HEX oldName, HEX newName)
    
    // Delete file fileName
    VOID DeleteFile (HEX fileName)
    
    // Checks if file fileName exists
    BOOL FileExists (HEX fileName)
    
    // Prints all patch functions to the console
    VOID ShowFunctionList ()
    
    // Returns the path to the Steam directory
    HEX FindSteamDirectory ()
    
    // Returns the ID of the process named procName
    INT GetProcessID(HEX procName)
    
    // Creates a new process procName and command line cmdLine
    // and returns the ID of the created process, otherwise -1
    INT StartProcess(HEX procName, HEX cmdLine)
    
    // Concatenates all args passed to the function
    // into a text string and returns it
    HEX Concat(agrs ...)
    
    // Returns the current screen resolution in X
    INT GetScreenSizeX()
    
    // Returns the current screen resolution in Y
    INT GetScreenSizeY()
    
    // Restarts the game
    VOID Restart()
    
    // Creates a jump instruction at 'addressFrom' to 'memTo' memory
    // Replaces 5 bytes at 'addressFrom'
    VOID JMP(INT addressFrom, HEX memTo)
    
    // Creates a call instruction at 'addressFrom' on 'memTo' memory
    // Replaces 5 bytes at 'addressFrom'
    VOID CALL(INT addressFrom, HEX memTo)
    
    // Returns the language used in Union
    // Rus = 1, Eng = 2, Deu = 3, Pol = 4
    // Rou = 5, Ita = 6, Cze = 7, Esp = 8
    INT GetLanguage()
    
    // Returns the address of the 'reference'
    INT GetRefAddress(HEX or INT or FLOAT reference)
 
Последнее редактирование:

Gratt


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

Русский English


  • Пример создания внешней функции
    Для выполнения каких-то неспецифических задач в патче используются внешние функции, логика которых описывается языком C++. Чтобы добавить собственную функцию, необходимо создать плагин на основе инструментов Union SDK. Предположим, нам необходима функция, возвращающая подробную информацию о запущенном exe файле для последующей ее обработки.

    1. Создаем чистый проект плагина.
    1607698835598.png

    2. Далее в любом удобном месте программы следует создать новую структуру (для меня это DllMain.cpp), производную от класса CPatchFunction. Внутри этой структуры определить PATCH_CLASS_DECLARATION для вашего класса. И объявить виртуальную функцию Execute - исполняемую функцию внешки.
    C++:
    struct CPatchFuncGetExeDetails : public CPatchFunction {
      PATCH_CLASS_DECLARATION( CPatchFuncGetExeDetails );
      virtual CPatchType* Execute();
      };

    3. Затем нужно определить класс в Union через макрос PATCH_CLASS_DEFINITION. Первым аргументов указывается Ваш класс. Вторым - назначение символа (в нашем случае это функция 'Function') и его имя (по которому мы сможем к нему обращаться 'GetExeDetails')
    После этого новая внешняя функция будет видна для патчей.
    Также добавляем пустые определения конструктора и деструктора нашей структуры, поскольку в нашей задаче никаких специфических операций в них производить не имеет смысла.
    C++:
    PATCH_CLASS_DEFINITION( CPatchFuncGetExeDetails, "Function:GetExeDetails" );
    CPatchFuncGetExeDetails:: CPatchFuncGetExeDetails() {}
    CPatchFuncGetExeDetails::~CPatchFuncGetExeDetails() {}

    4. Далее описываем саму функцию Execute. Для того, чтобы сразу вернуть несколько результатов вычисления функции, будем записывать их прямо в передаваемые аргументы.
    Будем принимать 5 аргументов:
    HEX gameDirectory - путь к директории игры.
    HEX systemFolderName - путь к системной папке игры.
    HEX exeFileName - имя исполняемого файла.
    INT gameVersion - версия игры G1, G1A, G2, G2A (1, 2, 3, 4 соответственно).
    INT exeHash - хеш-сумма исполняемого файла.

    Вернем True или False, в зависимости от того, действительна ли текущая операция. Например, если запущен спейсер или vdfs32.exe, то перед нами явно не готика. Выходим из данной операции с ошибкой.

    Аргументы функции лежат в объекте arrParameters, в виде символов, которые необходимо привести к конкретным типам через динамик каст. Если аргументы патча проходят все Ваши проверки на валидность, то начинаем записывать в них информацию.
    C++:
    CPatchType* CPatchFuncGetExeDetails::Execute() {
      // Создаем, инициализируем и определяем bool переменную,
      // возвращающую результат выполнения функции.
      CPatchBool* returnValue = new CPatchBool();
      returnValue->Init();
      returnValue->SetValue( False );
    
    
      // Возвращаем False, если текущий ехе не является
      // готикой (например если спейсер или vdfs32.exe)
      if( Union.GetEngineVersion() == Engine_Unknown )
        return returnValue;
    
    
      // Проверяем количество переданных аргументов в функцию.
      // Если их количество не удовлетворяет условию функции,
      // выводим ошибку и возвращаем False.
      if( arrParameters.GetNum() != 5 ) {
      argumentsError:
        Message::Error( "Function 'GetExeDetails' takes 5 parameters:\n1. HEX gameDirectory\n2. HEX systemFolderName\n3. HEX exeFileName\n4. INT gameVersion\n5. INT exeHash" );
        return returnValue;
      }
    
    
      // Пытаемся привести аргументы к необходимым типам
      CPatchHex* gameDirectory    = dynamic_cast<CPatchHex*>(arrParameters[0]);
      CPatchHex* systemFolderName = dynamic_cast<CPatchHex*>(arrParameters[1]);
      CPatchHex* exeFileName      = dynamic_cast<CPatchHex*>(arrParameters[2]);
      CPatchInteger* gameVersion  = dynamic_cast<CPatchInteger*>(arrParameters[3]);
      CPatchInteger* exeHash      = dynamic_cast<CPatchInteger*>(arrParameters[4]);
    
      if( !gameDirectory || !systemFolderName || !exeFileName || !gameVersion || !exeHash )
        goto argumentsError;
    
    
      // Записываем полный путь к папке игры
      string tmp = Union.GetGameDirectory();
      gameDirectory->SetValue( tmp.ToChar(), tmp.Length() + 1 );
    
      // Записываем полный путь к системной папке игры
      tmp = Union.GetSystemDirectory();
      systemFolderName->SetValue( tmp.ToChar(), tmp.Length() + 1 );
    
      // Получаем и записываем имя файла текущего exe
      char name[512];
      int nameLength = GetModuleFileName( Null, name, 512 );
      tmp = (A name).GetWord( "\\", -1 );
      exeFileName->SetValue( tmp.ToChar(), tmp.Length() + 1 );
    
      // Записываем версию текущего движка, G1, G1A, G2 или G2A
      gameVersion->SetValue( Union.GetEngineVersion() );
    
      // Записываем хеш-сумму текущего exe файла
      exeHash->SetValue( Union.GetGothicHash() );
    
    
      // Задаем и возвращаем True
      returnValue->SetValue( True );
      return returnValue;
    }

    5. По этому принципу набросаем функцию, переводящую INT в строку шестнадцатеричного вида целого числа. Это нам пригодится для вывода хеш-суммы исполняемого файла.
    C++:
    struct CPatchFuncIntToHex : public CPatchFunction {
      PATCH_CLASS_DECLARATION( CPatchFuncIntToHex );
      virtual CPatchType* Execute();
    };
    
    PATCH_CLASS_DEFINITION( CPatchFuncIntToHex, "Function:IntToHex" );
    
    CPatchFuncIntToHex:: CPatchFuncIntToHex() {}
    CPatchFuncIntToHex::~CPatchFuncIntToHex() {}
    
    CPatchType* CPatchFuncIntToHex::Execute() {
      // Также определяем возвращаемое
      // функцией значение
      CPatchHex* hexView = new CPatchHex();
      hexView->Init();
      hexView->SetValue("", 1);
    
    
      // Проверяем аргументы на ошибки
      if( arrParameters.GetNum() != 1 ) {
      argumentsError:
        Message::Error( "Function 'IntToHex' takes 1 parameter:\n1. INT sourceInt" );
        return hexView;
      }
    
      CPatchInteger* sourceInt = dynamic_cast<CPatchInteger*>(arrParameters[0]);
      if( !sourceInt )
        goto argumentsError;
    
      string tmp = AHEX32( sourceInt->GetValue() );
      hexView->SetValue( tmp.ToChar(), tmp.Length() + 1 );
      return hexView;
    }

    6. После написания кода, собираем проект под любой платформой, так как она не принципиальна. Код функции патча не должен принадлежать к мультиплатформенному интерфейсу, так как он не имеет связей с конкретным API готики.
    Помещаем собранный плагин в папку System и создаем Patch файл, который сперва должен загрузить новый плагин, чтобы он мог обращаться к Вашей функции. Примечание: для правильного отображения символов патча не в ANSI кодировке, следите, чтобы активная кодировка имела BOM. Для этого рекомендуется использовать продвинутые текстовые редакторы типа Notepad++.
    Union:
    // Тег движка не указываем, нам без разницы какой
    // именно движок запустит следующие патчи.
    #engine
        #patch [Execute new external function]
            // Загрузка плагина, в котором определены
            // функции GetExeDetails и IntToHex
            INT OK = LoadPlugins("ExternalPatchFunction.dll")
    
            // Проверяем, что наш плагин был загружен. Функция
            // LoadPlugins возвращает количество успешно загруженных
            // плагинов. Соответственно 'OK' не должно быть равно нулю.
            IF OK != 0
                // Обявляем переменные, которые передадим в
                // качестве аргументов. В них будет записана
                // информация по текущему исполняемому файлу.
                HEX gameDirectory
                HEX systemFolderName
                HEX exeFileName
                INT gameVersion
                INT exeHash
    
                // Теперь получаем саму информацию. При этом подставляем
                // вызов функции в блок IF, чтобы начать обработку
                // информации только в случае успеха операции.
                IF GetExeDetails(gameDirectory, systemFolderName, exeFileName, gameVersion, exeHash)
                    HEX message = "Информация по текущему exe"
                    message += Concat("\nПуть к игре: ", gameDirectory)
                    message += Concat("\nПуть к папке System: ", systemFolderName)
                    message += Concat("\nИмя исполняемого файла: ", exeFileName)
                    message += Concat("\nВерсия движка игры: ", gameVersion)
                    message += Concat("\nХеш-сумма ехе файла: ", IntToHex(exeHash)) // А вот где пригодилась функция IntToHex :)
                    MessageBox(message)
                ELSE
                    MessageBox("Процесс не является готикой!")
                END
            END
        #/patch
    #/engine

    7. При попытке запуска игры Union подгрузит Ваш Patch файл, который в свою очередь запустит плагин с новыми функциями. Если все прошло успешно, то Вы получите похожее окно:
    1607704229072.png


    8. Далее для удобства данные Patch и Dll можно засунуть в отдельный VDF том или к моду. Таким образом созданные Вами файлы не будут занимать полезное место в каталогах игры.

  • An example of creating an external function
    To perform some non-specific tasks, the patch uses external functions, the logic of which is described in the C ++ language. To add your own function, you need to create a plugin based on the Union SDK tools. Suppose we need a function that returns detailed information about the running exe file for further processing.

    1. Create an empty plugin project.
    1607698835598.png

    2. Then, in any convenient place in the program, you should create a new structure (for me it is DllMain.cpp), derived from the CPatchFunction class. Inside this structure, define the PATCH_CLASS_DECLARATION for your class. And declare a virtual function Execute - an executable external function.
    C++:
    struct CPatchFuncGetExeDetails : public CPatchFunction {
      PATCH_CLASS_DECLARATION( CPatchFuncGetExeDetails );
      virtual CPatchType* Execute();
      };

    3. Then you need to define the class in Union via the PATCH_CLASS_DEFINITION macro. The first argument is your class. The second is the purpose of the symbol (in our case it is the 'Function' function) and its name (by which we can refer to it 'GetExeDetails')
    The new external function will then be visible for patches.
    We also add empty definitions of the constructor and destructor of our structure, since in our task it makes no sense to perform any specific operations on them.
    C++:
    PATCH_CLASS_DEFINITION( CPatchFuncGetExeDetails, "Function:GetExeDetails" );
    CPatchFuncGetExeDetails:: CPatchFuncGetExeDetails() {}
    CPatchFuncGetExeDetails::~CPatchFuncGetExeDetails() {}

    4. Next, we describe the function Execute itself. In order to return several results of the function calculation at once, we will write them directly into the passed arguments.

    We will take 5 arguments:
    HEX gameDirectory - path to the game directory.
    HEX systemFolderName - path to the system folder of the game.
    HEX exeFileName is the name of the executable file.
    INT gameVersion - version of the game G1, G1A, G2, G2A (1, 2, 3, 4, respectively).
    INT exeHash - hash of the executable file.

    Returning True or False, depending on whether the current operation is valid. For example, if a spacer or vdfs32.exe is running, then we are clearly not gothic. We exit this operation with an error.

    The function arguments lie in the arrParameters object, in the form of symbols that must be converted to specific types through the cast speaker. If the arguments of the patch pass all your validity checks, then we begin to write information into them.
    C++:
    CPatchType* CPatchFuncGetExeDetails::Execute() {
       // Create, initialize and define a bool variable,
       // returning the result of the function execution.
      CPatchBool* returnValue = new CPatchBool();
      returnValue->Init();
      returnValue->SetValue( False );
    
       // Return False if the current exe is not
       // gothic (for example, if a spacer or vdfs32.exe)
      if( Union.GetEngineVersion() == Engine_Unknown )
        return returnValue;
    
    
    
       // Check the number of arguments passed to the function.
       // If their number does not satisfy the function condition,
       // print an error and return False.
      if( arrParameters.GetNum() != 5 ) {
      argumentsError:
        Message::Error( "Function 'GetExeDetails' takes 5 parameters:\n1. HEX gameDirectory\n2. HEX systemFolderName\n3. HEX exeFileName\n4. INT gameVersion\n5. INT exeHash" );
        return returnValue;
      }
    
    
      // Trying to cast arguments to the required types
      CPatchHex* gameDirectory    = dynamic_cast<CPatchHex*>(arrParameters[0]);
      CPatchHex* systemFolderName = dynamic_cast<CPatchHex*>(arrParameters[1]);
      CPatchHex* exeFileName      = dynamic_cast<CPatchHex*>(arrParameters[2]);
      CPatchInteger* gameVersion  = dynamic_cast<CPatchInteger*>(arrParameters[3]);
      CPatchInteger* exeHash      = dynamic_cast<CPatchInteger*>(arrParameters[4]);
    
      if( !gameDirectory || !systemFolderName || !exeFileName || !gameVersion || !exeHash )
        goto argumentsError;
    
    
    // Write the full path to the game folder
      string tmp = Union.GetGameDirectory();
      gameDirectory->SetValue( tmp.ToChar(), tmp.Length() + 1 );
    
      // Write down the full path to the system folder of the game
      tmp = Union.GetSystemDirectory();
      systemFolderName->SetValue( tmp.ToChar(), tmp.Length() + 1 );
    
      // Get and write the filename of the current exe
      char name[512];
      int nameLength = GetModuleFileName( Null, name, 512 );
      tmp = (A name).GetWord( "\\", -1 );
      exeFileName->SetValue( tmp.ToChar(), tmp.Length() + 1 );
    
    // Write down the version of the current engine, G1, G1A, G2 or G2A
      gameVersion->SetValue( Union.GetEngineVersion() );
    
      // Write the hash of the current exe file
      exeHash->SetValue( Union.GetGothicHash() );
    
    
      // Set and return True
      returnValue->SetValue( True );
      return returnValue;
    }

    5.Following this principle, we sketch out a function that converts INT to a hexadecimal integer string. This is useful for us to display the hash sum of the executable file.

    C++:
    struct CPatchFuncIntToHex : public CPatchFunction {
      PATCH_CLASS_DECLARATION( CPatchFuncIntToHex );
      virtual CPatchType* Execute();
    };
    
    PATCH_CLASS_DEFINITION( CPatchFuncIntToHex, "Function:IntToHex" );
    
    CPatchFuncIntToHex:: CPatchFuncIntToHex() {}
    CPatchFuncIntToHex::~CPatchFuncIntToHex() {}
    
    CPatchType* CPatchFuncIntToHex::Execute() {
       // Also define the returned
       // function value
      CPatchHex* hexView = new CPatchHex();
      hexView->Init();
      hexView->SetValue("", 1);
    
    
      // Check arguments for errors
      if( arrParameters.GetNum() != 1 ) {
      argumentsError:
        Message::Error( "Function 'IntToHex' takes 1 parameter:\n1. INT sourceInt" );
        return hexView;
      }
    
      CPatchInteger* sourceInt = dynamic_cast<CPatchInteger*>(arrParameters[0]);
      if( !sourceInt )
        goto argumentsError;
    
      string tmp = AHEX32( sourceInt->GetValue() );
      hexView->SetValue( tmp.ToChar(), tmp.Length() + 1 );
      return hexView;
    }

    6. After writing the code, we assemble the project for any platform, since it is not fundamental. The patch function code does not have to belong to the multiplatform interface, as it has no links to a specific gothic API.
    We place the assembled plugin in the System folder and create a Patch file, which must first load the new plugin so that it can access your function. Note: for correct display of non-ANSI patch symbols, make sure that the active encoding has a BOM. For this, it is recommended to use advanced text editors such as Notepad ++.
    Union:
     // Don't specify the engine tag, it doesn't matter to us what
    // the engine will run the next patches.
    #engine
        #patch [Execute new external function]
           // Load the plugin that defines
          // GetExeDetails and IntToHex functions
            INT OK = LoadPlugins("ExternalPatchFunction.dll")
    
             // Check that our plugin has been loaded. Function
             // LoadPlugins returns the number of successfully loaded
             // plugins. Accordingly, 'OK' should not be zero.
            IF OK != 0
            // Declare the variables that we will pass to
            // as arguments. They will contain
            // information on the current executable file.
                HEX gameDirectory
                HEX systemFolderName
                HEX exeFileName
                INT gameVersion
                INT exeHash
    
              
                 // Now we get the information itself. In this case, we substitute
                 // call the function in the IF block to start processing
                 // information only if the operation is successful.
                IF GetExeDetails(gameDirectory, systemFolderName, exeFileName, gameVersion, exeHash)
                    HEX message = "Current exe info"
                    message += Concat("\nPath to game folder: ", gameDirectory)
                    message += Concat("\nPath to system folder: ", systemFolderName)
                    message += Concat("\nExe name: ", exeFileName)
                    message += Concat("\nEngine version: ", gameVersion)
                    message += Concat("\nХExe hash sum: ", IntToHex(exeHash)) // And this is where the IntToHex function we use ;)
                    MessageBox(message)
                ELSE
                    MessageBox("The process is not Gothic!!")
                END
            END
        #/patch
    #/engine

    7. When you try to start the game, Union will load your Patch file, which in turn will launch the plugin with new functions. If everything went well, you will receive a similar window:
    `132132.jpg


    8. Further, for convenience, the Patch and Dll data can be shoved into a separate VDF volume or to a mod. Thus, the files you create will not take up useful space in the game directories.
 
Последнее редактирование модератором:

Gratt


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

Русский English


  • API вызовы
    Из патчей можно производить вызовы с соглашением о вызовах _cdecl и _stdcall по известному или импортируему адресу. Для вызова произвольной функции достаточно привести ее адрес HEX@. Количество и типы параметров при этом игнорируются, а контролируется исключительно программистом. Пример вызова функции MessageBoxA средствами API вызова:
    Union:
        // Пример реализации API вызова
        #patch
            INT MB_YESNO        = 0x00000004
            INT MB_ICONQUESTION = 0x00000020
            INT IDYES           = 6
     
            // Импорт функции и последующий ее вызов. Если функция возвращает
            // значение, то он будет возвращен в виде INT значения, которое
            // можно привести к любому другому типу.
            HEX @imp_MessageBoxA = ImportSymbol("User32.dll", "MessageBoxA")
            INT result = imp_MessageBoxA(0, "Hello, world!\0", "\0", MB_YESNO | MB_ICONQUESTION)
     
            // Тест, если был выбран ответ ДА, то будет
            // выведено дополнительное сообщение на экран.
            IF result == IDYES
                MessageBox("Selected choice YES")
            ELSE
        #/patch

    Ассемблерные вставки
    Блок ассемблера генерирует в памяти участок байткода с доступом для чтения, записи и исполнения. Для возможности обращения к памяти программа создает переменную типа HEX@ по имени блока, которая ссылается на эту память. Длину байткода можно определить функцией GetHexSize. Вставка может размещаться как в глобальной области документа (внутри блока #engine), так и внутри конкретного патча. При этом, если вставка будет заключена внутри оператора IF, генерация байткода произойдет согласно выражению условного блока.

    Ассемблерные вставки делятся на два типа:
    1. Динамическая вставка - блок ассемблера может быть собран и вызван напрямую. Этот тип используется по умолчанию.
    2. Статическая вставка - блок ассемблера собирается под определенную область памяти. Отличие от предыдущего только в том, что все смещения считаются относительно явно заданного базового адреса. Сгенерированный байткод нужно скопировать в необходимую область памяти (см. функцию MemCopy).

    Ассемблерные вставки могут иметь два параметра, которые заключаются в квадратные скобки:
    1. Имя - обязательный параметр. Указывается словом name (прим. name:MyAsmProc). При отсутствии слова name именем блока будет считаться первое слово, заключенное в квадратные скобки.
    2. Базовый адрес - формальный параметр. Он указывается для создания статической вставки, определяется словом base (прим. base:0x004B44A2). При отсутствии параметра базовый адрес будет вычислен автоматически (то есть такая вставка будет динамической).

    Ассемблерную вставку можно вызвать как напрямую из патча, так и из движка:
    1. Вызвать сгенерированный байткод напрямую можно путем API вызова через созданную HEX@ переменную. См. пункт API вызовы.
    2.1. При динамической вставке обращение к байткоду должно происходить из движка через команды перехода jmp или call (в зависимости от задачи). Для этого можно использовать одноименные патч-функции (см. список функций в первом посте), которые автоматически посчитают смещение к выбранной вставке. Такой метод потребует заменить 5 байт в движке для реализации перехода.
    2.2. При статической вставке байткод должен быть помещен в ту область памяти, для которой он предназначен. Сделать это можно функцией MemCopy, передав в качестве аргумента память через созданную HEX@ переменную.

    В ассемблерную вставку можно передавать переменные из патчей. Для этого перед переменной необходимо указать оператор $. При типе операнда rm или mem будет взят адрес переменной. Иначе будет взято ее значение. Исключением является тип HEX, у которого может быть взят только адрес. Если переменная участвует в патче в качестве хранилища данных (rm || mem), то она не будет выгружаться из памяти, чтобы байткод мог использовать ее на протяжении всего времени жизни процесса.


    Пример передачи значения из переменной variableA в переменную variableB
    Union:
        #patch
          INT variableA = 10
          INT variableB
          #assembler [AsmCopyVariable]
            mov eax, $variableA
            mov [$variableB], eax
            ret
          #/assembler
          AsmCopyVariable() // API вызов
          MessageBox(variableB)
        #/patch


    Пример многократного вывода MessageBoxA из winAPI. Повторение вывода до тех пор, пока пользователь нажимает кнопку ДА:
    Union:
        // Средствами патчей и ассемблерных вставок реализуем блок,
        // который будет выводить сообщения на экран до тех пор, пока
        // пользователь соглашается на повторный вывод.
        #patch
            // Импортируем адрес фунции
            INT proc = ImportSymbol("User32.dll", "MessageBoxA")
     
            // Определяем параметры окна, в частности
            // кнопки YES/NO для ответа
            INT MB_YESNO        = 0x00000004
            INT MB_ICONQUESTION = 0x00000020
            INT IDYES           = 6
            INT flags           = MB_YESNO |  MB_ICONQUESTION
            HEX captionText     = ""
            HEX messageText     = "Show message again?\0"
     
            // Определяем ассембреную вставку, в которой реализован
            // циклический вызов MessageBoxA, пока
            // пользователь нажимает в ответ IDYES
            #assembler [name:ShowMessage]
            tryAgain:
                push    $flags          ; flags
                push    $captionText    ; caption
                push    $messageText    ; text
                push    0               ; hwnd
                call    $proc
                cmp     eax, $IDYES
                jz      tryAgain
                ret
            #/assembler
    
            ShowMessage()
        #/patch


    Пример патча, выравнивающего NPC относительно лестницы (реальный патч из union.patch):
    Union:
        #patch [Aligning npc by ladder]
            // Переменная, в которой будет храниться вектор
            HEX vector = '00 00 00 00 00 00 00 00 00 00 00 00'
     
            #assembler [AlignNpc]
                // Проверка соответствия this на класс oCMobLadder
                cmp     dword ptr [ebp], 83CF2Ch ; oCMobLadder vftable
                jnz     return
     
                // Взятие at вектора лестницы
                mov     ecx, ebp                 ; ladder
                lea     eax, [$vector]
                push    eax                      ; vector
                call    52DCB0h                  ; GetAtVectorWorld
     
                // Проверка что npc валиден
                mov     ecx, edi                 ; npc
                test    ecx, ecx
                jz      return
     
                // Присвоение at вектора для целевого npc
                push    eax
                call    61CBC0h                  ; SetHeadingAtWorld
     
                // Восстановление затертых 5 байт инструкций
                // из-за создания перехода к данной вставке
            return:
                mov     ecx, [esp+3Ch-0Ch]
                pop     edi
     
                // Возврат к адресу, ближайшему к тому, в котором
                // был сгенерирован переход к данной вставке
                jmp     727E4Ah
            #/assembler
            // Создание перехода из функции
            // oCMobLadder::Interact к вставке AlignNpc
            JMP(0x00727E45, AlignNpc)
        #/patch

  • API calls
    Calls from patches can be made using the calling convention of _cdecl and _stdcall at a known or imported address. To call an arbitrary function, it is enough to cast its address HEX @. The number and types of parameters are ignored and controlled exclusively by the programmer. An example of calling the MessageBoxA function using the call API:
    Union:
        // API implementation example
        #patch
            INT MB_YESNO        = 0x00000004
            INT MB_ICONQUESTION = 0x00000020
            INT IDYES           = 6
     
            // Importing a function and then calling it. If the function returns
            // value, then it will be returned as an INT value, which
            // can be cast to another type
            HEX @imp_MessageBoxA = ImportSymbol("User32.dll", "MessageBoxA")
            INT result = imp_MessageBoxA(0, "Hello, world!\0", "\0", MB_YESNO | MB_ICONQUESTION)
     
            // Test, if the answer was YES, then it will be
            // an additional message is displayed on the screen.
            IF result == IDYES
                MessageBox("Selected choice YES")
            ELSE
        #/patch

    Inline assembler
    The assembler block generates a piece of bytecode in memory with read, write, and execute access. To be able to access memory, the program creates a variable of the HEX@ type by the name of the block, which refers to this memory. The length of the bytecode can be determined using the GetHexSize function. An insertion can be placed both in the global area of the document (inside the #engine block), or inside a specific patch. In this case, if the insertion is enclosed within the IF statement, the bytecode generation will occur according to the expression of the conditional block.

    There are two types of Assembler insertions:
    1. Dynamic insertion - an assembler block can be built and called directly. This type is used by default.
    2. Static insertion - the assembler block is assembled for a specific memory area. The only difference from the previous one is that all offsets are calculated relative to an explicitly specified base address. The generated bytecode must be copied to the required memory area (see the MemCopy function).

    Assembler insertions can have two parameters, which are enclosed in square brackets:
    1. Name is required. It is indicated by the word name (approx. Name:MyAsmProc). If the word name is absent, the block name will be the first word enclosed in square brackets.
    2. The base address is a formal parameter. It is indicated to create a static insert, defined by the word base (approx. Base:0x004B44A2). If the parameter is absent, the base address will be calculated automatically (that is, such an insert will be dynamic ).

    Assembler insertion can be called both directly from the patch and from the engine:
    1. You can call the generated bytecode directly by calling the API through the created HEX@ variable. See point API calls .
    2.1. With dynamic insertion, the bytecode must be accessed from the engine through the jmp or call jump commands (depending on the task). To do this, you can use the same-named patch functions (see the list of functions in the first post), which will automatically calculate the offset to the selected insert. This method will require replacing 5 bytes in the engine to implement the transition.
    2.2. With static insertion, the bytecode must be placed in the memory area for which it is intended. This can be done using the MemCopy function, passing memory as an argument through the created HEX@ variable.

    Variables from patches can be passed to the assembler insertion. To do this, you must specify the $ operator in front of the variable. If the type of the operand is rm or mem, the address of the variable will be taken. Otherwise, its value will be taken. An exception is the HEX type, from which only the address can be taken. If a variable participates in the patch as a data store (rm || mem), then it will not be unloaded from memory so that the bytecode can use it throughout the lifetime of the process.


    An example of passing a value from variableA to variableB
    Union:
        #patch
          INT variableA = 10
          INT variableB
          #assembler [AsmCopyVariable]
            mov eax, $variableA
            mov [$variableB], eax
            ret
          #/assembler
          AsmCopyVariable() // API calling
          MessageBox(variableB)
        #/patch


    An example of multiple output of MessageBoxA from winAPI. Loop showing MessageBox until the user presses the YES button:
    Union:
        // Using patches and assembler inserts, we implement a block,
        // which will print messages to the screen until
        // the user agrees to the repeat it.
        #patch
            // Importing the address of the function
            INT proc = ImportSymbol("User32.dll", "MessageBoxA")
     
            // Define window parameters, in particular
            // YES/NO buttons to answer
            INT MB_YESNO        = 0x00000004
            INT MB_ICONQUESTION = 0x00000020
            INT IDYES           = 6
            INT flags           = MB_YESNO |  MB_ICONQUESTION
            HEX captionText     = ""
            HEX messageText     = "Show message again?\0"
     
             // Define the assembly in which it is implemented
             // the loop call of MessageBoxA until
             // user clicks IDYES
            #assembler [name:ShowMessage]
            tryAgain:
                push    $flags          ; flags
                push    $captionText    ; caption
                push    $messageText    ; text
                push    0               ; hwnd
                call    $proc
                cmp     eax, $IDYES
                jz      tryAgain
                ret
            #/assembler
    
            ShowMessage()
        #/patch


    An example of a patch aligning an NPC with a ladder (real patch from union.patch):
    Union:
        #patch [Aligning npc by ladder]
            // The variable in which the vector will be stored
            HEX vector = '00 00 00 00 00 00 00 00 00 00 00 00'
     
            #assembler [AlignNpc]
                // Checking whether 'this' matches the oCMobLadder class
                cmp     dword ptr [ebp], 83CF2Ch ; oCMobLadder vftable
                jnz     return
     
                // Taking 'at' (unit) vector of the ladder
                mov     ecx, ebp                 ; ladder
                lea     eax, [$vector]
                push    eax                      ; vector
                call    52DCB0h                  ; GetAtVectorWorld
     
                // Checking that 'npc' is valid
                mov     ecx, edi                 ; npc
                test    ecx, ecx
                jz      return
     
                // Assigning 'at' (unit) vector to the target npc
                push    eax
                call    61CBC0h                  ; SetHeadingAtWorld
     
                // Recovering overwritten 5 bytes of instructions
                // due to the creation of a moving to the given insertion
            return:
                mov     ecx, [esp+3Ch-0Ch]
                pop     edi
     
                // Return to the address closest to the one where
                 // a jump to this insertion was generated
                jmp     727E4Ah
            #/assembler
            //Creating a jumping from a function
            // oCMobLadder::Interact to insertion AlignNpc
            JMP(0x00727E45, AlignNpc)
        #/patch

 
Последнее редактирование:

Gratt


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

Русский English


  • Продвинутые ассемблерные вставки
    Это немного модифицированные ассемблерные вставки, которые значительно сокращают количество операций программисту.
    Такие вставки не нуждаются в ручном вызове, так как они сразу создают переход к своей области памяти.
    Также они автоматически создают обратный переход в исходную функцию.
    Ну и в заключение - вставки запоминают замененные собою инструкции и позволяют при необходимости вызвать их командой orgcode. При этом оригинальные инструкции будут дополнительно зачищены инструкцией nop, чтобы дебаггер мог корректно дизассемблировать измененный участок памяти.


    1. Продвинутая инструкция считается таковой, если имя такой вставки является адресом и не содержим более никаких параметров. Переход к этой вставке будет создан автоматически. Пример реализации:
    Union:
    #assembler [0x0068CF02]


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


    3. Команда orgcode подставляет во вставку инструкции, которые были заменены при создании перехода к вставке. При этом если замененной инструкцией является другой переход (в тч вызов), то все смещения будут автоматически пересчитаны. Обращаться к orgcode можно в любом месте байткода (на сколько этого требует логика):
    Union:
        #patch [Close alpha-lines on multipage documents]
            #assembler [0x0068CF06]
                mov     ebx, [edi+48h]
                imul    ebx, -2
                add     eax, ebx
                orgcode
            #/assembler
        #/patch

    При обращении к orgcode можно пропустить часть оригинальных инструкций, если в них нет нужны. Для этого используется оператор '+'.
    В качестве примера имеем следующий код, в котором нам необходимо заменить только инструкцию imul:
    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

    При создании вставки в данный код будет помещен переход длиной 5 байт. Он полностью затрет инструкцию imul, но также и add, поскольку необходимо заменить не менее 5 байт. Таким образом в orgcode будет две инструкции, сумма размеров которых дает 6 байт. Код с изменениями станет выглядеть так:
    Union:
    0068CF02    jmp     [asm]                 5 bytes
    0068CF07    nop                           1 byte
    0068CF08    mov     [esp+24h+var_8], ecx  4 bytes
    0068CF0C    lea     esp, [esp+0]          4 bytes

    Поскольку по условию мы заменяем только imul, то при обращении к orgcode необходимо эту инструкцию пропустить.
    Для этого используем оператор '+', указывая на сколько инструкций следует сместить подстановку:
    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

  • Advanced assembler insertions
    These are slightly modified assembler insertions, which significantly reduce the number of operations for the programmer.
    Such insertions do not need to be called manually, since they immediately create a transition to their memory area.
    They also automatically create a jump back to the original function.
    Well, in conclusion - insertions remember the instructions replaced by themselves and allow, if necessary, to call them with the orgcode command. In this case, the original instructions will be additionally cleaned up with the nop instruction so that the debugger can correctly disassemble the changed memory section.


    1. An advanced instruction is considered if the name of such an insertion is an address and does not contain any parameters. A transition to this insertion will be created automatically. Implementation example:
    Union:
    #assembler [0x0068CF02]


    2. The insertion creates a return transition automatically, but if you need to specify the return address manually, the address can be specified in the parameters of the closing tag:
    Union:
    #/assembler [0x0068CF08]


    3. The orgcode command substitutes the insertion instructions that were replaced when the insertion transition was created. In this case, if the replaced instruction is another transition (including a call), then all offsets will be automatically recalculated. You can refer to orgcode anywhere in the bytecode (as far as the logic requires it):
    Union:
        #patch [Close alpha-lines on multipage documents]
            #assembler [0x0068CF06]
                mov     ebx, [edi+48h]
                imul    ebx, -2
                add     eax, ebx
                orgcode
            #/assembler
        #/patch

    When referring to orgcode, you can skip some of the original instructions if they are not needed. The '+' operator is used for this.
    As an example, we have the following code, in which we only need to replace the imul instruction:

    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

    When creating an insert, a 5-byte jump will be placed in this code. It will completely overwrite the imul instruction, but also add, since at least 5 bytes must be replaced. Thus, in orgcode there will be two instructions, the sum of the sizes of which gives 6 bytes. The modified code will look like this:

    Union:
    0068CF02    jmp     [asm]                 5 bytes
    0068CF07    nop                           1 byte
    0068CF08    mov     [esp+24h+var_8], ecx  4 bytes
    0068CF0C    lea     esp, [esp+0]          4 bytes

    Since by condition we replace only imul, then when referring to orgcode, you need to skip this instruction.
    To do this, we use the '+' operator, indicating how many instructions to shift the substitution:
    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
 
Последнее редактирование модератором:
Статус
В этой теме нельзя размещать новые ответы.
Сверху Снизу