Исходник UltraLight for Moonloader

damirtakioma

Участник
Автор темы
54
28
UltraLight for Moonloader
HTML / CSS / JS интерфейсы прямо в GTA SA и SA-MP, управляемые из Lua​



Что это

Библиотека для MoonLoader, которая позволяет делать окна интерфейса на обычном HTML, CSS и JavaScript и показывать их поверх игры. Вместо ручной отрисовки через render/imgui вы пишете страницу, а из Lua только отправляете в неё данные и получаете события от кнопок.

Под капотом работает движок Ultralight (лёгкий WebKit-рендерер для игр и приложений).



Как это устроено

GTA SA, 32-разрядная программа, а Ultralight 1.4 распространяется только в 64-разрядной сборке. Поэтому работа разделена на два процесса:

Код:
gta_sa.exe (x86)                               ul_helper.exe (x64)
 ├─ MoonLoader: ваш Lua-скрипт                  └─ Ultralight 1.4
 │    └─ lib/ultralight.lua (FFI)                   рендерит HTML-страницу
 └─ ultralight_lua.asi  ◄─── общая память ───►
      • запускает ul_helper.exe                команды: load_url, eval, мышь, клавиши
      • рисует кадр поверх игры (D3D9)          ◄── кадры (картинка) и сообщения JS → Lua

  1. ultralight_lua.asi (32 бит, без Ultralight внутри). При старте скрипта запускает хелпер, получает от него готовые кадры и рисует их поверх игры через хук EndScene. Ввод и команды передаёт обратно хелперу.
  2. ul_helper.exe (64 бит). Содержит Ultralight, загружает страницу, исполняет JS, рендерит на CPU и кладёт кадры в общую память. Завершается вместе с игрой.
  3. lib/ultralight.lua. Lua-обёртка через FFI. Всё, что вы используете в своих скриптах, находится здесь.
  4. Общая память. Два буфера кадра и две очереди сообщений (в хелпер и из хелпера).

Путь данных Lua → страница: ul.call / ul.push превращаются в JS-вызов, который выполняется в странице.
Путь данных страница → Lua: страница вызывает lua('имя', данные), это уходит в очередь, а Lua забирает событие в ul.process() и вызывает ваш обработчик, зарегистрированный через ul.on.

Перезагрузка Lua-скрипта (Ctrl+R) безопасна: хелпер уже запущен и просто переиспользуется.



Требования
  • MoonLoader, SAMPFUNCS, ASI Loader

Установка
  1. Скопируйте содержимое архива в корень GTA:
Код:
GTA/
├─ ultralight_lua.asi
├─ ultralight/
│   ├─ ul_helper.exe
│   ├─ Ultralight.dll, UltralightCore.dll, WebCore.dll, AppCore.dll
│   └─ resources/ (cacert.pem, icudt67l.dat)
└─ moonloader/
    └─ lib/ultralight.lua



HTML создаётся из Lua-скрипта

Отдельные .html файлы в архиве не нужны. Страница хранится обычной строкой внутри Lua-скрипта, а библиотека сама превращает её в файл:

Lua:
local HTML = [==[
<html><head><meta charset="utf-8"> ... </head><body> ... </body></html>
]==]

local url = ul.create_page('mypage', HTML)   -- создаёт moonloader/resource/ul/mypage.html
ul.load_url(url)

  • Файл записывается в moonloader/resource/ul/<имя>.html автоматически при старте скрипта. Если содержимое не изменилось, диск не трогается.
  • Весь интерфейс лежит в одном .lua файле. Его удобно распространять, и ничего не нужно раскладывать по папкам.
  • Если в вашей странице нет своей функции lua(), библиотека сама добавит служебный JS (связь со Lua и отслеживание фокуса на полях ввода).
  • Хотите вообще без файла? Используйте ul.load_html(HTML). Но при загрузке из файла работают относительные пути к картинкам и шрифтам.
  • Сохраняйте скрипт в UTF-8: текст страницы пишется на диск как есть. Если скрипт в cp1251, передавайте u8(HTML).
  • Если страница лежит в отдельном файле, загружайте её так: ul.load_url('file:///moonloader/resource/мои_файлы/page.html'). Путь считается от папки GTA.



