Меняем игровую память через SCM (San Andreas)
1.
Всем известно, что San Andreas поддерживает массивы в SCM.
В общем виде массивы представлены так:
- Code:
(array name)(index name),(size)(type)
Имя массива и индекса - это переменные, содержащие значения, которые определяют место начала массива (array name) и смещение от этого начала (или номер ячейки в массиве) на определенное число байтов (index name).
Так вот, например, $array($index,11i) будет указывать на определенную ячейку памяти, которая рассчитывается следующим образом:
берется имя (подробнее в разделе DMA) переменной $array, к ней прибавляется значение переменной $index, умноженное на размер одной ячейки.
Например, для типа i (integer) размер одной ячейки равен 4 байтам.
- Code:
i,f : 4 байта
s : 8 байтов
v : 16 байтов
Таким образом, общая формула такова:
- Code:
cA = aN + (iN * t)
cA адрес ячейки
aN $array
iN значение $index
t размер ячейки для данного типа
2.
Значение cA (адрес ячейки) - это смещение в main.scm относительно его начала. Все глобальные переменные и глобальные массивы хранятся в теле main.scm в блоке, который идет в самом начале файла (так называемый первый сегмент). Если вы откроете main.scm в хекс-редакторе, вы увидите, что в начале идет очень много нулей. Вот здесь потом и прописываются значения переменных.
Допустим, aN равно $8, iN равно 10, а тип массива Integer, т.е. t равно 4. Тогда cA равно 48. Тогда $array($index,11i) прочитает 4 байта из первого сегмента, начиная с 48-го байта от начала.
3.
DMA. DMA расшифровывается как Direct Memory Access (прямой доступ к памяти). Относительно скриптинга в GTA, речь идет об использовании прямого доступа к конкретной ячейке памяти. Реализуется эта технология путем указания числового имени для глобальной переменной. Например, $100 всегда указывает на сотую ячейку памяти (или четырехсотый байт, т.к. одна ячейка равна 4 байтам) относительно начала первого сегмента SCM.
А вот куда будет указывать $var (например), вы заранее знать не можете, т.к. память для переменных с текстовыми именами распределяется уже компилятором.
Правда здесь есть два исключения:
- Вы можете указать компилятору конкретную ячейку памяти для глобальной переменной командой Alloc.
- Переменные из файла CustomVariables.ini всегда имеют опреденную ячейку.
Трансформируем наш пример в DMA:
- Code:
$9 = 1
$10($9,11i)
$10 это имя массива, которое определяет его позицию в первом сегменте (40-й байт). $9 равно 1 поэтому cA = 40+1*4 = 44. $10($9,11i) читает и пишет значения в диапазоне [44..47] байты SCM.
Домашнее задание: докажите, что если $9 равно 0, то $10($9,11i) тоже самое что $10.
4.
aDMA. aDMA расшифровывается как Advanced DMA или продвинутый доступ к памяти.
Технология DMA имеет три ограничения:
- нельзя использовать имена $0 и $1, так как они выходят за пределы первого сегмента SCM.
- использование DMA-переменных влияет на размер первого сегмента, а значит и всего файла. Например, если вы используете в скрипте переменную $10000, то размер первого сегмента увеличится до катастрофического значения в ~40 килобайт, даже если у вас во всем файле лишь десяток переменных.
- DMA использует шаг в 4 байта, и вы не имеете доступа к адресам, которые не делятся нацело на 4. Например, вы не можете прочитать [11..14] байты, а только [8..11] - $2 или [12..15] - $3.
aDMA лишен этих недостатков. Вы можете использовать любые значения, кроме отрицательных. Реализуется aDMA через тип данных &. Используется шаг в один байт
Так, переменная &11 будет читать [11..14] байты, &9999999 – [9999999.. 1000002] и т.д.
&0 прочитает первые 4 байта SCM, которые равны 2147xxxxxx
Домашнее задание: проверьте это
- Code:
054C: use_GXT_table 'POOL'
01E3: text_1number_styled 'NUM' &0 5000 ms 1 // ~1~
Самое важное, что aDMA не влияет на размер первого сегмента, и вы можете читать/менять значение за его пределами (например код второго сегмента - моделей).
Однако и DMA и aDMA ограничены в размере: максимальные значения для них равны $16383, &65536 соответственно. Поэтому доступный диапазон для таких переменных равен 65536.
5.
Возвращаемся к массивам. Как я уже говорил, для расчета конечной ячейки памяти к имени массива (оффсету) прибавляется содержимое индекса * 4. Теперь попробуем поразмыслить, ведь содержимое переменной не ограничено никакими лимитами (кроме естественно предела в 4,294,967,295 для всех 32-битных приложений, к коим относится и
GTA). А значит теоретически мы может получить доступ к любой единице памяти игры.
Так и есть.
- Code:
$index = 10 000 000
$10($index,1i) = 1
Этот код запишет 1 в cA = 40+10 000 000*4 = 40000040..3 байт.
Вроде все верно. Одна есть одна деталь: игровой движок рассчитывает конечный адрес cA, исходя из того, что адресация происходит внутри SCM, а значит это смещение будет не абсолютным, а относительным (от начала main.scm в памяти игры).
Так в чем проблема, спросите вы, нужно лишь учесть этот смещение и все. Да, так и есть.
Глобальный адрес main.scm в памяти игры равен: 0xA49960. Именно это значение прибавляется для получения адреса. Поэтому отняв его от значения iN и разделив число на 4 (смотри формулу) мы получим «магическое» значение iN, которое и будет потом, при обработке его игрой указывать на нужную нам ячейку.
Преобразуем формулу с учетом адреса SCM:
- Code:
cA = 0xA49960 + aN + (iN * t)
Из нее легко видно, что нужно сделать чтобы cA был равен нужному нам адресу.
Есть последняя деталь. Чтобы не возиться с учетом aN, мы устанавливаем его равным 0. Делается это при помощи aDMA, о чем я писал выше.
Конечный код преобразования глобального адреса в «магическое» число:
- Code:
:MemoryRead32
0@ -= 0xA49960
0@ /= 4
008B: 1@ = &0(0@,1i)
return
Эта функция возвращает в переменной 1@ значение памяти по адресу, переданному в 0@.
Например, есть адрес 0xB7CE50 – текущее количество денег игрока (другие адреса)
Прочитаем сколько денег на счету у игрока:
- Code:
…
0@ = 0xB7CE50
gosub @MemoryRead32
054C: use_GXT_table 'POOL'
01E3: text_1number_styled 'NUM' 1@ 5000 ms 1 // ~1~
...
:MemoryRead32
0@ -= 0xA49960
0@ /= 4
008B: 1@ = &0(0@,1i)
return
Код для записи в память разных значений вы можете прочитать в моем посте на GTAForums.com.
И напоследок.
Не все адреса одинаково полезны.. тьфу, доступны. При попытке изменить/прочитать некоторые из них вы можете получить ошибку с вылетом игры. Будьте осторожны.