CELua[RU]
    • Категории
    • Последние
    • Метки
    • Популярные
    • Пользователи
    • Группы
    • Зарегистрироваться
    • Войти
    1. Главная
    2. MasterGH
    3. Сообщения
    Не в сети
    • Профиль
    • Подписки 1
    • Подписчики 1
    • Темы 129
    • Сообщения 252
    • Группы 4

    Сообщения

    Последние Лучшие сообщения Спорные
    • CE Plugin: AA Maker 2.4.2

      AAmaker предназначен для создания создания autoassembler скриптов для Cheat Engine 6.5 или выше

      cd490844-e873-49d0-bfe4-3b914fe35217-image.png

      Предыстория. Изначально нужно было генерировать динамически АА-шаблоны по правилам, не зная регистров и прочего, на инструкции по сигнатуре. Это не было реализовано.

      Вместо этого пошла другая ветвь - генерация статичного АА-шаблона. Есть некоторые особенности. Создает сигнатуру с пропусками между первыми байтами опкодов. Это сделано для поддержки множества версий игр (для первой ветви развития плагина), но это пропускает последующие за первым байтом опкоды и значит регистры в опкоде могут быть разными, а это значит, что сама идея таких сигнатур не очень универсальная и подойдет разве ,что для опкодов без регистров или же для ноппинга. Либо фиксить сигнатуру на нормальную. Либо пользоваться генерируемыми шаблонами в CE по умолчанию.

      -----------
      Это плагин CE Lua plugin для Cheat Engine 6.5 или выше

      AAmaker предназначен для содания создания autoassembler скриптов.

      1. Установите aamaker.lua в "Autorun" директорию
      2. Запустите Cheat Engine.
      3. Присоедините процесс
      4. Перейдите в "Memory View" окно
      5. Выделите адрес кода
      6. Правой кнопки мышки вызовите контекстное меню
      7. Используйте функции "AA Maker" в контекстном меню
        5b40b07a-28d6-4489-9e76-6d916254187f-image.png
        ----------
        + исправления
        + новый директивы:
        {$AddressInjection}
        {$OriginalCode}
        {$CheatCode}
        {$Nops}
        {$ArrayOfbyte}
        {$ProcessName}
        {$Date}
        {$PrintLog}
        + исправления шаблонов
        + удален aobscan. Изменен aobscanmodule
      8. Исправление ошибок (автор ++METHOS)
      9. Добавление шаблонов (автор ++METHOS)
        Исправление. Совместимость с CE 6.5
      написал в Плагин-строй
      MasterGHM
      MasterGH
    • CE Lua Крестики и нолики

      Крестики и нолики.CT

      c7c664ad-b109-47d7-a1f0-3c8882243941-image.png
      Игра с рандомом. Есть счет.
      Запускается через таблицу в аттаче.
      Здесь мог быть AI на Lua, возвращающий номер клетки от 1 до 9.

      function InputAI()
       -- рандом
       return math.random (1, 9)
      end
      
      player1Symbol = '1'
      player2Symbol = '0'
      whoStep = 0
      player1Score = 0
      player2Score = 0
      
      function InputAI()
       -- рандом
       return math.random (1, 9)
      end
      
      \-- Проверка правил
      function caheckRules(symbol)
      
        -- Комбинации выигрыша
        local tableRules =
        {
          {1,2,3}, {4,5,6}, {7,8,9},  -- горизонтальные клетки
          {1,4,7}, {2,5,8}, {3,6,9},  -- вертикальные клетки
          {1,5,9}, {3,5,7}            -- клетки по диагонали
        }
      
        -- Результат проверки клеток
        local mask = [[
          return UDF1.CEButton%s.Caption == 'symbol' and
                 UDF1.CEButton%s.Caption == 'symbol' and
                 UDF1.CEButton%s.Caption == 'symbol'
        ]]
      
        for i = 1, #tableRules do
      
          local luaStringCode = mask:format(tableRules[i][1],
            tableRules[i][2], tableRules[i][3]):gsub('symbol', symbol)
      
          if loadstring (luaStringCode)() then
            return true
          end
      
        end
      
        return false
      end
      
      function StartGame()
        whoStep = 0
        for i = 1, 9 do
          loadstring ('UDF1.CEButton'..i..'.Caption = ""')()
        end
      end
      
      \-- 1 - ход сделан, 0 - ход не удался
      function Input(indexInput)
      
        if loadstring ('return UDF1.CEButton'..indexInput..'.Caption')() ~= '' then
          return 0
        end
      
        local writeSymbol = player1Symbol
        if whoStep == 0 then
          whoStep = 1
        else
          whoStep = 0
          writeSymbol = player2Symbol
        end
      
        local s = 'UDF1.CEButton'..indexInput..'.Caption = '..writeSymbol
        loadstring (s)()
      
        local somebodyWinner = false
      
        if caheckRules(player1Symbol) then
          player1Score = player1Score + 1
          UDF1.CELabelScore.Caption = player1Score..':'..player2Score
          ShowMessage('Player1 is winner!')
          somebodyWinner = true
      
        elseif caheckRules(player2Symbol) then
          player2Score = player2Score + 1
          UDF1.CELabelScore.Caption = player1Score..':'..player2Score
          ShowMessage('Player2 is winner!')
          somebodyWinner = true
        end
      
        -- Если кто-то выиграл, то очистить поле
        if somebodyWinner then
          StartGame()
          return 1
        end
      
        --  Проверка ничьи
        local countEmpty = 9
        for i = 1, 9 do
          if loadstring ('return UDF1.CEButton'..i..'.Caption ~= ""')() then
            countEmpty = countEmpty - 1
          end
        end
      
        if countEmpty <= 0 then
          UDF1.CELabelScore.Caption = player1Score..':'..player2Score
          ShowMessage('Friendship!')
          StartGame()
          return 1
        end
      
        -- Ходит IA
        if whoStep == 1 then
          ::repeat1::
          local index = InputAI()
          if Input(index) == 0 then
            goto repeat1
          end
        end
      end
      
      
      function CEButtonClick(sender)
        Input(tonumber(sender.name:match('%d')))
      end
      
      function MenuItem1Click(sender)
        player1Score = 0
        player2Score = 0
        UDF1.CELabelScore.Caption = player1Score..':'..player2Score
        StartGame()
        ShowMessage('Restart!')
      end
      
      UDF1.show()
      
      написал в MasterGH
      MasterGHM
      MasterGH
    • CE Stack Viewer

      a50f1946-1900-4fa6-8a0c-31f2f1062c12-image.png

      Информация о файле
      Назначение

      Плагин позволяет в реальном режиме игры наблюдать за стеком на определенной инструкции отладочного кода. Это в свою очередь позволяет в окне расструктуризации данных "Dessect Data Structure" сравнивать старые и текущие структуры стека в реальном режиме игры, наблюдать за появлением красных данных и обращать на них особое внимание. Красные данные показывают чем отличаются стеки при действиях в игре. Например, выстрелив один раз и выстрелив следующий раз можно увидеть отличия красным цветом в стеке в реальном режиме. Это могут быть не только изменения патронов, но и чего-то другого, когда уже стреляем другим оружием. Важно то, что можно видеть изменения в реальном времени, а не по копиям стека, которые приходилось раньше снимать много раз. Мы можем видеть как часто по времени что-то меняется, все в динамике...

      Есть у меня сомнения по поводу постоянного ESP сравнения, при котором происходит "наблюдение" за стеком, но пока оставлю как есть.

      Установка

      Распаковать файлы архива в директорию "autorun". Например,

      C:\Program Files (x86)\Cheat Engine 6.4\autorun
      Краткое руководство

      1. Запускаем игру

      2. Находим адрес параметра

      3. Ставим брейкпоинт на адрес и появляются инструкции (или что-то делаем в игре чтобы инструкции появились)

      4. Выделяем инструкцию, которую требуется исследовать

      5. Останавливаем отладку

      6. Открываем Stack Viewer из окна Дизассемблер-> Tools->* Stack Viewer [Plugin] и переносим в ESP, EIP (или RSP, RIP) в поля окна Stack Viewer. Нажимаем галку "Is Active" и идем в игру, и если надо что-то делаем в игре, потому что инструкция должна выполниться хотя бы один раз чтобы заполнить дамп стека в реальном времени

      7. Открываем окно расструктуризации и пишем метку "MemStackRunTime" (или другую) и делаем расструктуризацию.

      8. Открывает плагин Tiny Dumper и клонируем "MemStackRunTime" в другую метку "StackDump1" (или другую). Теперь у нас в окне две структуры: с разовой копией и постоянно обновляющейся копией стека.

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

      10. Для остановки наблюдения за стеком выключаем галку "Is Active"

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • RE: CE Tiny Dumper

      Пошаговое руководство по Tiny Dumper

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

      1. Перед запуском CE устанавливаем файлы плагина в папку autorun (frmTinyDumper.xml и TinyDumper.lua)

      2. Запускаем игру например "Painkiller - Battle out of Hell 1.64" поставленная в оконный режим через 3DAnalyze.

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

      1. Запустить CE с установленным плагином и подключиться к игре

      2. Ищем адрес здоровья (или брони, или патронов), тип Double (8 байт)
        0x24571EA8 = 99.2900238037109

      3. Ставим бряк на доступ или на запись на адрес 0x24571EA8

      2bf8d038-de92-4de0-88f6-944b9093adf5-image.png

      1. Идем в игру и встаем под удар персонажем

      Появляются инструкции
      3b2e99be-4a03-455c-a575-28511d347064-image.png
      и там же нажимаем на кнопку Stop, чтобы бряки не мешались в окне бряков

      1. Берем например инструкцию

      1015C194 - 89 53 08 - mov [ebx+08],edx
      6ad80302-80f1-4905-8bac-bdbfbb930bfd-image.png
      По ней идем в дизассемблер и на инструкции жмем F5 - установка бряка на доступ

      2755fb71-f09c-4815-88c7-191fbfdf2dc7-image.png

      1. Открываем окно бряков, выделяем инструкцию и пишем условие на прерывание с остановкой процесса

      EBX==0x24571EA0 (это условие взяли, потому что ebx находится в инструкции, а значение взяли EBX из отладчика)

      2c1e1ed4-0df0-4314-981f-f8e31550959d-image.png
      43e9dfd5-1c49-4bf2-bf1a-b588c308181c-image.png

      Нажимаем Ок, идем в игру
      9. Видим, что произошла остановка игры
      148b600b-4ffe-4e2c-893a-df3330aff53f-image.png
      10. Открываем окно ТиниДампера
      6db64901-ca0e-4d32-8be9-2fcb27894556-image.png

      1. Пишем значение регистра esp и название будущей зарегенной метки
        26586a39-c6f1-40ee-a44b-bd966578e736-image.png

      2. Нажимаем "Dump and Close" и CTRL+U чтобы видеть под рукой зареганные ваши метки

      7f7a0d40-9107-4c19-b13f-2105a57422a5-image.png

      1. Открываем окно расструктуризации CTRL+D и пишем метку в свободное поле
        1104aed5-e0c1-45bd-b880-877433c6c1a1-image.png

      2. Далее следуем действия как на рисунках
        9cac0888-208c-4f0e-9ae2-b53f6c49aeb4-image.png
        7db1307b-9305-4fd6-9b34-e75c2c8cc466-image.png

      3. Наконец результат
        6d506668-7bed-4cae-9c22-47af124c0c16-image.png

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

      1. Сейчас процесс игры остановлен на бряке кода. Чтобы продолжить игру. Удаляем бряк из окна бряков и отпускаем игру по F9.

      2. Теперь снимаем другие дампы на том же адресе для адресов здоровья врагов на предмет отличиях их или для поиска указателей... Во всяком случае сохраненный дамп может дать какие-то подсказки и зацепки. Главное не начать новый уровень игры или не загружать сохранения, т.к. обычно при переходе на новый уровень данные могут измениться... На этом пока все

      -----
      Tiny dumper 1.2 Сравнение структур после перезагрузки. Поиск фильтра свой-чужой-дружественый

      1. Запускаем игру, запускаем CE и присоединяем процесс игры к CE

      2. Сохраняем игровой слот перед тем как искать структуры

      3. Находим три структуры игроков: свой игрок, дружественный, враг

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

      5. Игру закрываем, снова открываем, загружаем слот, присоединяем процесс игры к CE

      6. В Tiny dumper 1.2 вызываем контекстное меню правой кнопки мышки и вызываем опцию "Rewrite dumps" - дампы из кеша перезаписываются в память в процесса игры

      7. Снова находим три структуры игроков: свой игрой, дружественный, враг

      8. В окне расструктуризации создаем три группы

      Группа1 "Свой игрок":

      -адрес после перезагрузки

      -метка из тини дампера своего игрока до перезагрузки

      Группа2 "Чужой игрок":

      -адрес после перезагрузки

      -метка из тини дампера чужого игрока до перезагрузки

      Группа3 "Дружественный игрок":

      -адрес после перезагрузки

      -метка из тини дампера дружественного игрока до перезагрузки

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

      2. Пишем АА-скрипт со смещением фильтра. Правильно написанный АА-скрипт не будет крешить игру.

      3. Активируем АА-скрипт и проверяем в игре, что свой и дружественный игроки не получают урон, а вражеские получают урон. Если это не так, то берем другие смещения из пункта 9 и повторяем 10.

      Если фильтр найти не удалось, то ищем 3 многоуровневых указателя до верхего статического адреса для своего, для 2-х врагов. Открываем Structure Spider.

      Указываем в поиске уровень вложенности указателей.

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

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

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • CE Tiny Dumper

      7a0117d9-fc33-467d-9ef8-3ad6b1049fe0-image.png

      Функции:

      1. Копирование участка виртуальной памяти фиксированной длины

      2. Связывание адреса копированных данных с новой меткой

      3. Возможность сравнивать дампы во время игры с любого доступного адреса (в том числе стека)

      4. Возможность сравнивать сохраненные дампы структур после перезагрузки игры

      Основное назначение - снимать дампы со стека в пошаговой отладке по адресу ESP и сравнивать эти дампы в окне расструктуризации данных (Dessect Data/Structure). Таким образом можно найти сходства или различия в передаваемых аргументах функций, а также в адресах обратного вызова по инструкции ret. Сравнение аргументов может помочь определить указатели, смещения на данные. Сравнение адресов возврата может помочь определить развилки между ветками кода ботов и ветками персонажа за которого играете. Можно начать исследовать эту развилку для определения условий свой/чужой.

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

      Данные для ввода:

      Source Address - адрес с которого будет сниматься дамп

      Size dump - размер участка данных в байтах

      Register label - метка для обращения к адресу

      *Подсказки:

      1. Плагин можно найти в первом окне дизассемблера в меню Tools (Утилиты). Окна-клоны дизассемблера не имеют подключенного подменю.

      2. Зарегистрированные метки, которые вы забыли можно посмотреть и удалить через окно зарегистрированных меток "Symbol Config" (нажать на CTRL+U в окне дизассемблера)

      3. Память дампов сама не очищается. Поэтому не рекомендуется снимать очень часто огромные дампы и тем самым засорять память. Если все же память переполнена, что мало вероятно, то просто перезапустить игру.

      Что нового в версии 1.1
      Размещено 6 марта, 2015

      1. Добавлен список зарегистрированных меток
      2. Добавлено контекстное меню с опциями копирования меток в буфер обмена и удаление меток с освобождением памяти
      3. Кнопка снятия дампа больше не закрывает окно

      Что нового в версии 1.2
      Размещено 6 марта, 2015

      Добавления:

      1. При выделении записи в полях выводятся данные адреса, размер дампа и название зарегистрированной метки
      2. Добавлена опция сохранения дампов
      3. Добавлена опция загрузки дампов
      4. Добавлена опция очистить таблицу
      5. Добавлена опция перезаписи дампов
      6. После закрытия игры дампы можно сохранять
        Исправления:
      7. Исправлено подключение к русской версии
      8. Исправлены название опций
        Описание контекстного меню:
      9. Copy Name - копирует имя выделенной записи
      10. Remove selected - удаляет выделенную запись
      11. Save to file - сохранить все дампы в файл, в указанную директорию
      12. Load from file - загрузить дампы из файла
      13. Ramove all records - удаляет все дампы и зарегистрированные метки
      14. Rewrite dumps - перезаписывает дампы в новую память, регистрирует метки адресов дампов повторно
      написал в Плагин-строй
      MasterGHM
      MasterGH
    • Tool C# Regular Expression

      a159e201-c947-4e08-a6e7-f22fd12524a1-image.png

      Regular Expression.rar

      string allText = richTextBox1.Text;
      string pattern = textBox2.Text;
      string s = string.Empty;
      
      try 
      {
        Regex regex = new Regex(pattern);
        MatchCollection matches = regex.Matches(allText);			
        if (matches.Count > 0)
        {
            foreach (Match match in matches)
                s += match.Value + ";\r\n";
        }
        else
        {
            s = "Совпадений не найдено";
        }				
        textBox3.Text = s;
      } catch (Exception) {
        
        //throw;
      }
      
      написал в MasterGH
      MasterGHM
      MasterGH
    • CE mapbranches
      MapBranches

      Описание

      (Другие записи: https://celua.ru/topic/113/ветвления-кода-связанные-с-адресом-старые-записи/2)

      1. На адрес устанавливается брейкпоинт.
      2. Идем в игру делаем или не делаем что-то
      3. Начинают срабатывать инструкции на брейкпоинте
      4. От каждой инструкции начинается трейслог, подобный тому, который есть у CE, но менее тормозной
      5. Заканчивается трейслог на инструкции, на 100-ом счетчике после ret-а (об этом пункте будет уточнение). Здесь выходим на коневой цикл
      6. Трейслог останавливается и ожидание следующей инструкции, которая сработает на брейкпоинте, она должна быть отличной

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

      По этим данным можно нарисовать схему, где ret-ы меняют направление пути прохождения потоком дизассемблерного кода

      На скриншотах ниже
      Вертикальные линии со кружками -— это ветвь трейслога
      Белый кружок — адрес ret-a, который не повторялся
      Пунктирный кружок — адрес ret-а, который повторялся. Стрелка от такого пунктирного круга будет указывать на белый круг.

      По схеме видим, где ret-ы меняют свое направление поднимаясь по рутине в корневом цикле

      1cbcd3d0-9724-4f79-8586-c53a70388824-image.png

      b9062c23-18da-4e84-8055-4cb2e36a5a94-image.png

      9309d64f-ac96-4919-b7a1-1d67e58590b3-image.png

      По плану по визуальной части:

      1. Клик на кружок — переход на код

      2. Придумать что-то, чтобы стрелки не накладывались друг на друга

      3. Установка брейкпоинтов на кружках — становиться красными

      4. Легенда. Номер ветви, связанная инструкция, состояние брейкпоинтов. Комментарий к ветви

      5. Загрузка/сохранение легенды

      Для чего нужно. Для поиска условий, которые включают и выключают ветви кода. Т.е. чтобы можно было это условие быстро найти и изменив его, повторить выполнение кода ветви, которая "пробьётся" из корневого цикла. Таким образом попытаться повторить действие в игре, а если не получаться повторить, то определить дополнительные условия.

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • RE: Ветвления кода связанные с адресом (старые записи)

      Трассеровка по ретам

      -------
      857b57ff-3395-4d71-9f76-e191f66fc90b-image.png

      На скриншоте 22 ветви трассеровки от адреса патронов не входя в call-ы до корневого цикла. Корневой цикл определяется, когда поиск происходит более трех секунд и не находит ничего. Вполне хватает этого времени. Буду еще повторно тестить и проверять доходит ли он цикла или надо еще что-то придумывать.

      Желтым выделяется точка, вертикальная полоса ветви и связи с такими же узлами. Узел это адрес выхода из ret

      Внизу ряд счетчиков от 00 до 99. Если более 99 то отсчет заного. Показывает дианамику обращения к ветвям после нахождения корневого цикла

      Есть еще несколько функций, которых не видно на скриншоте — в контекстном меню.

      Анализ можно запустить из окна CE из главной таблицы, выделив адрес и опцию либо на чтение, либо на доступ.

      При тестах увидел, что логи неполные. Например, первый кружок это инструкция читающая патроны очень часто. От этой инструкции идут вверх множество вариантов ветвей,а у меня один вариант — вертикальная полоска с кружками. От первого круга надо сделать ветвление влево и вправо.

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

      function IsRet(address)
      		local value = readBytes(address,1, false)
      		return value == 0xC3 or value == 0xCB or  value == 0xC2 or value == 0xCA
      	end
      

      Скорость трассеровки должна в несколько раз увеличиться. К сожалению, смогу это узнать, когда перепишу текущий плагин.

      Там были уже сделаны изменения (все еще сырое для публикации плагина, пока не публикую)

      • Нумерация ветвей
      • Клик на круг — переход в дефоттный дизассемблер
      • Подсвечивать выбранный круг
      • Перерасчет позиций, когда поменялся размер окна
      • Счетчики выполнений инструкций срабатывающих к обращению на адрес
      • Изменен способ рисования связей между ветвями
      • Контекстное меню Показать ветку
      • Контекстное меню Скрыть ветку
      • На скрытой ветви не показываются связи упрощая обзор. Код поднимается снизу вверх. Если одна ветвь ниже другой, то можно её скрыть. Ориентируемся по счетчикам хитов на инструкции
      • Контекстное меню Сброс хитов
      • Оптимизация поиска корневого цикла
      • Выделение связей
      • Оптимизация рисования новых свзей
      • Контекстное меню пауза/продолжение
      • Запуск из контекстного меню при выделеии адреса в главной таблице
      • При закрытии окна снимаются все брейкпоинты
      • Возможность ставить логи на запись из контекстного меню и на чтение в главной таблице CE
      написал в MasterGH
      MasterGHM
      MasterGH
    • Ветвления кода связанные с адресом (старые записи)

      В этой записи блога не будет чего-то, что показало бы "вау, это что-то новое и есть результат". Все сырое и результат пока мне только снится — быстрый поиск условий и включение, и выключение ветвей по этим условиям. Мыслю я не инструкциями, не группой инструкций, а ветвлениями кода и условиями, которые их запускают. Проще 20 ветвлений по 5 окон, чем тонна инструкций... Жаль пока теория, практики с результатом нет.

      Рисунок. На нем слева прототип, справа текущий сырой вариант

      1. ставим брейкпоинт на адрес патронов

      2. от него расплетаются ветви кода от каждого хита, от каждой инструкции, и по ретам ветвление выходит из рутины

      Кружи означают ret-ы. Клик на круг будет переходлм в дизассемблер

      Стрелки будут показывать входы и выход связанные с соседними рутинами (поиск общих адресов будет стрелками)

      e3ab56bb-c1a6-4626-bead-f41fbb327a0d-image.png
      На экране справа вариант без стрелок, т.к. не успел еще их отладить. 4 ветки трассера по ретам из рутины связанные с адресом патронов, выстрелами.

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

      07232118 - 8B 85 90060000  		- mov eax,[ebp+00000690]
      0721C03F - 0FB7 8E 90060000  	- movzx ecx,word ptr [esi+00000690]
      0722FE69 - 39 86 58030000 		- cmp [esi+00000358],eax
      0722FEA8 - 83 BE 58030000 00 	- cmp dword ptr [esi+00000358],00
      0721C4A0 - 0FB6 96 B0030000  	- movzx edx,byte ptr [esi+000003B0]
      0723155C - 83 BE B0030000 00 	- cmp dword ptr [esi+000003B0],00
      07230357 - 83 BE 90060000 00 	- cmp dword ptr [esi+00000690],00
      0721E00C - 8B BE 90060000  		- mov edi,[esi+00000690]
      0722330C - 01 BE 90060000  		- add [esi+00000690],edi
      0722FFC7 - 83 BE 58030000 00 	- cmp dword ptr [esi+00000358],00
      072306DF - 83 BE 90060000 00 	- cmp dword ptr [esi+00000690],00
      0722E808 - 8B 86 90060000  		- mov eax,[esi+00000690]
      07231150 - 8B 8E 90060000  		- mov ecx,[esi+00000690]
      07231186 - 83 86 90060000 01	- add dword ptr [esi+00000690],01
      072311D4 - 8B 86 90060000 		- mov eax,[esi+00000690]
      0723121A - 3B 96 90060000		- cmp edx,[esi+00000690]
      07231FE9 - 83 BE 90060000 00	- cmp dword ptr [esi+00000690],00
      0721CA71 - 0FB6 86 B0030000		- movzx eax,byte ptr [esi+000003B0]
      0721E9B7 - 83 BE 90060000 00	- cmp dword ptr [esi+00000690],00
      0721EA26 - DA BE 90060000		- fidivr [esi+00000690]
      

      a80c8713-7d3b-48ee-9fd4-bb2ed6058b0c-image.png

      Как видно, довольно все еще сырое, но видно, что все 20 ветвлений по ретам сформировались до корневого цикла.

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

      ClassSettings = {}
      ClassSettings.__index = ClassSettings
      function ClassSettings:New(_fileName, _maskFile)
      
      	local obj = {}
      	obj.stringList = createStringlist()
      	obj.fileName = _fileName
      	obj.maskFile = _maskFile
      	
      	obj.directoryPath = getCheatEngineDir()..'autorun'
      	obj.filePath = obj.directoryPath..'\\'..obj.fileName
      	
      	-- Есть ли такой ключ
      	function obj:HasKey(keyString)
      		local stringCount = self.stringList.Count
      		for i=0,stringCount-1 do
      			local items = self:Split(self.stringList[i])
      			if(keyString == items[1]) then
      				return true 
      			end
      		end
      	end
      	
      	-- Получить значение ключа
      	function obj:Get(keyString, defaultValue)
      		if(obj:HasKey(keyString)) then
      			local stringCount = self.stringList.Count
      			for i=0,stringCount-1 do
      				local items = self:Split(self.stringList[i])
      				if(keyString == items[1]) then
      					return  items[2]
      				end
      			end
      		end
      		return defaultValue
      	end
      	
      	-- Записать ключ
      	function obj:Set(keyString, stringOrDigitalValue)
      		-- Искать номер строки
      		local stringCount = self.stringList.Count
      		for i=0,stringCount-1 do
      			local items = self:Split(self.stringList[i])
      			if(keyString == items[1]) then
      				items[2] = stringOrDigitalValue
      				self.stringList.remove(self.stringList[i])
      				break
      			end
      		end
      		
      		self.stringList.add (keyString..' '..stringOrDigitalValue)
      	end
      	
      
      	-- Возвращает числовой вариант
      	function obj:GetDigital(keyString, defaultValue)
      		if(obj:HasKey(keyString)) then
      			return tonumber(obj:Get(keyString))
      		end
      		return defaultValue
      	end
      
      	-- Сохранить все ключи
      	function obj:Save()
      		self.stringList.saveToFile(obj.filePath)
      	end
      
      	function obj:FileExist(directoryPath, pathToFile, mask)
      		local paths = getFileList(directoryPath, mask, false)
      		for i=1,#paths do
      			if(paths[i] == pathToFile) then
      				return true
      			end
      		end
      		return false
      	end
      	
      	function obj:Split(argString)
      		local resultTable = {}
      		for i in string.gmatch(argString, "%S+") do
      			table.insert(resultTable, i)
      		end
      		return resultTable
      	end
      
      	
      	-- Загрузка ключей в память
      	if(obj:FileExist(obj.directoryPath, obj.filePath, obj.maskFile)) then
      		obj.stringList.loadFromFile(obj.filePath)
      	end	
      	setmetatable(obj, self)
      	return obj
      end
      	
      	
      classSettings = ClassSettings:New('userdata2.txt', '*.txt')
      \----------
      function DeleteAllBreakPoints()
      	local tableAddressOnBreakPoint = debug_getBreakpointList()
      	for i =1, #tableAddressOnBreakPoint do
      		debug_removeBreakpoint(tableAddressOnBreakPoint[i])
      	end
      end
      
      
      ClassTraceBranch = {}
      function ClassTraceBranch:New(_dataAddress, _onEnd)
      
      	local obj  = {}
      	obj.address 		= _dataAddress
      	obj.OnEnd 			= _onEnd
      	obj.Is64Bit 		= targetIs64Bit()
      	obj.tableData 		= {}
      
      	--Breakpoint methods: 				bpmInt3=0, bpmDebugRegister=1, bpmException=2
      	--breakpoint continue methods:      co_run=0, co_stepinto=1, co_stepover=2
      	--Breakpoint triggers:				bptExecute=0, bptAccess=1, bptWrite=2
      	--debug_setBreakpoint(self.address, 1, bptExecute, bpmDebugRegister)
      	
      	
      	function obj:OnBreakpoint() 
      	
      	
      		local XIP = 0
      		if(self.Is64Bit) then XIP = RIP else XIP = EIP end
      		
      		local someFind = false
      		local endAction = false
      		
      		for i=1,#self.tableData do
      			--if(self.tableData[i].XIP == XIP) then
      			if(self.tableData[i].XIP == XIP) then
      				self.tableData[i].Count = self.tableData[i].Count + 1
      				someFind = true
      				if(self.tableData[i].Count > 100) then
      					endAction = true
      				end
      				break
      			end
      		end
      		
      		if(endAction) then
      			--table.insert ( self.tableData, {XIP = XIP, Count = 1})
      			--self:PrintData()
      			--print('-->'..string.format('%08X',XIP))
      			DeleteAllBreakPoints()
      			debug_continueFromBreakpoint(co_run)
      			self.OnEnd()
      		else
      			if(not someFind) then
      				local _,opcode,_,_ = splitDisassembledString(disassemble(XIP))
      				local isRet = string.match(opcode,'ret')
      				--if(isRet) then
      				--	print('ret '..string.format('%08X', XIP))
      				--end
      				table.insert ( self.tableData, {XIP = XIP, Count = 1, isRet = isRet})
      				debug_continueFromBreakpoint(co_stepover)
      			end
      		end
      		return 1
      	end
      
      	function obj:PrintData()
      		local s = ''
      		local addressPrevious = getPreviousOpcode(self.address)
      		local _,opcode,_,_ = splitDisassembledString(disassemble(addressPrevious))
      		print(string.format('%08X - %s:\r\n', addressPrevious, opcode))
      		
      		for i=1,#self.tableData do
      			local _,opcode,_,_ = splitDisassembledString(disassemble(self.tableData[i].XIP))
      			s = s..string.format('%08X - %s - %s\r\n', self.tableData[i].XIP, self.tableData[i].Count, opcode)
      		end
      		print(s)
      	end	
      
      	debug_continueFromBreakpoint(co_stepover)
      	
      	setmetatable (obj, self)
      	obj.__index = ClassTraceBranch
      	return obj
      end
      
      ClassManagerBranches = {}
      function ClassManagerBranches:New(mainAddress, sizeBreakPoint)
      
      	local obj  = {}
      
      	
      	obj.tableData 				= {} --XIP = 0, traceBranch = nil
      	obj.mainAddress				= mainAddress
      	obj.sizeBreakPoint 			= sizeBreakPoint
      	obj.currentTraceBranch 		= nil
      	obj.isLogBranch				= false
      	
      	local Is64Bit 				= targetIs64Bit()
      
      	function obj:Containts(XIP)
      		for i=1, #self.tableData do
      			if(self.tableData[i].XIP == XIP) then 
      				return true
      			end
      		end
      		return false
      	end
      	
      	function obj:Stop()
      		
      		DeleteAllBreakPoints()
      		
      		for i = 1, #self.tableData do
      			local addressPrevious = getPreviousOpcode(self.tableData[i].traceBranch.address)
      			local _,opcode,_,_ = splitDisassembledString(disassemble(addressPrevious))
      			print(string.format('%08X %s', addressPrevious, opcode))
      		end
      		
      		print('\r\n')
      		
      		for i = 1, #self.tableData do
      			self.tableData[i].traceBranch:PrintData()
      		end
      		
      		debug_continueFromBreakpoint(co_run)
      	end	
      	
      	function obj:OnBreakpoint()
      	
      		if(self.isLogBranch) then
      			return self.currentTraceBranch:OnBreakpoint() 
      		end
      
      		local XIP = 0
      		if(Is64Bit) then XIP = RIP else XIP = EIP end
      		
      		if(not self:Containts(XIP)) then
      	
      			local hitAddress = getPreviousOpcode(XIP)
      			local _,opcode,_,_ = splitDisassembledString(disassemble(hitAddress))
      			--print(string.format('Start Trace at : %08X %s', hitAddress, opcode))
      			
      			DeleteAllBreakPoints()
      			
      			self.currentTraceBranch = ClassTraceBranch:New
      			(	
      				XIP, 
      				function ()
      					--print('Stop')
      					self.isLogBranch = false
      					debug_setBreakpoint(self.mainAddress, self.sizeBreakPoint, bptAccess, bpmDebugRegister)
      				end
      			)
      
      			table.insert (self.tableData, {XIP = XIP, traceBranch = self.currentTraceBranch})
      			
      			self.isLogBranch = true
      		end
      		
      		return 1
      	end
      	
      	
      	print('Start at: '.. obj.mainAddress)
      	debug_setBreakpoint(obj.mainAddress, obj.sizeBreakPoint, bptAccess, bpmDebugRegister)
      		
      	setmetatable (obj, self)
      	obj.__index = ClassManagerBranches
      	return obj
      end
      
      
      \--if(getOpenedProcessID() == 0) then openProcess('test.exe') end
      
      if(getOpenedProcessID() == 0) then 
      	openProcess('xrengine.exe')
      end
      
      managerBranches = ClassManagerBranches:New('37859970',4)
      
      function debugger_onBreakpoint()
      	return managerBranches:OnBreakpoint()
      end
      
      
      \--managerBranches:Stop()
      
      \-----------------------------
      
      \-- КЛАСС ClassMapBranch
      ClassMapBranch = {}
      ClassMapBranch.__index = ClassMapBranch
      function ClassMapBranch:New(_frmMarkers, _managerBranches)
      
      	local _frmMapBranch = createForm(false) --frmDissassembler -- createForm(false)
      	
      	_frmMapBranch.DoubleBuffered = true
      	_frmMapBranch.Caption 	= 'Map branches'
      	_frmMapBranch.Width 	= classSettings:GetDigital('obj.frmMapBranch.Width', 500)
      	_frmMapBranch.Height 	= classSettings:GetDigital('obj.frmMapBranch.Height', 900)
      	_frmMapBranch.Left		= classSettings:GetDigital('obj.frmMapBranch.Left', 0)
      	_frmMapBranch.Top 		= classSettings:GetDigital('obj.frmMapBranch.Top', 0)
      		
      
      	if(classSettings:Get('obj.frmMapBranch.Visible', '1') == '1') then
      		_frmMapBranch.show()
      	end
      	_frmMapBranch.show()
      		
      	_frmMapBranch.setBorderStyle(bsSizeable)
      	_frmMapBranch.FormStyle = 'fsNormal'
      	
      	
      	local obj = {}
      	
      	
      	obj.classManagerBranches = _managerBranches
      	obj.frmMapBranch 		= _frmMapBranch
      	obj.frmMarkedBranches	= _frmMarkers
      	obj.selectedAddress 	= -1
      	obj.mainCanvas 			= obj.frmMapBranch.Canvas
      	obj.bufferGraphic 		= createBitmap(obj.frmMapBranch.Canvas.Width, obj.frmMapBranch.Canvas.Height)
      	
      	obj.dy 					= 20
      	obj.column0_X 			= 30	-- маркер X
      	obj.column1_X 			= 65
      	obj.column2_X 			= 70 + 50
      	obj.column3_X 			= 70 + 50 + 40
      	obj.extraLineRender_Y	= -20
      	obj.tableVisibleAddress = {}		-- содержит данные рисуемой таблицы (см. свойства инже по коду)
      	
      	-- Можно поставить другие цвета для фона и шрифта
      	obj.colorBackground 	=  classSettings:GetDigital('obj.colorBackground', 0x00525252)
      	obj.colorFont 			=  0x00FFFFFF --classSettings:GetDigital('obj.colorFont', )
      	
      	
      	obj.frmMapBranch.Color 		= obj.colorBackground
      	obj.frmMapBranch.Font.Color = obj.colorFont 
      	
      	obj.isDirtyData = true
      	setProperty(obj.frmMapBranch, 'OnPaint', function () 
      		
      		if(obj.isDirtyData) then
      			obj.isDirtyData = false
      			obj:Draw()
      		end
      
      		obj.mainCanvas.draw(0, 0, obj.bufferGraphic)
      	end)
      	
      	setProperty(obj.frmMapBranch, 'OnResize', function ()
      		if(obj.bufferGraphic.Width ~= obj.mainCanvas.Width or
      			obj.bufferGraphic.Height ~= obj.mainCanvas.Height) then
      			obj.isDirtyResize = true
      		  end
      		end
      	)
      
      	-- Не уничтожать окно при закрытии
      	setProperty(obj.frmMapBranch, 'OnClose', function (sender) 
      		return caHide --Possible options: caHide, caFree, caMinimize, caNone
      	end
      	)		
      
      	
      	obj.checkTimer = createTimer(obj.frmMapBranch)
      	obj.checkTimer.Interval = 50
      	obj.lastTopAddress = 0
      	obj.isDirtyResize = false
      	obj.needUpdateDraw = false
      	
      	obj.checkTimer.OnTimer = function ()
      	
      		obj.needUpdateDraw = true
      		if(	obj.needUpdateDraw) then
      			obj.needUpdateDraw = false
      			obj.frmMapBranch.repaint()
      		end
      
      		if(obj.isDirtyResize) then
      			obj.isDirtyResize = false
      			obj.bufferGraphic.destroy()
      			obj.bufferGraphic = createBitmap(obj.frmMapBranch.Canvas.Width, obj.frmMapBranch.Canvas.Height)
      			obj.bufferGraphic.Canvas.Brush.Color 	= obj.colorBackground
      			obj.bufferGraphic.Canvas.Font.Color 	= obj.colorFont
      			obj.bufferGraphic.Canvas.floodFill(x,y)
      			obj.isDirtyData = true
      			obj.frmMapBranch.repaint()
      		end		
      	end
      	
      	
      	obj.mainAddress = string.format('%08X',obj.classManagerBranches.mainAddress)
      	
      	-- Рисует дизассемблерный код и может рисовать пути
      	function obj:Draw()
      		
      		local bufferGraphicCanvas = obj.bufferGraphic.Canvas
      		
      		bufferGraphicCanvas.Font.Size 		= sizeFont
      		bufferGraphicCanvas.Font.Color 		= obj.colorFont
      		bufferGraphicCanvas.Pen.Style 		= 'psSolid'
      		bufferGraphicCanvas.Brush.Style 	= 'bsClear'
      		bufferGraphicCanvas.Brush.Color 	= obj.colorBackground
      		
      		
      		bufferGraphicCanvas.clear()
      		obj.frmMapBranch.color = 0xFFFFFFFF -- obj.colorBackground
      		bufferGraphicCanvas.Brush.Color = 0xFFFFFFFF
      		bufferGraphicCanvas.Pen.Style   = 0xFFFFFFFF
      		
      		--bufferGraphicCanvas.Brush.Style = 'bsClear'
      		
      		local mainPoint = 
      		{
      			x = obj.frmMapBranch.Width / 2,
      			y =  obj.frmMapBranch.Height  - 50
      		}
      		
      		bufferGraphicCanvas.textOut(mainPoint.x, mainPoint.y, obj.mainAddress)
      		bufferGraphicCanvas.line(mainPoint.x - 300, mainPoint.y - 30, mainPoint.x + 300, mainPoint.y - 30)
      		
      		local tableData = obj.classManagerBranches.tableData
      		for i = 1, #tableData do
      			--local addressPrevious = getPreviousOpcode(tableData[i].traceBranch.address)
      			--local _,opcode,_,_ = splitDisassembledString(disassemble(addressPrevious))
      			--local s = string.format('%08X %s', addressPrevious, opcode)
      			--bufferGraphicCanvas.textOut(100, 100 + i * 20, 'Test: '.. s)
      			local dx = i*40
      			local dy = 20
      			
      			--bufferGraphicCanvas.line
      			--(
      			--	mainPoint.x - 300 + dx,
      			--	mainPoint.y - 30 - dy,
      			--	mainPoint.x - 300 + dx,
      			--	mainPoint.y - 30
      			--)
      			
      			local tableDataFromBranche = tableData[i].traceBranch.tableData
      			local countRets = 0
      			for j = 1, #tableDataFromBranche do
      				--table.insert ( self.tableData, {XIP = XIP, Count = 1, isRet = string.match(opcode,'ret')})
      				--table.insert ( self.tableData, {XIP = XIP, Count = 1, isRet = string.match(opcode,'ret')})
      				if(tableDataFromBranche[j].isRet) then
      					countRets = countRets + 1
      					bufferGraphicCanvas.ellipse
      					(	
      						mainPoint.x - 300 + dx - 5,
      						mainPoint.y - 30 - dy - countRets * 20 - 5,
      						mainPoint.x - 300 + dx + 6,
      						mainPoint.y - 30 - dy - countRets * 20 + 6
      					)
      				end
      			end
      			
      			bufferGraphicCanvas.line
      			(
      				mainPoint.x - 300 + dx,
      				mainPoint.y,
      				mainPoint.x - 300 + dx,
      				mainPoint.y - dy - countRets * 20 - 10
      			)
      		end	
      		
      		--local priority1 = math.random(0,155)
      		--local someColor = byteTableToDword({priority1,priority1,priority1,0})
      		--bufferGraphicCanvas.Font.Color = someColor
      				
      
      			
      
      		obj.mainCanvas.draw(0, 0, obj.bufferGraphic)
      	end	
      	
      
      	function obj:DrawArrow(point1, point2, distance, colorArrow, minY, a1_IsFarTop, a1_IsFarBottom, a2_IsFarTop, a2_IsFarBottom, a1_IsFarTop, a1_IsFarBottom, a2_IsFarTop, a2_IsFarBottom)
      
      		local bufferCanvas = obj.bufferGraphic.Canvas
      		
      		bufferCanvas.Pen.Color = colorArrow
      		bufferCanvas.Pen.Style = 'psDot'	
      		
      		local height = bufferCanvas.Height
      		
      		isDrawSideLine1 = true
      		if(point1.y < minY) 			then	point1.y = minY  isDrawSideLine1 = false
      		elseif (point1.y > height) 	then	point1.y = height isDrawSideLine1 = false
      		end
      		
      		isDrawRow = true
      		if(point2.y < minY) 			then	point2.y = minY	  isDrawRow = false
      		elseif (point2.y > height) 	then	point2.y = height isDrawRow = false
      		end
      		
      		if(isDrawSideLine1) then
      			bufferCanvas.line(point1.x, point1.y, point2.x - distance, point1.y)
      		end
      		bufferCanvas.line(point2.x - distance, point1.y, point2.x - distance, point2.y)
      		
      
      	
      		if(isDrawRow) then
      			bufferCanvas.line(point1.x, point2.y, point2.x - distance, point2.y)
      			obj.bufferGraphic.Canvas.Pen.Style = 'psSolid' --'psSolid'
      			local x1 = point2.x - 5
      			local x2 = point2.x 
      			local y2 = point2.y
      			
      			-- Рисование стрелки костыльным способом
      			bufferCanvas.line(x1,		y2 - 3,			x2,		y2)
      			bufferCanvas.line(x1,		y2 - 7 + 10,	x2,		y2)
      			bufferCanvas.line(x1,		y2 - 3,			x1,		y2 - 7 + 10)
      			bufferCanvas.line(x1 + 1,	y2 - 2,			x1 + 1, y2 - 7 + 9)
      			bufferCanvas.line(x1 + 2,	y2 - 1,			x1 + 2, y2 - 7 + 9)
      			bufferCanvas.line(x1,		y2 - 1,			x1 + 6, y2)
      		end
      
      	end
      				
      		
      	obj.frmMapBranch.OnDestroy = function (sender)
      		obj:SaveParameters()
      	end
      	
      	function obj:SaveParameters()
      	
      		classSettings:Set('obj.frmMapBranch.Width', obj.frmMapBranch.Width)
      		classSettings:Set('obj.frmMapBranch.Height', obj.frmMapBranch.Height)
      		classSettings:Set('obj.frmMapBranch.Left', obj.frmMapBranch.Left)
      		classSettings:Set('obj.frmMapBranch.Top', obj.frmMapBranch.Top)
      		
      		classSettings:Set('obj.colorBackground', obj.colorBackground)
      		
      		if(obj.frmMapBranch.Visible) then
      			classSettings:Set('obj.frmMapBranch.Visible', '1')
      		else
      			classSettings:Set('obj.frmMapBranch.Visible', '0')
      		end
      			
      		classSettings:Save()
      	end			
      		
      	obj:Draw()
      
      	setmetatable(obj, self)
      	return obj
      end	
      
      
      
      mapBranch = ClassMapBranch:New(frmMarkedBranches, managerBranches)
      
      написал в MasterGH
      MasterGHM
      MasterGH
    • RE: CE Dissect Data Scaner 1.0.2 (4 hardware breakpoints)

      Проведено огромное количество опытов над структурами. Много переделок.

      Очень кратко напишу, что поменялось.

      1. Название структуры состоит из адреса, количества адресов в ней и времени в миллисекундах на один байт в структуре.

      Подчеркнуто красным
      23c85a19-21c7-4812-8cf3-b55339a75bdb-image.png

      1. Смещения отбираются только те, на которых за X времени не было обнаружено ни одной инструкции на запись. Чем больше та самая чувствительность, тем точнее результат

      2. В именах структур теперь ценная информация. Это смещение, регистр и тип

      На скриншоте ниже можно посмотреть пункты 2 и 3.

      Также на этом скриншоте я заморозил по соседству адрес (поставил Lock) и пару раз пострелял в игре Сталкере Зов Припяти
      13ebe817-8585-4768-9c47-c6f75c2b1306-image.png
      Выводы, которые я сделал за очень и очень скромное время пользования. За минут 15 и на паре структур оружия в двух разных играх L4D2 и Сталкер Зов Припяти

      1. Если править 4-х байтовые значения, то легко можно нарваться на вылет игры. Но подключившись снова, можно продолжить.

      2. Правки значений с типом в 1 байт могут заблокировать оружие (в двух играх) или же устроить скорострельность на полную катушку в L4D2.

      3. Правки значений с типом float. Можно легко нарваться на вылет в игре, если например поставить нолик. Деление на ноль или какая-то иная причина. Лучше ставить чуть больше нуля, можно положительные или отрицательные значения

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

      5. Для сканера используется 1 аппаратный брейкпоинт, остальные три штуки еще не используются, т.к. сложно их прикрутить. Если использовать все 4 аппаратных бряка, то скорость сканера была бы в 4 раза быстрее. Сейчас на 2К байт по 10 мс, у меня уходит где-то 150 секунд. Если я ставил 20мс, то находилось на 10 смещений больше или какие-то другие смещения пропадали.

      Код будущего плагина все еще находится на стадии тестирования. Поэтому пока плагин не выкладываю.

      upd1: инструкции cmp, add, sub, xor, and, not, test, mulss, fsub, fmul, dec, inc, mul; теперь выводятся в имя элемента структуры.

      На скриншоте случайно вышел на координаты UI таймера. Сделал три скана трех структур в новых окнах

      629e6be0-a9da-4ebe-9bb5-011f3108998a-image.png
      upd2: перемещение структуры
      576ed324-e895-4d61-b511-02cac97fbbe2-image.png
      02ba5e5b-763b-4580-a0a0-88b9920270b7-image.png
      4e4dac89-4c6a-41be-a1c5-f59dd987106e-image.png
      Итог перемещения двух структур в первую
      b2bc976c-9111-4466-b3d3-443d9cd21478-image.png

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • [CE Lua Plugin] Способ раскраски значений найденных адресов

      930079fc-c2ee-4bae-9d42-667e8ee06307-image.png

      Пример раскрашивания адресов без каких-либо условий

      MainForm.Foundlist3.OnCustomDrawSubItem =
        function(sender, ListItem, SubItemIndex, state, DefaultDraw)
        if ListItem.Index % 3 == 0 then
          if SubItemIndex % 2 == 0 then
            sender.canvas.font.color = 0xff0000
          else
            sender.canvas.font.color = 0x00ffff
          end
        else
          sender.canvas.font.color = 0xffff00
        end
        return true
      end
      
      1. Из прошлой записи блога нашли "MainForm.Foundlist3" для работы с TListView (компонент от Lazarus среды разработки)

      2. OnCustomDrawSubItem — функция обработчик раскрашивания вложенных элементов в Item. Не поленитесь, зайдите в файл документации (C:\Program Files\Cheat Engine 7.4\celua.txt)

      Также есть OnCustomDrawItem — раскрашивание невложенных элементов.

      source

      Этот способ раскрашивания можно использовать по условиям. Чтобы связать условия с адресами и цветом смотри MemScan Class, FoundList class в celua.txt.

      написал в MasterGH
      MasterGHM
      MasterGH
    • CE Action Logger 1.0 Beta

      551365e9-d214-48a9-809d-dae6f8f79f17-image.png

      Cheat Engine 6.8.3 Cheat Engine Extensions

      Как установить:
      Распаковать файлы в директория Cheat Engine

      Версия: Beta 1.0 (22.018.2019)
          + Добавлены базовые действия в логи
          + Добавлены ссылки на ресурс game hack lab
      
      Файлы:
          autorun->User actions logger.lua
          autorun->GHLLogo.lua        
          autorun->forms->GHLLogo.frm
      
      написал в Плагин-строй
      MasterGHM
      MasterGH
    • RE: CE Tool Lua Regular expressions 2

      Lua поиск элемента до и после строки

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

      В утилите "Lua Regular Expressions (v. 1.0)"
      image.png
      Текст во вкладке "gmatch"

      Game1
      gameCompany
      DAU
      53.21k
      -20%
      Game2
      gameCompany
      DAU
      20.35k
      -20%

      Поиск элемента после строки: "DAU "DAU%c%c(.-)%c%c"

      53.21k;20.35k;

      Поиск элемента до строки: "%c%c(.-)gameCompany%c%c"
      (паттерн со двигом скобок для поиска предыдущей фразы)
      не прокатит для вывода списка игр над фразой gameCompany

      ;53.21k
      -20%

      Game2
      ;

      Очевидно, можно сделать поиск по похожим фрагментам, которые идут последовательно сверху вниз.

      Cначала добавим первую пустую строку и видим повторяющиеся фрагменты

      "%c%cGame1%c%cgameCompany%c%c"

      пишем шаблон ".*%c%c(.-)%c%cgameCompany%c%c" и опять мимо

      Game1;53.21k
      -20%

      Game2;

      Потому что текст над Game2 пошел выше Game2. Тогда делаем захват, только первой фразы и дальше не идем "%c%c(%w-)%c%cgameCompany%c%c"

      Game1;Game2;
      И тогда все ок.

      Но это еще не все. Осталась первая пустая строка, которую добавили, если её удалить, тогда "%c%c(%w-)%c%cgameCompany%c%c"

      Game2;
      Не видит Game1.

      Значит мы можем убрать %c%c, и будет "(%w-)%c%cgameCompany"

      Game1;Game2;

      Дальше название игры может быть таким "My Game: my Game". Здесь и пробел и двоеточие. В таком случае текст уже будет

      My Game1: my Game

      gameCompany

      DAU

      53.21k

      -20%

      My Game2: my Game

      gameCompany

      DAU

      20.35k

      -20%

      Пробуем "(%w-)%c%cgameCompany"

      Game;Game;
      Что не верно, т.к. захват одним (%w-)

      Мы должны в скобках развернуть фразу имени игры. В ней могут быть пробелы, числа, текст и двоеточие

      '([%w%s]-)%c%cgameCompany'

      my Game; my Game;

      Затем ([:%w%s]-)%c%cgameCompany

      My Game1: my Game;

      My Game2: my Game;

      Затем %c?%c?([:%w%s]-)%c%cgameCompany

      My Game1: my Game;
      My Game2: my Game;

      Вот и все. Если попариться один раз, то тексты уже парсить будет гораздо быстрее.

      Так я вывел столбы DAU и названий игр в таблицу, что было в районе 500 строк из пару десятков тысяч строк

      p.s. Текст в консоли Lua отличается %c%c, а %с

      p.s.p.s. Можно раcсплитить текст по "/n/r" или "/n" в таблицу строк и по индексам данных находить предыдущую или последующую фразу. Но мне проще две строки ввести "%c?%c?([:%w%s]-)%c%cgameCompany" и "DAU%c%c(.-)%c%c"

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • CE Tool Lua Regular expressions 2

      CT таблица для составления регулярных выражений 4-х функций

      string.match (s, pattern [, init])
      string.gmatch (s, pattern)
      string.gsub (s, pattern, repl [, n])
      string.find (s, pattern [, init [, plain]])
      

      Функция string.gsub может принимать в аргумент функции "repl" таблицу (тогда будет замена по ключам значений из таблицы ) или в аргумент "repl" может попадать некоторая другая функция с аргументом найденного слова (тогда будет вызов этой другой функции при каждом захвате символа или слова). Более подробное в документации Lua 5.3

      Еще несколько ссылок с практическим руководством

      lua-users wiki: Sample Code
      lua-users wiki: Optimisation Tips
      lua-users wiki: Object Oriented Programming
      lua-users wiki: Tutorial Directory

      9c7cb7f1-e10d-4d07-84c5-6d2ee905e441-image.png

      63a3f6ae-ce5e-4d49-b556-534380157b0f-image.png
      2df0435b-b355-48e3-934a-00e4daa0dcca-image.png

      8f2a6a5e-2b4c-4d9a-b13d-17a80956faa8-image.png

      f23565ca-e4bb-4896-a680-54f00a0a2975-image.png

      написал в Плагин-строй ce plugin
      MasterGHM
      MasterGH
    • CE disassembler

      80185edf-e083-49a5-9ed3-dde77b9d94cf-image.png
      2c49f00f-7aad-453e-8131-196e0c918444-image.png

      Кратко

      1. Добавил сохранение/загрузку параметров. Хранение в текстовом файле в папке autoruns

      2. Добавил настройки графического интерфейса (слайдеры, пункт меню)

      3. Обновления UI

        • Гладкая перерисовка, двойной буфер
        • Стрелки прыжков сдвигаются вправо при увеличении их количества
        • Слайдер яркости фона
        • Слайдер количества адресов в невидимой области от 0 до +1К от верхнего и нижнего адресов. Увеличивает количество стрелок для прыжков.
        • Стрелки в невидимой области обозначаются двумя цветами.
          Один цвет для тех стрелок, у которых адрес прыжка и адрес назначения не выходят в область видимости.
          Второй цвет для тех стрелок, которых адрес прыжка или адрес назначения входят в области видимости.
          Видимые стрелки обозначаются белым.
          На рисуемые стрелки прыжков влияет слайдер яркости.
        • Не показывать стрелки прыжков для инструкций типа 'jmp [...]', 'jne [...]'...
      4. Сохранение и загрузка при заходе и выходе из CE

        • значения слайдеров
        • верхнего адреса
        • выбранного адреса
        • параметры шрифтов и других

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

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

      Также в плагин встроена поддержка работы с окнами трейслогов.
      8de95150-d583-40ce-9bd0-f351c8ae3e6d-image.png

      Происходит постоянный поиск новых и отсутствующих окон трейслогов... Делаем трейслоги. Двойной клик по адресам и переходим по адресу в дизассемблер (если он открыт) и видим в нем раскрашенные пути маркерами. Если окон много, то видим пересечения, расхождения путей и видим инструкции, которые не выполнялись...Встроено контекстное меню установки брейкпоинтов на call-ы в трейслоге. Если его вызывать, то после идем в игру и, не делая того, что исследуем, прерываемся на брейкпоинтах и снимаем их. Когда больше не прерываемся, то делаем в игре то, что нужно и прерываемся на оставшемся брейкпоинте. На нем исследуем условие входа в рутину. Тут как бы можно на страх и риск изменить условие, чтобы ветка кода повторила свое выполнение. Если позволяет, то выполнить поток вызвав call... Но я придерживаюсь варианта изменения данных для того чтобы поток сам повторил рутину. Либо подменить параметры функции или подменить параметры после выхода функции. На данный момент показать что-то на практике нечего. Как будет, сделаю и покажу видео.

      Встроена поддержка маркирования путей на выбранном участке кода. Это специальное окно в меню "утилитах" в окне дизассемблера его можно найти. Указывается верхний адрес и нижний адрес области кода. Например, функции. При старте начинается логирование уникальных прохождения от начального до конечного адреса и раскраска маркерами. Возможных таких путей 8. Т.е. делая что-то в игре (разовые действия) видим уникальную ветвь, она тут же появляется и на этой ветви можно прерваться поставив опцию, и оттрейсить выход из рутины...

      Более подробное описание и скриншоты в закрытом разделе в моем старом блоге. Имеют к нему доступ пользователи со статусом "Разработчики". Новую информацию я буду писать в этой теме.

      Плагин сейчас на стадии чернового варианта, кто хочет пробуйте. Установка с репозитория. Чтобы подключить нужно загрузить .CT таблицу. Знаю что не удобно, но пока так.

      Плагин планируется развивать дальше, сейчас это черновой вариант

      написал в Плагин-строй ce plugin
      MasterGHM
      MasterGH
    • RE: CE Compact View

      Ниже находится обучающий пример плагина для CE Lua с установкой компактного режима применяя парадигму ООП — инкапсуляции.

      Для программирования плагинов на Lua могут пригодится приемы ООП. Это касается сущностей, их поведений и взаимодействий между ними. В данном примере CECompactView — описание класса на основе мета-таблицы и оно является сущностью. Функции класса — uncompact() и compact(), которые реализуют поведения этой сущности. Основной скрипт с описанием сущности можно поместить в отдельный файл .lua и далее можно кратко инициализировать сущность и управлять ей

      Например

      \-- Создание экземпляра класса через new() функцию, где аргументом является состояние компактный или некомпактный
      ce_compact_view = CECompactView:new(true)
      
      \-- Проверить состояние
      \--print(ce_compact_view:get_state() and 'Compact View Mode' or 'Full View Mode')
      
      \-- Установить компактный режим
      \--ce_compact_view:compact()
      
      \-- Установить не компактный режим
      \--ce_compact_view:uncompact()
      

      Сам класс или мета-таблица.

      \-- Класс инкапсулирующий поведения компактного состояния окна CE
      CECompactView = {}
      
      function CECompactView:new(state)
      
      	local obj = {}
      	
      	-- Состояние компактное или нет: true или false
      	obj.compact_mode = state
      	
      	-- Форма CE, где getMainForm — поддерживаемая функция CE и возвращает главную форму CE
      	obj.form_ce = getMainForm()
      	
      	-- Получение элементов контекстного меню главной формы
      	local menu_items = obj.form_ce.menu.items
      	
      	-- Создание нового контекстного меню с названием CompactView и именем ItemCompact
      	obj.menu_item_compact = createMenuItem(menu_items)
      	obj.menu_item_compact.name = 'ItemCompact'
      	obj.menu_item_compact.caption = 'CompactView'
      	
      	-- Добавление контекстного меню на главную форму
      	menu_items.add(obj.menu_item_compact)
      
      	-- Сменить состояние окна CE: компактное или нет
      	function obj:set(state)
      		-- view_components состояния скрытия компонентов
      		obj.compact_mode = state
      		-- видимость компонентов обратна компактному режиму
      		local view_components = not state
      		obj.form_ce.panel1.visible = view_components
      		obj.form_ce.panel4.visible = view_components
      		obj.form_ce.panel5.visible = view_components
      		obj.form_ce.Splitter1.visible = view_components
      		-- Записать имя по состоянию
      		obj.menu_item_compact.caption = state and 'Full View Mode' or 'Compact View Mode' 
      		-- Поставить обработчик по состоянию
      		obj.menu_item_compact.onClick = state and obj.compact or obj.uncompact
      		
      		print(state and 'Compact View Mode' or 'Full View Mode')
      	end
      
      	-- Некопактное окно CE
      	function obj:uncompact()
      		obj:set(true)
      	end
      
      	-- Компактное окно CE
      	function obj:compact()
      		obj:set(false)
      	end
      
      	-- Состояние окна CE: компактное или нет
      	function obj:get_state()
      		return obj.compact_mode
      	end
      
      	-- Связь таблицы (смотрим докуметацию по Lua)
      	setmetatable(obj, self)
      	obj.__index = self
      
      	-- Активировать состояние
      	obj:set(state)
      
      	-- Возвращает ссылку на экземпляр
      	return obj
      end
      \------------
      
      \-- Создание экземпляра класса
      ce_compact_view = CECompactView:new(true)
      
      \-- Проверить состояние
      \--print(ce_compact_view:get_state() and 'Compact View Mode' or 'Full View Mode')
      
      \-- Установить компактный режим
      \--ce_compact_view:compact()
      
      \-- Установить не компактный режим
      \--ce_compact_view:uncompact()
      

      Скрипт можно сохранить в Lua файл и добавить в папку autorun.

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • CE Compact View

      Функции

      1. Контекстное меню. Компактное/ не компактное окно CE
      2. Сохранение и загрузка положения и размера формы CE
      3. Сохранение и загрузка компактного состояния формы или не компактного

      После запуска, CE будет там, где её (CE) закрыли с позицией, размерами и с состоянием "Компактны режим", если последний был включен.

      Параметры сохраняются на жесткий диск в файл "..\autorun\userdata.txt"

      написал в Плагин-строй
      MasterGHM
      MasterGH
    • Поиск данных в окне Tracer
      \-- ищет адрес по адресу
      referencedAddress == 0x0165F8BC
      
      \-- ссылка на байты. Почему-то не работает. Ниже будет другой путь
      referencedBytes == dwordToByteTable(98)
      
      \-- сравнение по инструкции. В конце инструкции нужно ставить пробел
      instruction == "ret "
      
      \-- так будет искать все вхождения "mov"
       instruction:match("mov")
      

      Ну и более интересная версия перебора и одновременного выделения записей.

      Здесь нужно открыть окно "Трейсера" и рядом в Lua окне писать свои условия в функции "Compare()", "PrintData()", Selected()

       -- Поиск окна Трейслога
      function GetTTreeViewTracelogs()
        local max = getFormCount()
        for i=0, max-1 do
          if(getForm(i).ClassName == 'TfrmTracer') then
            return getForm(i)
          end
        end
        return nil
      end
      
      function FindTraceLogData()
        -- Нашли окно Трейслога
        tracerForm = GetTTreeViewTracelogs()
        -- Перебираем все записи
        for index=0, tracerForm.Count do
          -- Если запись пуста, то пропускаем
          if tracerForm.Entry[index] ~= nil then
            local entry = tracerForm.Entry[index]
            if Compare(entry) then
              Selected(entry, index)
              PrintData(entry,index )
            end
          end
        end
      end
      
      \-- Можно свое условие
      addressEAX = 0x001FB780
      
      function Compare(entry)
         return addressEAX == entry.context.EAX
      end
      
      function PrintData(entry)
        --print(tracerForm.Entry[index].instruction)
         print(disassemble(entry.context.RIP))
      end
      
      function Selected(entry, index)
         tracerForm.lvTracer.Items[index].Selected = true
      end
      
      FindTraceLogData()
      

      Из документации по окну Tracer

      TfrmTracer class (Inheritance: Form->ScrollingWinControl->CustomControl->WinControl->Control->Component->Object)
      
      properties
        Count: integer - number of entries in the list
        selectionCount: integer - The number of selected entries
      
        Entry[index]: table - Information about each entry. Read only. (Index starts at 0)
          table is formatted as:
          {
            address: integer - address of the instruction
            instruction: string - disassembled instruction
            instructionSize: integer - bytesize of the instruction
            referencedAddress: integer - address the code references
            referencedData: bytearray - The bytes of the referenced data at the time of tracing
            context: contexttable - the state of the cpu when this instruction got executed (contains registers(EAX/RAX, ...), floating points(FP) and XMM values
            hasStackSnapshot: boolean - set to true if there is a stack entry      
            selected: boolean - Set to true if the entry is selected
      
          }
      
      
        StackEntry[index]: bytearray - The stacksnapshot of that entry. Nil if not available
      
      methods
      \---------------------------------------------------
      Treeview Class : (Inheritance: CustomControl->WinControl->Control->Component->Object)
      createTreeView(owner)
      
      properties
        Items: TreeNodes - The Treenodes object of the treeview (ReadOnly)
        Selected: TreeNode - The currently selected treenode
      
      methods
        beginUpdate()
        endUpdate()
        getItems()
        getSelected()
        setSelected()
        fullCollapse()  : Collapses all the nodes, including the children's nodes
        fullExpand() : Expands all the nodes and all their children
        saveToFile(filename): Saves the contents of the treeview to disk
      
      \---------------
      
      TreeNodes class : (Inheritance: TObject)
      properties
        Count : Integer - The total number of Treenodes this object has
        Item[]: TreeNode - Array to access each node
        [] = Item[]
      methods
        clear()
        getCount()
        getItem(integer) : Return the TreeNode object at the given index (based on the TreeView's Treenodes)
        add(text:string): Returns a new root Treenode object
        insert(treenode, string): Returns a new treenode object that has been inserted before the given treenode
        insertBehind(treenode, string): Returns a new treenode object that has been inserted after the given treenode
      
      \---------------
      
      TreeNode class : (Inheritance: TObject)
      properties
        Text: string - The text of the treenode
        Parent: Treenode - The treenode this object is a child of. (can be nil) (ReadOnly)
        Level: Integer - The level this node is at
        HasChildren: boolean - Set to true if it has children, or you wish it to have an expand sign
        Expanded: boolean - Set to true if it has been expanded
        Count : Integer - The number of children this node has
        Items[]: Treenode - Array to access the child nodes of this node
        [] = Items[]
        Index: Integer - The index based on the parent
        AbsoluteIndex: Integer - The index based on the TreeView's Treenodes object (Items)
        Selected: Boolean - Set to true if currently selected
        MultiSelected: Boolean - Set to true if selected as well, but not the main selected object
        Data: Pointer - Space to store 4 or 8 bytes depending on which version of CE is used
      methods
        delete()
        deleteChildren()
        makeVisible()
        expand(recursive:boolean=TRUE OPTIONAL) : Expands the given node
        collapse(recursive:boolean=TRUE OPTIONAL)  : collapses the given node
        getNextSibling(): Returns the treenode object that's behind this treenode on the same level
        add(text:string): Returns a Treenode object that is a child of the treenode used to create it
        
        ------------------------------------------
        wordToByteTable(number): {}          - Converts a word to a bytetable
      dwordToByteTable(number): {}         - Converts a dword to a bytetable
      qwordToByteTable(number): {}         - Converts a qword to a bytetable
      floatToByteTable(number): {}         - Converts a float to a bytetable
      doubleToByteTable(number): {}        - Converts a double to a bytetable
      extendedToByteTable(number): {}      - Converts an extended to a bytetable
      stringToByteTable(string): {}        - Converts a string to a bytetable
      wideStringToByteTable(string): {}    - Converts a string to a widestring and converts that to a bytetable
      
      byteTableToWord(table, OPTIONAL signed:boolean): number       - Converts a bytetable to a word
      byteTableToDword(table, OPTIONAL signed:boolean): number      - Converts a bytetable to a dword
      byteTableToQword(table): number      - Converts a bytetable to a qword
      byteTableToFloat(table): number      - Converts a bytetable to a float
      byteTableToDouble(table): number     - Converts a bytetable to a double
      byteTableToExtended(table): number   - Converts a bytetable to an extended and converts that to a double
      byteTableToString(table): string     - Converts a bytetable to a string
      byteTableToWideString(table): string - Converts a bytetable to a widestring and converts that to a string
      
      bOr(int1, int2)   : Binary Or
      bXor(int1, int2)  : Binary Xor
      bAnd(int1, int2)  : Binary And
      bShl(int, int2)   : Binary shift left
      bShr(int, int2)   : Binary shift right
      bNot(int)         : Binary not
      

      Для вывода referencedBytes из трейслога можно использовать byteTableToDword(referencedBytes) (смотрим документации выше) получая из TfrmTracer.Entry[index].referencedBytes или в строке поиска у Трейслога вбить

      byteTableToDword(referencedBytes) > 0 and print(string.format("0x%08X - 0x%08X", RIP, byteTableToDword(referencedBytes))) == 1
      
      написал в MasterGH tracer ce lua
      MasterGHM
      MasterGH
    • Функции для работы с адресами
      disassemble(address): Disassembles the given address and returns a string in the format of "address - bytes - opcode : extra"
      splitDisassembledString(disassembledstring): Returns 4 strings. The address, bytes, opcode and extra field
      
      getInstructionSize(address): Returns the size of an instruction (basically it disassembles the instruction and returns the number of bytes for you)
      getPreviousOpcode(address): Returns the address of the previous opcode (this is just an estimated guess)
      
      написал в MasterGH
      MasterGHM
      MasterGH
    • Пользовательские тип данных в hex-окне (пример)
      function bytes_to_value_function(b1,b2,b3,b4)
        -- для примера
        local table = {b1, b2, b3, b4}
        local dword = byteTableToDword(table)
        local refDataFromAddress = getAddressSafe(dword)
        if dataPointer ~= nil then
           return refDataFromAddress
        end
        return dword
      end
      
      function value_to_bytes_function(integer)
        -- просто для примера
        return bXor(integer, 100)
      end
      local isFloat = false
      registerCustomTypeLua('Pointer 2', 4, bytes_to_value_function, value_to_bytes_function, isFloat)
      

      Справка

      CustomType class (Object)
      The custom type is an convertor of raw data, to a human readable interpretation.
      
      global functions
        registerCustomTypeLua(typename, bytecount, bytestovaluefunction, valuetobytesfunction, isFloat)
          Registers a Custom type based on lua functions
          The bytes to value function should be defined as "function bytestovalue (b1,b2,b3,b4)" and return an integer as result
          The value to bytes function should be defined as "function valuetobytes (integer)" and return the bytes it should write
      
          returns the Custom Type object
      
      
        registerCustomTypeAutoAssembler(script)
          Registers a custom type based on an auto assembler script. The script must allocate an "ConvertRoutine" and "ConvertBackRoutine"
      
          returns the Custom Type object
      
        getCustomType(typename) : Returns the custom type object, or nil if not found
      
      properties
        name: string
        functiontypename: string
        CustomTypeType: TCustomTypeType - The type of the script
        script: string - The custom type script
        scriptUsesFloat: boolean - True if this script interprets it's user side values as float
      
      methods
        byteTableToValue({bytetable},Address Optional)
        valueToByteTable(value, Address Optional)
      
      написал в MasterGH custom types
      MasterGHM
      MasterGH
    • 1 / 1