Быстрый старт

Минимальный скрипт: окно с кнопкой, по нажатию в чат приходит сообщение.

Lua:
script_name('ul_hello')

local ul = require 'ultralight'
local encoding = require 'encoding'
encoding.default = 'CP1251'
local u8 = encoding.UTF8

local HTML = [==[
<html><head><meta charset="utf-8"><style>
  body { margin:0; background:transparent; font-family:Arial; color:#fff; }
  .card { margin:8px; padding:16px; border-radius:12px; background:rgba(20,24,32,.9); }
  button { padding:8px 14px; border:0; border-radius:6px; background:#3d7bdc; color:#fff; cursor:pointer; }
</style></head><body>
  <div class="card">
    <h3 id="title">Привет из HTML</h3>
    <button onclick="lua('hello', { text: 'Кнопку нажали' })">Нажми</button>
  </div>
  <script>function setTitle(s){ document.getElementById('title').textContent = s; }</script>
</body></html>
]==]

local shown = true

function main()
  while not isSampAvailable() do wait(100) end

  ul.init {
    x = 100, y = 100, w = 400, h = 200,
    draggable = true,                              -- окно можно двигать мышью
    cursor = true,                                 -- библиотека сама включает курсор
  }
  ul.load_url((ul.create_page('hello', HTML)))

  ul.on('hello', function(d)                       -- событие из страницы
    sampAddChatMessage('[UI] ' .. u8:decode(d.text), -1)
  end)

  sampRegisterChatCommand('hello', function()
    shown = not shown
    ul.set_visible(shown)                          -- курсор включится и выключится сам
  end)

  while true do
    wait(0)
    ul.process()                                   -- ОБЯЗАТЕЛЬНО каждый кадр
  end
end

function onWindowMessage(msg, wparam, lparam)      -- ввод мыши и клавиатуры в окно
  if ul.window_message(msg, wparam, lparam) then
    consumeWindowMessage(true, false)
  end
end

function onScriptTerminate(s)
  if s == thisScript() then ul.shutdown() end
end

Функцию lua() в странице объявлять не нужно: библиотека добавляет её сама (если вы не объявили свою). Курсор в примере включается флагом cursor = true, а окно двигается за любое место без кнопок.



Флаги окна: перемещение, размер, курсор

Всё настраивается флагами прямо в ul.init{...}, дополнительного кода не нужно:

Lua:
ul.init {
  x = 100, y = 100, w = 520, h = 400,

  draggable     = true,    -- окно можно двигать мышью                (по умолчанию false)
  resizable     = true,    -- размер меняется за край или угол        (по умолчанию false)
  cursor        = true,    -- курсор включается вместе с окном        (по умолчанию false)
  lock_controls = true,    -- при cursor=true блокировать управление  (по умолчанию true)

  min_w = 300, min_h = 200,    -- границы размера при изменении мышью (по умолчанию 100 x 60)
  max_w = 1280, max_h = 720,   --                                      (по умолчанию 1920 x 1080)
  resize_border = 8,           -- толщина зоны захвата по краям, px   (по умолчанию 8)
}

  • Окно тащится левой кнопкой мыши за любое место, где нет кнопок и полей ввода. Нажатие на кнопку, ссылку, <input>, <textarea> или <select> окно не двигает, они работают как обычно.
  • Чтобы тащить только за шапку, пометьте её атрибутом data-ul-drag. Как только в странице есть хотя бы один такой элемент, остальные места окна перестают его двигать.
  • Чтобы исключить область (список с прокруткой, ползунок, карту), пометьте её атрибутом data-ul-nodrag. Он действует и на вложенные элементы.
  • Окно не выходит за границы экрана.
  • Служебный JS для перетаскивания библиотека добавляет в страницу сама при ul.create_page. Если вы загружаете страницу другим способом, вызовите lua('__drag') на mousedown вашей «ручки».
HTML:
<div class="card">
  <div class="head" data-ul-drag>               <!-- тащим только за эту полосу -->
    Заголовок окна
    <button onclick="lua('close')">✕</button>   <!-- кнопка в шапке работает как кнопка -->
  </div>
  <div class="body" data-ul-nodrag> ... </div>  <!-- здесь окно не двигается -->
</div>

  • Размер меняется, если взяться мышью за любой край или угол окна (зона захвата по краям шириной resize_border, 8 пикселей по умолчанию) и потянуть. Левый и верхний края тоже работают, окно при этом сдвигается.
  • Размер ограничен значениями min_w, min_h, max_w, max_h, а также границами экрана. Максимум самой библиотеки: 1920×1080.
  • Нажатия в зоне захвата идут библиотеке, а не странице. Не размещайте кнопки вплотную к краю окна (оставьте отступ больше resize_border).
  • Форма курсора не меняется (из Lua её не поменять), поэтому нарисуйте в углу окна небольшую «ручку» (◢), чтобы пользователь понимал, что размер можно менять.
  • Страница должна подстраиваться под размер: используйте проценты, flex, grid, vw/vh, а не фиксированные пиксели. Во время изменения размер применяется не чаще ~30 раз в секунду.
HTML:
<style>
  html, body { height:100%; margin:0; }
  .card { height:100%; display:flex; flex-direction:column; box-sizing:border-box; }
  .body { flex:1; overflow:auto; }              /* растёт вместе с окном */
  .grip { position:absolute; right:10px; bottom:6px; opacity:.4; }
</style>
<div class="card"><div class="body">...</div><div class="grip">◢</div></div>

Флаг cursor = true поручает показ курсора библиотеке: он включается при показе окна и выключается при скрытии окна или ul.shutdown(). Под капотом это функция showCursor(show, lockControls):
  • cursor = true, lock_controls = true → showCursor(true, true): курсор виден, управление персонажем и камерой заблокировано. Для меню, инвентарей, диалогов.
  • cursor = true, lock_controls = false → showCursor(true, false): курсор виден, персонаж и камера управляются. Для кликабельных HUD, когда нужно ходить.
  • Окно скрыто или скрипт завершён → showCursor(false, false): курсор спрятан, управление возвращено.
  • cursor = false (по умолчанию): библиотека курсор не трогает, вы вызываете showCursor(...) сами, как раньше.

Текущие настройки лежат в ul.opts, их можно менять в любой момент:
Lua:
ul.opts.draggable = false            -- запретить двигать (например, пока открыт диалог)
ul.opts.resizable = true
ul.opts.lock_controls = false        -- применится при следующем показе окна

ul.set_rect(200, 100, 600, 400)      -- переместить и изменить размер из кода
print(ul.opts.x, ul.opts.y, ul.opts.w, ul.opts.h)   -- текущие положение и размер
Положение и размер после перетаскивания и изменения размера хранятся в ul.opts, так что их можно сохранить в конфиг и подставить в ul.init при следующем запуске.



Справочник по Lua-функциям

  • ul.init{ x, y, w, h, ...флаги }: создаёт окно (положение и размер в пикселях, не больше 1920×1080). Флаги draggable, resizable, cursor, lock_controls, min_w, min_h, max_w, max_h, resize_border описаны в разделе «Флаги окна». Повторный вызов (например, после перезагрузки скрипта) просто переиспользует уже запущенный хелпер.
  • ul.status(): состояние хелпера: -1 упал или не запустился, 0 не создан, 1 запускается, 2 готов.
  • ul.set_rect(x, y, w, h): переместить и/или изменить размер окна (значения сохраняются в ul.opts).
  • ul.set_visible(true/false): показать или скрыть. Скрытое окно не рисуется и не нагружает процессор (страница и таймеры при этом продолжают работать). При cursor = true заодно включает или выключает курсор.
  • ul.shutdown(): спрятать окно, очистить страницу и (при cursor = true) выключить курсор. Вызывайте в onScriptTerminate.

  • ul.create_page(name, html): создать HTML-файл из Lua-строки в moonloader/resource/ul/<name>.html. Возвращает url, путь_на_диске. Имя очищается от лишних символов (остаются буквы, цифры, _ и -).
  • ul.load_url(url): загрузить страницу по адресу. Локальные файлы указываются относительно папки GTA: file:///moonloader/resource/ul/mypage.html.
  • ul.load_html(html): загрузить HTML прямо из строки, без файла.
  • ul.eval(js): выполнить произвольный JS-код в странице.
При каждой загрузке страницы кэш ul.push сбрасывается автоматически, значения придут заново.

  • ul.call('имя', арг1, арг2, ...): вызвать JS-функцию. Аргументы передаются как JSON: числа, строки, булевы значения, таблицы.
  • ul.push(таблица [, 'имя_js_функции']): отправить только изменившиеся значения. По умолчанию вызывается JS-функция update(diff). Таблицы-значения (списки, вложенные объекты) сравниваются по содержимому. Возвращает true, если что-то было отправлено. Вызывайте в цикле сколько угодно: лишний трафик не создаётся.
  • ul.reset_cache(): заставить следующий ul.push отправить всё заново (например, при повторном открытии окна).

  • ul.on('событие', function(данные) ... end): зарегистрировать обработчик. Данные приходят уже в виде Lua-значения (число, строка, таблица).
  • ul.process(): забирает накопившиеся события и вызывает обработчики. Вызывайте каждый кадр в цикле main(). Ошибки в обработчиках перехватываются и печатаются в консоль, скрипт не падает.
  • Служебные события: 'ready' (страница загрузилась, можно подписаться и отправить стартовые данные).

  • ul.window_message(msg, wparam, lparam): вызывайте из onWindowMessage. Передаёт в страницу движение мыши, нажатия кнопок (левая, средняя, правая), колесо прокрутки, клавиши и символы. Возвращает true, если сообщение нужно «съесть» (consumeWindowMessage(true, false)), чтобы игра не реагировала на клик по окну.
  • Мышь работает, пока курсор над окном. Курсор включайте флагом cursor = true в ul.init (см. «Флаги окна») или вручную функцией showCursor(show, lockControls):
    • showCursor(true, true): показывает курсор и блокирует управление персонажем и камерой. Подходит для открытых меню, инвентарей, диалоговых окон.
    • showCursor(true, false): показывает курсор, но управление персонажем и камерой остаётся активным. Удобно для кликабельных элементов во время ходьбы и специальных HUD.
    • showCursor(false, false) (или showCursor(false)): прячет курсор и возвращает стандартное управление игрой.
  • Клавиатура уходит в страницу только пока сфокусировано поле ввода (<input> или <textarea>). Это определяется автоматически.
  • Русский ввод: символы приходят в ANSI и конвертируются в UTF-8 автоматически.

  • ul.ready: true после ul.init
  • ul.visible: показано ли окно
  • ul.keyboard: true, пока фокус в поле ввода (клавиатура направлена в окно)
  • ul.opts: текущие настройки окна (x, y, w, h и все флаги), их можно менять на лету



Что должно быть на стороне страницы (JS)

HTML:
<script>
  // Страница → Lua. Эту функцию библиотека добавляет сама, если вы не определили свою.
  function lua(name, data) {
    console.log('@lua ' + JSON.stringify({ e: name, d: data === undefined ? null : data }));
  }

  // Lua → страница: сюда приходит ul.push{...} (только изменившиеся поля)
  function update(s) {
    if (s.hp !== undefined) document.getElementById('hp').style.width = s.hp + '%';
  }
</script>

Служебные события, которые страница отправляет сама (если вы пользуетесь автодобавлением):
  • __ready: страница загрузилась (сбрасывает кэш push и вызывает Lua-обработчик ready)
  • __kbd: фокус вошёл в поле ввода или вышел (включает и выключает передачу клавиатуры)
  • __drag: нажали на «ручку» перетаскивания (работает при draggable = true; JS для этого библиотека добавляет сама)

Если вы пишете свою функцию lua(), эти события нужно отправлять самому: на DOMContentLoaded вызвать lua('__ready'), а на focusin и focusout полей ввода вызывать lua('__kbd', true/false).



Примеры применения

Lua:
-- в main(), в цикле:
local t = 0
while true do
  wait(0)
  ul.process()
  if os.clock() - t > 0.1 then          -- 10 раз в секунду достаточно
    t = os.clock()
    ul.push {                            -- уйдут только изменившиеся значения
      hp    = getCharHealth(PLAYER_PED),
      armor = getCharArmour(PLAYER_PED),
    }
  end
end
HTML:
<div class="bar"><div id="hp"></div></div>
<div class="bar"><div id="armor"></div></div>
<script>
  function update(s) {
    if (s.hp    !== undefined) document.getElementById('hp').style.width    = Math.min(100, s.hp) + '%';
    if (s.armor !== undefined) document.getElementById('armor').style.width = Math.min(100, s.armor) + '%';
  }
</script>

HTML:
<button onclick="lua('menu', {id: 1})">Начать работу</button>
<button onclick="lua('menu', {id: 2})">Купить</button>
Lua:
ul.on('menu', function(d)
  if d.id == 1 then sampSendChat('/work') end
  if d.id == 2 then sampSendChat('/buy') end
end)

HTML:
<input id="msg" placeholder="Введите текст">
<button onclick="lua('send', { text: document.getElementById('msg').value })">Отправить</button>
Lua:
ul.on('send', function(d)
  sampAddChatMessage('Вы ввели: ' .. u8:decode(d.text), -1)   -- из UTF-8 в cp1251
end)
Клавиатура включается автоматически, пока курсор ввода в поле. Backspace, Enter, стрелки и русский ввод работают.

Lua:
ul.push { items = {
  { name = 'Аптечка',  count = 3 },
  { name = 'Отмычка',  count = 1 },
}}
HTML:
<div id="list"></div>
<script>
  function update(s) {
    if (!s.items) return;
    var box = document.getElementById('list');
    box.innerHTML = '';
    s.items.forEach(function (it) {
      var row = document.createElement('div');
      row.textContent = it.name + ' × ' + it.count;
      box.appendChild(row);
    });
  }
</script>
Списки приходят целиком, но только когда содержимое изменилось.

Lua:
ul.call('notify', u8('Вы получили награду'), 'ok')
HTML:
<script>
  function notify(text, kind) {
    var el = document.createElement('div');
    el.className = 'toast ' + kind;
    el.textContent = text;
    document.body.appendChild(el);
    setTimeout(function () { el.remove(); }, 3000);   // исчезнет через 3 сек
  }
</script>
Анимации, плавные переходы и таймеры делаются средствами CSS и JS на стороне страницы, Lua для них не нужен.

Lua:
local MENU, SHOP = [==[ ...html меню... ]==], [==[ ...html магазина... ]==]

ul.load_url((ul.create_page('menu', MENU)))
ul.on('goto_shop', function() ul.load_url((ul.create_page('shop', SHOP))) end)
Обработчики ul.on остаются, кэш push сбрасывается автоматически при каждой загрузке. Либо делайте одну страницу с несколькими блоками и переключайте их из JS.

Lua:
local car = isCharInAnyCar(PLAYER_PED) and storeCarCharIsInNoSave(PLAYER_PED)
ul.push {
  inCar = car and true or false,
  speed = car and math.floor(getCarSpeed(car) * 3.6) or 0,   -- множитель подберите под свой сервер
}

Lua:
ul.init {
  x = 200, y = 120, w = 480, h = 360,
  draggable = true, resizable = true,
  min_w = 320, min_h = 220,
  cursor = true, lock_controls = false,   -- курсор виден, но ходить можно
}
ul.load_url((ul.create_page('panel', HTML)))
HTML:
<style>
  html, body { height:100%; margin:0; background:transparent; font-family:Arial; color:#fff; }
  .card { height:100%; box-sizing:border-box; display:flex; flex-direction:column;
          border-radius:12px; background:rgba(20,24,32,.92); overflow:hidden; }
  .head { padding:10px 14px; background:rgba(255,255,255,.06); font-weight:bold; }
  .body { flex:1; padding:14px; overflow:auto; }
  .grip { position:absolute; right:10px; bottom:6px; opacity:.4; }
</style>
<div class="card">
  <div class="head" data-ul-drag>Панель (тяните за заголовок)</div>
  <div class="body">Содержимое растягивается вместе с окном.</div>
  <div class="grip">◢</div>
</div>

Что ещё можно сделать:
  • Панели информации (статистика, задания, рейтинги)
  • Игровое меню, магазин, чекбоксы, переключатели, ползунки
  • Окна чата, уведомления, подсказки, мини-карта на CSS/SVG
  • Графики и диаграммы на SVG или Canvas
  • Любой интерфейс, который вы бы сделали на сайте



Готовый скрипт: /ui, информация о персонаже

В комплекте идёт ul_charinfo.lua, демонстрация всех возможностей. Команда /ui показывает и скрывает окно. Значения обновляются автоматически 10 раз в секунду (через ul.push). Окно можно двигать мышью, а курсор включает сама библиотека (флаги draggable = true, cursor = true).

Что показывает:
  • Игрок: ник, ID, цвет, деньги, score, розыск, пинг, скин, состояние (пешком, в машине, в воде, в воздухе), HP и броня
  • Позиция: координаты, угол, интерьер, район
  • Транспорт: модель, HP, скорость, двигатель, водитель или пассажир (пешком вместо этого показывается скорость движения)
  • Оружие: то, что в руках, и все слоты с патронами
  • Сервер: название, адрес, онлайн, FPS, игровое время и время ПК

Все игровые функции в скрипте обёрнуты в безопасный вызов: если в вашей сборке MoonLoader или SAMPFUNCS какой-то функции нет, соответствующее поле остаётся пустым, а окно продолжает работать. HTML окна лежит внутри самого .lua файла и создаётся скриптом.



Ограничения
  • ОДНО ОКНО НА ВСЮ СБОРКУ (может вскоре попробую пофиксить)!!! Несколько элементов интерфейса делайте внутри одной страницы.
  • Окно ловит мышь по всему своему прямоугольнику, в том числе по прозрачным местам. Для чисто декоративного HUD держите окно небольшим, либо не вызывайте ul.window_message, тогда клики пройдут в игру.
  • Максимальный размер окна: 1920×1080.
  • При resizable = true полоса по краям окна (resize_border, 8 px) принадлежит библиотеке: клики по ней в страницу не доходят. Форма курсора при наведении на край не меняется.
  • Рендер программный (CPU). WebGL недоступен, HTML5 видео и аудио экспериментальные.
  • Ultralight построен на WebKit, поэтому самые новые возможности JS и CSS могут не поддерживаться. Проверяйте на своей странице.
  • Рассчитано на GTA SA 1.0 US. Для другой версии игры нужно поменять адрес в asi/ultralight_lua.cpp (константа kGtaDevicePtr).
  • Библиотека не заменяет и не перехватывает встроенные CEF-интерфейсы лаунчеров. Это отдельный способ рисовать собственный интерфейс.

Производительность
  • Рендер идёт не чаще 60 раз в секунду и только пока окно показано.
  • Новый кадр передаётся в игру только если картинка изменилась.
  • Окно 520×640 передаёт около 1,3 МБ на кадр: на практике это незаметно.
  • Не вызывайте ul.call каждый кадр с одними и теми же данными: используйте ul.push, он отправляет только изменения.
  • Для данных из игры достаточно опроса 5-10 раз в секунду.



Если что-то не работает

  1. Выведите в консоль print(ul.status()): -1 хелпер не запустился (проверьте, что GTA/ultralight/ul_helper.exe и все DLL на месте и что DLL именно x64); 1 запускается; 2 работает.
  2. Лог Ultralight: GTA/ultralight/ultralight.log.
  3. Вызываете ли вы ul.process() в цикле и ul.set_visible(true)?
  4. Не за пределами ли экрана окно (проверьте x, y, w, h)?

Не найдена папка ресурсов. Скопируйте icudt67l.dat и cacert.pem из Ultralight SDK в GTA\ultralight\resources\. Хелпер при старте сам проверяет файл и показывает полный путь, который искал.

Текст страницы и данные для неё должны быть в UTF-8. Сохраняйте скрипт в UTF-8, а строки из игры (ник, название сервера) передавайте через u8(...). Строки, пришедшие из страницы, декодируйте через u8:decode(...) перед выводом в чат.

Проверьте, что скрипт вызывает ul.window_message из onWindowMessage и consumeWindowMessage(true, false), а курсор включён (флаг cursor = true в ul.init либо ваш вызов showCursor(true, true) или showCursor(true, false)).

  1. Включены ли флаги draggable = true и resizable = true в ul.init?
  2. Вызывается ли ul.process() каждый кадр? Перетаскивание начинается по событию из страницы, а его обрабатывает именно ul.process().
  3. Вызывается ли ul.window_message из onWindowMessage?
  4. Если в странице есть data-ul-drag, двигать можно только за эти элементы. Нажатие на кнопки, ссылки и поля ввода окно не двигает.
  5. Если страница загружена не через ul.create_page или содержит свой код с __drag, служебный JS не добавляется: вызовите lua('__drag') на mousedown сами.
  6. Для изменения размера берите окно ровно за край (в пределах resize_border). Форма курсора не меняется, это нормально.

Хелпер должен завершаться вместе с игрой. Если остался процесс, завершите его вручную в диспетчере задач и сообщите об этом в теме.



Важно знать
  • Библиотека запускает дополнительный процесс (ul_helper.exe) и использует ASI-плагин с хуком D3D9. На серверах с античитом или особыми правилами уточняйте, разрешено ли такое.
  • Ultralight бесплатен для некоммерческого использования. Условия лицензии смотрите на сайте и в поставке SDK.

Скачать
UltraLight-moonloader

пасиб за лайки. завтра пофишку мультиоконность и уберу копирование из окна.
 
Последнее редактирование:

#Northn

Известный
Всефорумный модератор
2,661
2,615
хорошачя работа, я когда-то года три назад садился реализовывать точь-в-точь то, что ты сделал, и так же через ультралайт. но мне под конец уже было впадлу и я забил, а так, там уже многое работало, включая биндинг к муну
разве что небольшая ремарка: видео воспроизводятся ужасно (проприетарных кодеков нет), а на ультралайт, кажется, забили
@RTD слыыыш смотри что тут
 

damirtakioma

Участник
Автор темы
54
28
хорошачя работа, я когда-то года три назад садился реализовывать точь-в-точь то, что ты сделал, и так же через ультралайт. но мне под конец уже было впадлу и я забил, а так, там уже многое работало, включая биндинг к муну
разве что небольшая ремарка: видео воспроизводятся ужасно (проприетарных кодеков нет), а на ультралайт, кажется, забили
@RTD слыыыш смотри что тут
ну, увы, исключительная проблема движка.
для условных худов, интерфейсов простеньких и тд в самый раз
у меня набросок масштабного фикса готов, там еще плюсом пару функций добавится, дома буду — реализую. ну а так, в целом с этим играться можно как угодно, это очень гибко. можно даже попробовать инжектнуть ультралайт и адекватно воспроизводить видосы. идеи что добавить есть — пишите
 
  • Нравится
Реакции: #Northn

MSIshka

Активный
134
40
Рассчитано на GTA SA 1.0 US. Для другой версии игры нужно поменять адрес в asi/ultralight_lua.cpp (константа kGtaDevicePtr).
Библиотека неплохая, но в следующий раз читай что тебе пишет нейронка, либо залей исходники на гитхаб.
 

Similar threads