Программы
Предназначение:
Прочее
Открытая версия виртуальной машины JASS, написаная на FreePascal и содержащая некоторые улучшения.
В приложеном архиве есть две версии библиотеки: нормальная и Limitless, в которой отключен лимит операций.
Список изменений
Остальные изменения в комментариях.
  • 2023.04.02
    • Добавил совместимость с модом RedirectCalls.
  • 2023.03.29
    • Оптимизировал работу со стэком, теперь время сортировки упало с 60-ти секунд до 54-ех.
  • 2023.03.28
    • Теперь нативки получают копии строк из аргументов, так что, в случае перевыделения таблицы строк, указатели не сломаются.
    • Теперь алгоритм склеивания строк аналогичен таковому у игры.
    • Исправлены ошибки связанные с нулевыми строками.
    • Теперь система подсчета ссылок учитывает значения в регистрах и стеке. Благодаря этому строки больше не утекают, но производительность просела: время сортировки поднялось с 40-ка секунд до 60-ти.
    • Исправлена моя ошибка, из-за которой локальные переменные не удалялись.
    • Теперь между вызовами функций инициализации глобалок проводится очистка потока, чтобы предыдущие вызовы не мешали следующим.

Отличия

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

Переменные

  • Каждый раз, при доступе к переменной или функции, игра хэширует (игнорируя уже имеющиеся в кэше значения) их имена и ищет их в хэштаблице, на что тратится лишнее время. Здесь же объекты достаются из массива по индексу, на что требуется константное время.
  • При доступе к переменным, игра в первую очередь предпочитает локальные, но если такой не обнаруживается, то использует глобальную. Проблема в том, что поиск глобальной переменной происходит независимо от результата поиска локальной и на это всегда напрасно уходит двойное время. Здесь это исправлено.
  • По выходу из функции, стекфрейм со всеми переменными уничтожается, я же провожу их минимальную очистку, чтобы затем эффективно переиспользовать.

Массивы

  • При объявлении переменной-массива, изначально та не занимает памяти. Объект создается при первой записи и память для элементов выделяется по необходимости. Так как максимальный размер массива всего 8192 элемента, которые в суме занимают лишь 32 КиБ, я решил сразу создавать массивы с полным объемом, чтобы не тратить постоянно время перевыделение памяти.

Строки

  • Каждый раз, когда в переменную помещается литеральная строка (значение которой прописано прямо в исходном коде, например: "Hello, World!"), игра расчитывает её хеш и производит поиск в хэш-таблице. У меня же это просто операция присвоения переменной числа с индексом строки, без лишних движений.
  • Счетчик ссылок строк сломан, из-за чего тот постоянно растет, что может привести к переполнению и строка будет преждевременно уничтожена. У меня исправлено.
  • Любая созданая строка будет существовать до конца игры (память утекает), я же уничтожаю строки, на которые не остается ссылок.
  • Игры не обнуляет автоматически локальные переменные по выходу из функции, что приводит к утечке ссылок. Я же обнуляю хотя бы строки, чтобы те могли уничтожиться, после того как станут ненужны.

Событие изменения переменной

  • У игры есть возможность подписаться на изменения переменной по имени. Из-за этого, при каждом исполнении инструкции записи в переменную, интерпретатор вызывает функцию обратного вызова, которая производит медленный поиск подписчиков в хэш-таблице, на что тратится лишнее время даже при условии отсутсвия таковых. У меня для этого достаточно лишь проверить ячейку массива по индексу. При желании можно скомпилироваться без этой функции, но тогда карты, использующие эту сомнительную возможность, перестанут работать (например, "Life in Arena").

Замеры производительности

В целом, следовало бы сделать полноценный тест с участием различных алгоритмов, но пока что есть только это.
Для оценки скорости сравнивалось время, затраченное на сортировку методом пузырька массива, длинною 8192 элемента, содержимое которого было предварительно отсортировано наоборот.
Производительность ниже, чем могла бы быть, из-за того, что компилятор отказывается inline'ить некоторые функции.
Длинна имен переменных Время с модом Время без мода
1 77.75 98.05
4 76.25 111.95
12 76.88 168.23
Время указано в секундах.

Тестовая карта

Можете скачать карту и повторить тест на своих компьютерах.
Для её запуска требуется мод (исходники), добавляющий новые нативки для получения текущего времени и для отключения лимита операций, так как иначе игра прервет поток из-за большого количества выполненых действий.
Смените его расширение на ".mix" и киньте в папку с игрой или воспользуйтесь инжектором.
В свою очередь, мод нуждается в библиотеке для регистрации нативок, она тоже должна присутствовать в папке игры.
Мод с нативками не способен повлиять на новый интерпретатор, так что для тестов воспользуйтесь Limitless версией, в которой изначально отключены проверки на количество операций.
Список добавляемых нативок
native GetTickCount takes nothing returns integer
native SetOperationLimitEnabled takes boolean state returns nothing
native IsOperationLimitEnabled takes nothing returns boolean
Когда карта загрузится, наберите в чат "-start" и начнется сортировка.
Игра зависнет и то, на сколько это продлится, будет зависеть от силы ядра вашего процессора.
`
LOADING AD...
16
Те, кто уже успел скачать, лучше перекачайте, так как кнопка "скачать" ссылалась на архив с версией без лимита операций (JassAcceleratorLimitless.zip), которая может привести к десинхронизации при игре в картах, в которых поток выполнения jass-скрипта обрывается из-за, например, бесконечных циклов. Разработчик мог не заметить ошибки, а вас выбросит из игры.
32
IceFog, все продолжаешь делать апграйды вара, с анрайзом что-ли скопирируйтесь и сделайте нам новый сервер для игры в варик со всеми плюшками =)
16
У меня были мысли о создании проэкта для совместного реверсинга варкрафта, как у майнкрафта есть MCP. Только нужно придумать как это всё оформить.
10
Автор занимается очень интересными вещами, я думаю им определенно нужно придать больший охват, чтобы они не остались "в столе".
10
Я сделал небольшой мод (исходники), исправляющий эту ошибку.
Обе ссылки выдают 404 ошибку.
16
Обе ссылки выдают 404 ошибку.
Поправил, теперь должно скачиваться.
16
Latest news!
Теперь исходный код доступен для скачивания.
22
IceFog, Спасибо за открытый код, будет интересно потыкаться, да и доверия таким проектам больше!
16
A new version is out! Scroll to the resource
  • Исправил у себя утечки памяти, при удалении переменных с типом массив.
  • Теперь, во избежание десинхронизации, по умолчанию используется эмуляция вещественных чисел.
  • Добавил поддержку нативки TriggerRegisterVariableEvent.
38
На чем основано решение выбрать мертвый язык для реализации?
При объявлении переменной-массива, изначально та не занимает памяти. Объект создается при первой записи и память для элементов выделяется по необходимости. Так как максимальный размер массива всего 8192 элемента, которые в суме занимают лишь 32 КиБ, я решил сразу создавать массивы с полным объемом, чтобы не тратить постоянно время перевыделение памяти.
Получается, наоборот, даунгрейд. Возвращение к пещерным технологиям) аллокация памяти в jass сделана на основе аллокации std::vector - там тоже динамическая степень двойки. Далеко не факт, что понадобится весь размер массива во всех случаях.
Моддеры часто пользуются этой возможностью.
Replies (9)
16
ScorpioT1000, игра не использует STL, вместо этого у неё свои велосипеды. Массив в JASS использует шаблонный класс TSGrowableArray, который при росте выделяет про запас до 63-х элементов, как я уже писал ранее.
Далеко не факт, что понадобится весь размер массива
Делалось в первую очередь для локалок. А для глобалок, наверное, можно вернуть как было.
16
На чем основано решение выбрать мертвый язык для реализации?
Я начинал с JASS'а, после чего перешел на Delphi. Там мне что-то не понравилось и я пересел на Lazarus/FreePascal. Потом я знакомился с Java, Python, Lua, но ни один из них не годился для хаков.
Еще есть Rust, но писать хаки на этом языке, сосредоточеном на безопасности, будет тем еще развлечением.
Лишь недавно я начал изучать C++, благодаря которому появился выбор и на котором теперь я буду предпочитать делать все новые моды.
Вот только этот проект уже написан на FreePascal и переписывать его на другой язык пока лень, хотя я уже успел настрадаться от него. Мог бы даже написать небольшую статью о том, почему не стоит на нём работать и о всех тех минах, что там расставлены.
10
IceFog, я доделываю перевод твоего JassAccelerator'a с Pascal на с++. Скоро выложу здесь в комментах проект. Посмотришь на него с другой стороны.)
38
IceFog, есть ещё C# и дотнет, который сейчас билдится даже на чайнике и имеет много возможностей работы с теми же виндовыми либами, но где не надо изобретать string, filesystem, utf-8, колесо и большой взрыв
10
IceFog, В общем надо дорабатывать проект. Остановился пока на файле hook.hpp там большая ассемблерная вставка. Там нужен результат. И весь проект надо проверять. Если добьёшь выкладывай новый. Я пока не буду его дорабатывать.
16
Vampir_kolik, удали тот ужас и используй вместо него MinHook.
10
IceFog, Я пока карту поделаю, вдруг успею до окончания наработок на UjAPI. Пока время есть. Ладно буду знать, что это аналог той функции.
2
Еще есть Rust, но писать хаки на этом языке, сосредоточеном на безопасности, будет тем еще развлечением.
Лишь недавно я начал изучать C++, благодаря которому появился выбор и на котором теперь я буду предпочитать делать все новые моды.
Тут неоспоримо, для хаков лучше Си/Плюсы, а вот если с нуля что то писать, то лучше всё же раст, поддерживать большой проект на плюсах это та еще боль.
Вот только этот проект уже написан на FreePascal и переписывать его на другой язык пока лень, хотя я уже успел настрадаться от него. Мог бы даже написать небольшую статью о том, почему не стоит на нём работать и о всех тех минах, что там расставлены.
было бы интересно почитать. Сейчас не так часто можно встретить что то интересное что писалось бы на этом. Последнее что я встречал это github.com/red-prig/fpPS4
16
было бы интересно почитать
Что сразу могу вспомнить, так это три вещи:

WriteLn

Функция вывода в консоль крашится, если к программе не подключена консоль.
Краш-код
program my_first_program;

begin
	WriteLn('Hello, World!'); // Ты пытался, но у тебя ничего не вышло.
end.

printf

При конструировании массива, который передается функциям с переменным числом аргументов, при передаче DWORD'а тот конвертируется в int32 и, если его значение вне диапазона 0..0x7FFFFFFF, то выкидывается исключение RangeError, которое приводит к крашу моих модов. Это происходит независимо от режима сборки (релиз/отладка) и состояния флага проверки ошибок диапазонов.
Remark: Note that there is no support for DWord (or cardinal) arguments in array of const. They are converted to vtInteger/vtLongint. This is for Delphi compatibility, and the compiler will ignore any resulting range checks when in mode Delphi.
В документации написано, что это сделано для совместимости с Delphi, но каким образом это (краши везде, кроме Delphi) способствует ей я так и не понял.
Пример кода с крашем
program omg;

uses
	SysUtils;

var
	number: Dword = 0xFFFFFFFF;

begin
	WriteLn(Format('number = %d', [number])); // crash
end.
Этот код не в состоянии вывести в консоль значение переменной.
Исправляется ручным конвертированием значения в тип, который точно вместит в себя все его значения:
	WriteLn(Format('number = %d', [Int64(number)]));

Память мертвого потока

При попытке освободить память, которая была выделена во время исполнения точки входа библиотеки, можно легко получить краш.
Происходит это в случае, если поток, во время исполнения которого была выделена память, уже завершился. Такое может произойти в случае, когда вместо помещения .mix файла в папку с игрой (тогда игра сама загрузит его и точка входа выполнится в главном потоке, который будет долго жить), используется инъектор DLL (в таком случае, в целевом процессе создается поток, который завершается сразу после загрузки библиотеки).
Вообще, в freepascal'е есть функция создания потока BeginThread которая, возможно, корректно обрабатывает завершение потока, перекладывая права владения на кого-нибудь другого, но поток в котором исполняется точка входа находится вне моей власти.
Для решения этой пролемы можно попробовать в точке входа сразу создавать новый поток через BeginThread и уже в нём выделять память, либо воспользоваться возможностью смены менеджера памяти (подключение модуля cmem самым первым в главном файле программы позволит использовать менеджер памяти из msvcr.dll).
Пример краш-кода
program dead_memory;

var
	memory: Pointer;

procedure OnChatMessage(s: PChar);
begin
	// ...
	FreeMem(memory); // crash
end;

begin
	memory:= GetMem(1024);
	AddChatHandler(@OnChatMessage);
end.
16
A new version is out! Scroll to the resource
  • ИИ скрипт больше не должен крашиться.
  • Теперь использую стандартные булеаны: 0 — ложь, 1 — истина.
  • Теперь игра не выделяет память для каждой строки дважды.
  • Исправлена моя утечка объектов CStringRep.
  • Исправлена моя утечка глобальных массивов.
To leave a comment please sign in to the site.