ВНИМАНИЕ! Наша конференция посвящена космической тематике и компьютерным играм. Политические вопросы и происходящие в мире события в данный момент на нашем сайте не обсуждаются!
|
» X3TC Моддинг: obj-файлы и маленькие утилитки | страница 4 |
|
|
|
Канал X3: Terran Conflict »
Модовый и скриптовый отсек X3: Terran Conflict: «X3TC Моддинг: obj-файлы и маленькие утилитки» |
|
|
YOYOMAN
71 EGP
 Рейтинг канала: 2(21) Репутация: 1 Сообщения: 141 Откуда: Украина Зарегистрирован: 13.07.2010
 |
|
CheckerTwo : |
Эта функция может быть представлена на С-подобном языке примерно вот в таком виде:
|
честно говоря я не знаю языка С , но я както смог прописать новые типы врат в обж тоесть их тепреь не 25 а 33 и фсё прекрассно пашет но там просто копипаста меток и добавление цыферки нового типа. А то была байда что не ставило символы Сев. Юж. и т.д. перед словом "врата" вместо этого было "???"
Код: |
function GetRaceLicenseSubtype(int RaceId)
{
switch(RaceId) {
case 1: return 26; break;
case 2: return 27; break;
case 3: return 28; break;
case 4: return 29; break;
case 5: return 30; break;
case 6: return <пофиг какой номер>; break;
default: return -1;
};
} |
case 6: return <пофиг какой номер>; break;
Но как мне эту строчку написать в асемблере?? ПРосто настрочить новю метку "с головы"? день сижу на этой секцией .
А с вратами гораздо проце.
_________________ Unreal Engine 4 |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
YOYOMAN : |
case 6: return <пофиг какой номер>; break;
Но как мне эту строчку написать в асемблере?? ПРосто настрочить новю метку "с головы"? день сижу на этой секцией .
А с вратами гораздо проце.
|
Примерно так:
Код: |
GetRaceLicenseSubtype:
enter 1, 1
push SP[3] ; RaceId
xjump 6d, 1
.long $00 ; default
.long $01 ; 1: аргон
.long $02 ;
.long $03 ;
.long $04 ;
.long $05 ;
.long $06 ;
$00: pushb -1d
ret
$01: pushb 26d
ret
$02: pushb 27d
ret
$03: pushb 28d
ret
$04: pushb 29d
ret
$05: pushb 30d
ret
$06: pushb <новый_ид_1>
ret
|
|
|
|
YOYOMAN
71 EGP
 Рейтинг канала: 2(21) Репутация: 1 Сообщения: 141 Откуда: Украина Зарегистрирован: 13.07.2010
 |
|
CheckerTwo : |
Примерно так:
|
ЛОЛ!! Это просто переписать секцию вот так? но номер 06 это Ксены. АОГ = 17
_________________ Unreal Engine 4 |
|
|
Whiskas
137 EGP
  Рейтинг канала: 3(36) Репутация: 5 Сообщения: 139
Зарегистрирован: 28.04.2011
 |
|
CheckerTwo : |
но полагаю что на ассемблере.
|
Именно Например, топливо к кораблям. Как в GTA4. Сам я-то не могу писать на асме. А вот если бы были уже готовые функции, как с десантами(выдрал из time of the truth), то было б круто. Эти примеры, коды и готовые решения и имею ввиду.
CheckerTwo : |
Красным цветом - размер секции в байтах
|
Спасибо! А то я сам дуб дубом
CheckerTwo : |
Что-то я не пойму как ты чего правишь...
|
CheckerTwo : |
И все ж, ты про какой редактор? HEX что ли? Не пугай! Не уверен, что HEX-редактором можно что-то написать и не наделать массу ошибок.
|
Вот небольшой пример. Думаю он тут пригодится. Вставляем код в оригинальный x3story.obj.
Пример 1. Разбрасывание десанта по классам.
Cкрытый текст (кликните здесь для просмотра)
Операции проводим на x3story.obj из версии игры 3.1
Итак, сам код функции вставляется непосредственно перед словом SYMB, типа в конец. Длина "функции кол-ва десанта" 6 байт.
Допустим хочется, чтобы на M1 было 33 десанта, на M2 - 45, а на M7 - 70.
Т.е. нужно вставить три функции по 6 байт, итого 18.
Вставляем 18 байт перед словом SYMB.
Цитата: |
0018D300 01 83 01 83 6E 00 01 05 06 83 01 83 6E 00 03 05 | .ƒ.ƒn....ƒ.ƒn...
0018D310 21 83 6E 00 03 05 2D 83 6E 00 03 05 46 83 53 59 4D 42 00 00 | Eƒn...%ƒn... ƒSYMB..
|
Предпоследний байт в каждой функции - это и есть кол-во десов.
Дуем в начало обжа и находим
Цитата: |
00000000 53 54 4F 52 00 00 03 21 4D 53 FD 59 43 4F 44 45 | STOR...!MSýYCODE
00000010 00 18 D2 F8 00 6E 00 08 | ..Òø.n..
|
Мы добавили 18 байт, поэтому вычисляем "новое начало". (Hex)18D2F8+(Dec)18 = (Hex)18D2F8 + (Hex)12 = 18D30A
Вписываем, чтоб было так:
Цитата: |
00000000 53 54 4F 52 00 00 03 21 4D 53 FD 59 43 4F 44 45 | STOR...!MSýYCODE
00000010 00 18 D3 0A 00 6E 00 08 | ..Òø.n..
|
Теперь можно дизассемблить x3story и просмотреть в x3story.out в самом конце, что получилось.
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
;
; === Select ====================================================
; function Obj_4314.Select(arg1);
;
| Obj_4314.Select:
0018D2F0: 6E 0001 | 0 enter 1, 1
0018D2F3: 05 06 | 0 pushb 6
0018D2F5: 83 | 1 ret
0018D2F6: 01 | push 0
0018D2F7: 83 | ret
0018D2F8: 6E 0003 | enter 1, 3
0018D2FB: 05 21 | pushb 33d ; 21h
0018D2FD: 83 | ret
0018D2FE: 6E 0003 | enter 1, 3
0018D301: 05 2D | pushb 45d ; 2Dh
0018D303: 83 | ret
0018D304: 6E 0003 | enter 1, 3
0018D307: 05 46 | pushb 70d ; 46h
0018D309: 83 | ret
|
|
Десов нам пока это не дает. Все просто - у классов M1, M2, M7 нет функции добавления десов. В это можно убедится, просмотрев
SHIP_M1 = class(Obj_2019)
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
; SHIP_M1 = class(Obj_2019)
; variables
; public
; function Create(arg1, arg2, arg3, arg4, arg5);
; function ShipAttackedBy(arg1, arg2, arg3);
; function StartControlTask();
; function GetNewSquadLeader();
; function StartPatrolSquad(arg1);
; function CreatePlayerOwned(arg1, arg2, arg3, arg4, arg5);
; function AttackedByHandler(arg1, arg2);
; function GetAnswers(arg1, arg2, arg3, arg4);
; end;
|
|
SHIP_M2 = class(Obj_2019)
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
SHIP_M2 = class(Obj_2019)
; variables
; public
; function Create(arg1, arg2, arg3, arg4, arg5);
; function CreatePlayerOwned(arg1, arg2, arg3, arg4, arg5);
; function AttackedByHandler(arg1, arg2);
; function GetAnswers(arg1, arg2, arg3, arg4);
; function GetDockBaySize();
; end;
|
|
SHIP_M7 = class(Obj_2019)
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
; SHIP_M7 = class(Obj_2019)
; variables
; public
; function Create(arg1, arg2, arg3, arg4, arg5);
; function ShipAttackedBy(arg1, arg2, arg3);
; function StartControlTask();
; function GetNewSquadLeader();
; function StartPatrolSquad(arg1);
; function CreatePlayerOwned(arg1, arg2, arg3, arg4, arg5);
; function AttackedByHandler(arg1, arg2);
; function GetAnswers(arg1, arg2, arg3, arg4);
; end;
|
|
Почему нет? Потому что у всех этих классов кол-во десанта одинаково и разработчикам не было смысла делать эту функцию для каждого класса. Они сделали одну общую функцию десантов, которая находится здесь:
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
; type
; Obj_2019 = class(SHIP_CARRIER)
; variables
; public
; function GetMaxMarines();
; end;
|
|
Obj_2019 - это парент дя SHIP_M1,SHIP_M2 и SHIP_M7. Он - Босс и командует ими. У него есть ф-ия десов и она добавляет 20 десов всем, кто в его подчинении  Ненадолго...
Все, что осталось сделать - это добавить ф-ию GetMaxMarines каждому нашему классу. Разумеется, непосредственно в оригинальный обж(версии 3.1!!!!) и без полной перекомпиляции.
Для начала открываем x3story.sym и находим
07E5 000007E3 | 0 8 | SHIP_M1
07E5 - это наш класс, M1
000007E3 (или просто 07E3) - это парент его, Obj_2019
Цифра 8 - кол-во функций у SHIP_M1.
Теперь нужно найти всё это внутри обжа. Вбиваем в поиск (искать HEX-значения!) 07 E5 00 00 07 E3
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
0019D390 00 00 00 00 00 00 00 00 07 E5 00 00 07 E3 48 C4 .........å...ãHÄ
0019D3A0 72 D9 00 00 00 08 00 0C 8A 34 00 00 00 81 00 00 rÙ......Š4.....
0019D3B0 00 07 00 00 00 05 00 0C 8A B7 00 00 C9 CD 00 00 ........Š·..ÉÍ..
0019D3C0 00 0E 00 00 00 03 00 0C 8E A2 00 00 F8 B2 00 00 ........Ž¢..ø²..
0019D3D0 00 04 00 00 00 00 00 0C 8F 6C 00 00 F9 14 00 00 ........l..ù...
0019D3E0 00 0A 00 00 00 00 00 0C 91 8E 00 00 F9 03 00 00 ........‘Ž..ù...
0019D3F0 00 04 00 00 00 01 00 0C 91 B8 00 00 F9 26 00 00 ........‘¸..ù&..
0019D400 00 07 00 00 00 05 00 0C 91 E9 00 00 EE 64 00 00 ........‘é..îd..
0019D410 00 08 00 00 00 02 00 0C 94 D5 00 00 39 95 00 00 ........”Õ..9•..
0019D420 00 06 00 00 00 04 00 00 00 00 00 00 07 E6 00 00 .............æ..
0019D430 07 E3 48 C4 72 D9 00 00 .ãHÄrÙ..
|
|
08 - кол-во функций
Всё что серым, это уже идёт другой объект: 07E6 (SHIP_M2). Здесь главное уловить границы, дабы правильно вписать код.
Меняем 08 на 09. Мы же добавляем новую функцию.
Теперь нужно вставить вот такой код:
0018D2F80000A5CE0000000200000000
18D2F8 - указатель(адрес) на начало кода нашей функции. У нас она находится именно там.
00000A5CE - это указатель на название функции. Можно глянуть в x3story.str:
0000A5CE : GetMaxMarines.
А куда вставлять? Сразу после 04.
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
0019D400 07 E5 00 00 07 E3 48 C4 72 .å...ãHÄr
0019D410 D9 00 00 00 09 00 0C 8A 34 00 00 00 81 00 00 00 Ù......Š4......
0019D420 07 00 00 00 05 00 0C 8A B7 00 00 C9 CD 00 00 00 .......Š·..ÉÍ...
0019D430 0E 00 00 00 03 00 0C 8E A2 00 00 F8 B2 00 00 00 .......Ž¢..ø²...
0019D440 04 00 00 00 00 00 0C 8F 6C 00 00 F9 14 00 00 00 .......l..ù....
0019D450 0A 00 00 00 00 00 0C 91 8E 00 00 F9 03 00 00 00 .......‘Ž..ù....
0019D460 04 00 00 00 01 00 0C 91 B8 00 00 F9 26 00 00 00 .......‘¸..ù&...
0019D470 07 00 00 00 05 00 0C 91 E9 00 00 EE 64 00 00 00 .......‘é..îd...
0019D480 08 00 00 00 02 00 0C 94 D5 00 00 39 95 00 00 00 .......”Õ..9•...
0019D490 06 00 00 00 04 00 18 D2 F8 00 00 A5 CE 00 00 00 .......Òø..¥Î...
0019D4A0 02 00 00 00 00 00 00 00 00 00 00 07 E6 00 00 07 E3 ............æ
|
|
После 02 должно быть 10 байтов заполненных нулями. Лишь потом 07 E6 00 00 07 E3
Можно дизасмить. Открываем x3story.out и глядим в конец:
Cкрытый текст (кликните здесь для просмотра)
;
; === Select ====================================================
; function Obj_4314.Select(arg1);
;
| Obj_4314.Select:
0018D2F0: 6E 0001 | 0 enter 1, 1
0018D2F3: 05 06 | 0 pushb 6
0018D2F5: 83 | 1 ret
0018D2F6: 01 | push 0
0018D2F7: 83 | ret
;
; === GetMaxMarines =============================================
; function SHIP_M1.GetMaxMarines();
;
| SHIP_M1.GetMaxMarines:
0018D2F8: 6E 0003 | 0 enter 0, 3
0018D2FB: 05 21 | 0 pushb 33d ; 21h
0018D2FD: 83 | 1 ret
0018D2FE: 6E 0003 | enter 0, 3
0018D301: 05 2D | pushb 45d ; 2Dh
0018D303: 83 | ret
0018D304: 6E 0003 | enter 0, 3
0018D307: 05 46 | pushb 70d ; 46h
0018D309: 83 | ret
.end
|
Для M1 десантов 33
Теперь для M2.
1) Открываем x3story.sym и находим:
07E6 000007E3 | 0 5 | SHIP_M2
2) Вбиваем в поиск 07 E6 00 00 07 E3 (или тупо 07E6000007E3, смотря что за HEX-эдитор)
Цитата: |
0019D430 07 E6 00 00 .æ..
0019D440 07 E3 48 C4 72 D9 00 00 00 05 00 0C 95 95 00 00 .ãHÄrÙ......••..
0019D450 00 81 00 00 00 07 00 00 00 05 00 0C 96 0D 00 00 ...........–...
0019D460 F9 26 00 00 00 07 00 00 00 05 00 0C 96 3E 00 00 ù&..........–>..
0019D470 EE 64 00 00 00 08 00 00 00 02 00 0C 98 F6 00 00 îd..........˜ö..
0019D480 39 95 00 00 00 06 00 00 00 04 00 0C 99 B6 00 00 9•..........™¶..
0019D490 97 95 00 00 00 02 00 00 00 00 00 00 00 00 00 00 —•..............
0019D4A0 07 EA 00 00 07 F1 48 C4 72 D9 .ê...ñHÄrÙ
|
Теперь глядим в x3story.out. Функция добавления 45-ти десов для M2 начинается с адреса 0018D2FE.
Нужно вставить вот такой код: 18D2FE0000A5CE0000020000000000 после 020000000000. И изменить кол-во ф-ий с 05 на 06.
Должно быть строго так:
Цитата: |
0019D430 07 E6 00 00 .æ..
0019D440 07 E3 48 C4 72 D9 00 00 00 06 00 0C 95 95 00 00 .ãHÄrÙ......••..
0019D450 00 81 00 00 00 07 00 00 00 05 00 0C 96 0D 00 00 ...........–...
0019D460 F9 26 00 00 00 07 00 00 00 05 00 0C 96 3E 00 00 ù&..........–>..
0019D470 EE 64 00 00 00 08 00 00 00 02 00 0C 98 F6 00 00 îd..........˜ö..
0019D480 39 95 00 00 00 06 00 00 00 04 00 0C 99 B6 00 00 9•..........™¶..
0019D490 97 95 00 00 00 02 00 00 00 00 00 18 D2 FE 00 00 —•..........Òþ..
0019D4A0 A5 CE 00 00 00 02 00 00 00 00 00 00 00 00 00 00 ¥Î..............
0019D4B0 07 EA 00 00 07 F1 48 C4 72 D9 .ê...ñHÄrÙ
|
Для M7. Было так:
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
0019D550 07 EB 00 00 07 E3 48 C4 72 D9 00 00 .ë...ãHÄrÙ..
0019D560 00 08 00 0C 9E 24 00 00 00 81 00 00 00 07 00 00 ....ž$.........
0019D570 00 05 00 0C 9E A7 00 00 C9 CD 00 00 00 0E 00 00 ....ž§..ÉÍ......
0019D580 00 03 00 0C A2 92 00 00 F8 B2 00 00 00 04 00 00 ....¢’..ø²......
0019D590 00 00 00 0C A3 5C 00 00 F9 14 00 00 00 0A 00 00 ....£\..ù.......
0019D5A0 00 00 00 0C A5 7E 00 00 F9 03 00 00 00 04 00 00 ....¥~..ù.......
0019D5B0 00 01 00 0C A5 A8 00 00 F9 26 00 00 00 07 00 00 ....¥¨..ù&......
0019D5C0 00 05 00 0C A5 D9 00 00 EE 64 00 00 00 08 00 00 ....¥Ù..îd......
0019D5D0 00 02 00 0C A8 C5 00 00 39 95 00 00 00 06 00 00 ....¨Å..9•......
0019D5E0 00 04 00 00 00 00 00 00 07 F2 00 00 08 55 .........ò...U
|
|
Должно быть так:
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
0019D550 07 E3 48 C4 72 D9 00 00 .ãHÄrÙ..
0019D560 00 09 00 0C 9E 24 00 00 00 81 00 00 00 07 00 00 ....ž$.........
0019D570 00 05 00 0C 9E A7 00 00 C9 CD 00 00 00 0E 00 00 ....ž§..ÉÍ......
0019D580 00 03 00 0C A2 92 00 00 F8 B2 00 00 00 04 00 00 ....¢’..ø²......
0019D590 00 00 00 0C A3 5C 00 00 F9 14 00 00 00 0A 00 00 ....£\..ù.......
0019D5A0 00 00 00 0C A5 7E 00 00 F9 03 00 00 00 04 00 00 ....¥~..ù.......
0019D5B0 00 01 00 0C A5 A8 00 00 F9 26 00 00 00 07 00 00 ....¥¨..ù&......
0019D5C0 00 05 00 0C A5 D9 00 00 EE 64 00 00 00 08 00 00 ....¥Ù..îd......
0019D5D0 00 02 00 0C A8 C5 00 00 39 95 00 00 00 06 00 00 ....¨Å..9•......
0019D5E0 00 04 00 18 D3 04 00 00 A5 CE 00 00 00 02 00 00 ....Ó...¥Î......
0019D5F0 00 00 00 00 00 00 00 00 07 F2 00 00 08 55 .........ò...U
|
|
Как видно, адреса к функциям должны быть друг под другом, также и ссылки на их названия. Всё должно быть красиво, как в таблице, поэтому-то для M1 добавляется 0018D2F80000A5CE0000000200000000 после 04, а для M2 18D2FE0000A5CE0000020000000000 после 020000000000
Не стал расписывать полностью. Вообщем сравнивайте и понимайте
Могут присутствовать ошибки! Поэтому ниже ссылка на рабочий пример
В качестве домашнего задания сделайте десантов на все классы(кроме истребителей) разное  Для практики и усвоения примера)
|
Не знаю как прикрепить, придется так:
Оригинальный x3story:
w w w.fayloobmennik.net/624563
Модифицированный пример:
w w w.fayloobmennik.net/624568
Удалять, сохраняться, загружаться можно без последствий. Гарантия 99.9%
Вообщем как-то так. И все hex-эдитором. Проще простого!
Зы
До strg не добрался пока. Тоже можно в чистый обж hex-эдитором добавлять. Например, для названия своей новой функции.
Кстати, что это за ошибка вылазит, когда ассемблишь?
Цитата: |
pass1:(x3story.asm:9) Warn: Obj-file 'x3story.obj' not found. Tables of symbols are not loaded.
|
Ну положил я рядом в папку x3story.obj, а он мне:
Cкрытый текст (кликните здесь для просмотра)
Цитата: |
pass1:(x3story.asm:239) Err: Redefined class 'DEBUG'.
pass1:(x3story.asm:241) Err: Redefined variable 'IsProfiling'.
pass1:(x3story.asm:242) Err: Redefined variable 'CheatActive'.
pass1:(x3story.asm:243) Err: Redefined variable 'var_203_2'.
pass1:(x3story.asm:244) Err: Redefined variable 'var_203_3'.
pass1:(x3story.asm:245) Err: Redefined variable 'NextCheatLaser'.
pass1:(x3story.asm:246) Err: Redefined variable 'NextCheatShield'.
pass1:(x3story.asm:247) Err: Redefined variable 'LastGate'.
pass1:(x3story.asm:248) Err: Redefined variable 'var_203_7'.
pass1:(x3story.asm:250) Err: Redefined procedure 'CheatEquip'.
pass1:(x3story.asm:251) Err: Redefined procedure 'MakeTestObj'.
|
и так каждую функцию
|
Последний раз редактировалось: Whiskas (18:39 17-05-2011), всего редактировалось 6 раз(а) |
|
|
YOYOMAN
71 EGP
 Рейтинг канала: 2(21) Репутация: 1 Сообщения: 141 Откуда: Украина Зарегистрирован: 13.07.2010
 |
|
Whiskas T-Mech : |
Ну положил я рядом в папку x3story.obj, а он мне: Cкрытый текст (кликните здесь для просмотра)
|
Офигеть просто!!!
А ты не ложи обж при компиляции в одну папку с асемблером и асмом НЕ обращяй внимания на ту хрень про загрузку таблиц.
_________________ Unreal Engine 4 |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
Whiskas T-Mech : |
Сам я-то не могу писать на асме. А вот если бы были уже готовые функции, как с десантами(выдрал из time of the truth), то было б круто. Эти примеры, коды и готовые решения и имею ввиду.
|
Для того, чтобы писать на ассемблере нужен только текстовый редактор и компилятор/декомпилятор.
Whiskas T-Mech : |
Вот небольшой пример. Думаю он тут пригодится. Вставляем код в оригинальный x3story.obj.
Пример 1. Разбрасывание десанта по классам.
...
Вообщем как-то так. И все hex-эдитором. Проще простого!
|
Да, то что ты делаешь HEX-редактором поистине круто. Но все ж это немного утомительно править в разных местах множество полей, следить за размерами секций и прочее. Кроме того так как у егософта секция STRG шифрована, добавлять свои функции несколько напряжно.
Компилятор может работать в 2х режимах.
Первый - патч существующего obj-файла. Выбор режима работы осуществляется директивой .loadobj "имя-обж-файла". Если директива .loadobj присутствует в компилируемом asm-файле, то выполняется загрузка указанного obj, разбираются все секции, формируются таблицы функций, переменных и строк во внутренних структурах компилятора. При этом девелоперу будут доступны все классы, функции и переменные, которые уже были в obj-файле и он может ссылаться на них, вызывать.
Во втором режиме - это когда в asm-файле отсутствует директива .loadobj - выполняется просто трансляция asm-файла в obj. Этот режим может быть использован для полной пересборки всего obj-а.
Whiskas T-Mech : |
Кстати, что это за ошибка вылазит, когда ассемблишь?
pass1:(x3story.asm:9) Warn: Obj-file 'x3story.obj' not found. Tables of symbols are not loaded.
|
Ты запустил компиляцию асм-файла, в котором есть директива .loadobj "x3story.obj", но рядом с этим файлом нет файла x3story.obj. Вот компилятор и ругнулся.
Whiskas T-Mech : |
Ну положил я рядом в папку x3story.obj, а он мне:
pass1:(x3story.asm:239) Err: Redefined class 'DEBUG'.
|
Теперь компилятор нашел и загрузил файл x3story.obj, загрузил все таблицы функций. А так как в асм-файле ты пытаешься компилировать уже то что есть и скомпилировано, компилятор ругается - думает, что пытаешься повторно определить уже откомпилированные функции.
Просто закоментируй строку в асм-файле .loadobj ... Компилятор будет собирать новый обж.
Давай, в качестве примера, разберем как можно написать твой патчик маринесов на ассемблере. Текстовым редактором (только не вордом), который может работать с простыми текстовыми файлами в кодировке win-1251 набираем вот такой текст:
Код: |
.title marine_tst
.radix 10d
; загружем оригинальный обж, который нужно подправить
.loadobj "x3story.obj"
.section code, con
; добавляем в описание класса SHIP_M1 новую функцию GetMaxMarines
.classdef SHIP_M1
.func GetMaxMarines;
.endclass SHIP_M1
; теперь пишем реализацию этой функции
; function SHIP_M1.GetMaxMarines();
SHIP_M1.GetMaxMarines:
enter 0, 1
pushb 33d ; 21h
ret
; ну и так далее для всех других классов
.classdef SHIP_M2
.func GetMaxMarines;
.endclass SHIP_M2
; function SHIP_M2.GetMaxMarines();
SHIP_M2.GetMaxMarines:
enter 0, 1
pushb 45d ; 2Dh
ret
.classdef SHIP_M7
.func GetMaxMarines;
.endclass SHIP_M7
; function SHIP_M7.GetMaxMarines();
SHIP_M7.GetMaxMarines:
enter 0, 1
pushb 70d ; 46h
ret
.end
|
Сохраняем его в отдельную папочку с именем marines.asm. Копируем в эту папочку оригинальный x3story.obj из версии 3.1. Копируем туда же xa_asm.exe и xa_asm.ini
Запускаем:
C:\test\xa_asm.exe marines.asm
После компиляции в этой же папочке появятся файлы marines.lst, marines.cod
В файле marines.lst можно посмотреть ошибки где и какие.
Файл marines.cod - это полученный OBJ-файл, который содержит все наши изменения.
|
|
|
Whiskas
137 EGP
  Рейтинг канала: 3(36) Репутация: 5 Сообщения: 139
Зарегистрирован: 28.04.2011
 |
|
CheckerTwo : |
Для того, чтобы писать на ассемблере нужен только текстовый редактор и компилятор/декомпилятор.
|
А ещё мозги и знание самого ассемблера, которых у меня нет...)
CheckerTwo : |
Кроме того так как у егософта секция STRG шифрована, добавлять свои функции несколько напряжно.
|
Я и не знал, что так можно компилятором патчить . Это реально в 1000 раз быстрее, правда вот меняется эта фигня из 4-ых байт целыми блоками и придется править обратно, как в оригинальном. По поводу strg, то такими патчами теперь все намного проще. Достаточно назвать функцию как нужно, например, GetMaximumMoneyAndFiftyBMWs и набор из 27+1 зашифрованных символов будут ждать тебя в конце обжа. Осталось тока выдрать
________________________________
Заметил, что плохиш компилятор изменяет размер секции PTCH c
Цитата: |
001AF5F0 50 54 43 48 00 02 48 14 00 00 00 15 48 C4 72 D9 PTCH..H.....HÄrÙ
|
на
Цитата: |
001B1B30 50 54 43 48 00 02 48 1C 00 00 00 15 48 PTCH..H.....H
001B1B40 C4 72 D9 ÄrÙ
|
Оригинальный obj из 13.cat патчил.
Теперь понятно почему в x3story из Time of Truth тож размер 02 48 1C. Точно так же и пропатчили оригинальный, только я тут ручками и с напильником все сижу
Спасибо за пример! Будем юзать патчи
Cкрытый текст (кликните здесь для просмотра)
Гм... в примере-то ошибка выходит. Если функция начинается с 6E0003, то должно быть так 0018D2F80000A5CE0000000300000000.
Если с 6E0002, то 0018D2F80000A5CE0000000200000000
|
Cкрытый текст (кликните здесь для просмотра)
CheckerTwo : |
В твоем патче <максимально_доступный_стек> был равен 3. В принципе не страшно, движек игры с этим справляется. Я поставил 1, потому что больший стек этой функции не нужен. Об этом речь?
|
Не совсем. Просто если ставить в коде к примеру enter 0, 12d(6E00 0C), то в обже, в ссылке на новую ф-ию getmaxmarines и должно быть DecToHex 12 = 0C. А я в примере ставил enter 0, 3(6E00 03), а сам в обж записывал 02. (хотя должен был именно 03). Не смертельная ошибка, конечно, и все же
Цитата: |
0019D550 07 EB 00 00 07 E3 4D D4 38 F1 00 00 .ë...ãMÔ8ñ..
0019D560 00 09 00 0C 9E 24 00 00 00 81 00 00 00 07 00 00 ....ž$.........
0019D570 00 05 00 0C 9E A7 00 00 C9 CD 00 00 00 0E 00 00 ....ž§..ÉÍ......
0019D580 00 03 00 0C A2 92 00 00 F8 B2 00 00 00 04 00 00 ....¢’..ø²......
0019D590 00 00 00 0C A3 5C 00 00 F9 14 00 00 00 0A 00 00 ....£\..ù.......
0019D5A0 00 00 00 0C A5 7E 00 00 F9 03 00 00 00 04 00 00 ....¥~..ù.......
0019D5B0 00 01 00 0C A5 A8 00 00 F9 26 00 00 00 07 00 00 ....¥¨..ù&......
0019D5C0 00 05 00 0C A5 D9 00 00 EE 64 00 00 00 08 00 00 ....¥Ù..îd......
0019D5D0 00 02 00 0C A8 C5 00 00 39 95 00 00 00 06 00 00 ....¨Å..9•......
0019D5E0 00 04 00 18 D3 04 00 00 A5 CE 00 00 00 0C 00 00 ....Ó...¹»......
0019D5F0 00 00 00 00 00 00 00 00 ........
|
|
Последний раз редактировалось: Whiskas (20:53 18-05-2011), всего редактировалось 2 раз(а) |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
Whiskas T-Mech : |
Заметил, что плохиш компилятор изменяет размер секции PTCH c
Цитата: |
001AF5F0 50 54 43 48 00 02 48 14 00 00 00 15 48 C4 72 D9 PTCH..H.....HÄrÙ
|
на
Цитата: |
001B1B30 50 54 43 48 00 02 48 1C 00 00 00 15 48 PTCH..H.....H
001B1B40 C4 72 D9 ÄrÙ
|
|
Поправил в новой версии. Действительно косяк. Спасибо.
Whiskas T-Mech : |
Гм... в примере-то ошибка выходит. Если функция начинается с 6E0003, то должно быть так 0018D2F80000A5CE0000000300000000.
Если с 6E0002, то 0018D2F80000A5CE0000000200000000
|
Если правильно тебя понял, то первая команда любой функции - enter <число_аргументов>, <максимально_доступный_стек>
В твоем патче <максимально_доступный_стек> был равен 3. В принципе не страшно, движек игры с этим справляется. Я поставил 1, потому что больший стек этой функции не нужен. Об этом речь?
-------------
Пока проверял и разбирал пример Whiskas T-Mech, обнаружил еще одну небольшую нехорошесть, мелочь но кто-нибудь да налетит. .
В первом посте подправленные версии компилятора/декомпилятора.
Ассемблер, версия 4.10b:
- исправлена ошибка в установке размера секции PTCH (спасибо Whiskas T-Mech)
- исправлена ошибка в определении класса для новой функции
- подправлена работа директива .settime (спасибо YOYOMAN)
Дизассемблер, версия 4.13b
- подправлены коментарии для xjump (спасибо vivere)
|
|
|
YOYOMAN
71 EGP
 Рейтинг канала: 2(21) Репутация: 1 Сообщения: 141 Откуда: Украина Зарегистрирован: 13.07.2010
 |
|
CheckerTwo : |
Примерно так:
|
Кстати, нифига это код не пашет. Ассемблер ругается на эти переменные вида $XX и не хочет компилить. Да ну ладно. Буду чё нибуть другое разбирать. Интересно капец.
Спасибо за обновлени утилиток.
_________________ Unreal Engine 4
Последний раз редактировалось: YOYOMAN (11:29 19-05-2011), всего редактировалось 1 раз |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
YOYOMAN : |
CheckerTwo : |
Примерно так:
|
Кстати, нифига это код не пашет. Ассемблер ругается на эти переменные вида $XX и не хочет компилить. Да ну ладно. Буду чё нибуть другое разбирать. Интересно капец.
Спасибо за обновлени утилиток.
|
У меня все скомпилилось
Cкрытый текст (кликните здесь для просмотра)
Код: |
1 .title Example 1
2 .radix 10d
3 .section code, con
4
5 .func GetRaceLicenseSubtype
6
7 00000001: GetRaceLicenseSubtype:
8 00000001: 6E 0001 enter 1, 1
9 00000004: 0D 0004 push SP[3] ; RaceId
10 00000007: 78 0006 00000001 xjump 6d, 1
11 0000000E: 0000002A .long $00 ; default
12 00000012: 0000002D .long $01 ; 1: рЁуюэ
13 00000016: 00000030 .long $02 ;
14 0000001A: 00000033 .long $03 ;
15 0000001E: 00000036 .long $04 ;
16 00000022: 00000039 .long $05 ;
17 00000026: 0000003C .long $06 ;
18
19 0000002A: 05 FF $00: pushb -1d
20 0000002C: 83 ret
21
22 0000002D: 05 1A $01: pushb 26d
23 0000002F: 83 ret
24
25 00000030: 05 1B $02: pushb 27d
26 00000032: 83 ret
27
28 00000033: 05 1C $03: pushb 28d
29 00000035: 83 ret
30
31 00000036: 05 1D $04: pushb 29d
32 00000038: 83 ret
33
34 00000039: 05 1E $05: pushb 30d
35 0000003B: 83 ret
36
37 0000003C: 05 01 $06: pushb 1
38 0000003E: 83 ret
39 0000003F: .end
Errors : 0 Warnings ; 0 Notes : 0
|
|
|
|
|
YOYOMAN
71 EGP
 Рейтинг канала: 2(21) Репутация: 1 Сообщения: 141 Откуда: Украина Зарегистрирован: 13.07.2010
 |
|
CheckerTwo : |
У меня все скомпилилось
|
Значит я чего то не догнал. Огромное преогромное спасибо!!! Буду пробовать.
_________________ Unreal Engine 4 |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
Уж и не знаю, хватит ли меня до конца
Итак, коротенькое описание (Введение)
Виртуальная машина Х3 (ВМ Х3).
ВМ Х3 - классическая стековая машина с совмещенным стеком данных и возвратов. То есть, все команды, выполняющие операции над данными, команды вызова функций и возвраты из них используют единый стек.
Стек - вид памяти, который реализует стек с процедурой LIFO (последним зашел - первым вышел). Стек допускает две операции: push и pop. Операция push выделяет новую ячейку на вершине стека и записывает в неё значение. Операция pop - освобождает ячейку. Для работы со стеком используется так называемый указатель стека SP (stack pointer).
Кроме того ВМ Х3 содержит команды для доступа (чтение/запись) к отдельным выделенным ячейкам памяти вне стека данных.
Арифметический блок ВМ осуществляет арифметико-логические операции над данными, хранящимися в стеке данных. Арифметический блок поддерживает следующий набор операций: пересылки, сложение, вычитание, логические функции, тестирование и сравнение. Арифметика может быть целочисленной или плавающей.
Программа ВМ Х3 представляет собой последовательность команд (инструкций). Эти команды "объясняют" ВМ, что она должна делать, точно описывая последовательность действий.
Реальная программа, которую выполняет ВМ Х3, это последовательность единиц и нулей. Эту строку бит принято называть машинным языком. Машинный язык - это тот язык, который машина понимает. ВМ извлекает команды машинного языка из памяти и затем выполняет команду.
Однако машинный язык мало о чем говорит людям. Если вы хотите сложить два числа 1+2 в ВМ Х3, последовательность команда будет выглядеть таким образом:
00000010 00000011 1000110
Эти три байта точно указывают ВМ, какие операции необходимо выполнить, чтобы получить результат. Если вы являетесь компьютером или ВМ, машинный язык - это действительно прекрасно, но он труден для программистов - людей. К счастью, существует более простой способ программирования. Этим методом, более близким людям, чем машинам, является программирование на языке ассемблера.
Чтобы лучше понять разницу, давайте взглянем на пример еще раз. Ассемблерные команды для сложения двух чисел 1+2 просты:
push 1
push 2
add sp[0], sp[1]
Ассемблер превращает эти предложения в бинарную форму, которую мы видели выше. Компьютер сам управляется с проблемой превращения файла с понятным человеку текстом в программу на машинном языке, которую могла бы выполнить ВМ Х3. Обычно каждое утверждение языка ассемблера генерирует одну команду машинного языка. В некоторых случаях это не так, потому что существуют команды, которые не являются частью выполняемой программы, а предназначены для ассемблера. Они описывают действия ассемблера, который должен выполнять их во время компилирования. Пример директивы ассемблера (предназначенной только для него команды) .radix 10d. Эта инструкция задает систему счисления и сообщает ассемблеру, что все числа в программе, с опущенными квалификаторами, будут рассматриваться как десятичные. Эта инструкция имеет смысл только для ассемблера. В ВМ нет команд, которые могли бы выполнить эту операцию.
Ассемблер Х3ТС
Прежде чем двигаться дальше, обсудим синтаксис команд языка ассемблера. Мы должны выделить основные компоненты языка ассемблера, чтобы можно было затем обозначать эти компоненты с помощью стандартных терминов.
Команда языка ассемблера состоит из четырех частей.
Код: |
lab1: popx 10d ; вытолкнуть из стека 10 ячеек
Метка ОпКод Операнды Комментарии
|
Единственная обязательная часть команды языка ассемблера - ОпКод (сокращение от ОПерационный КОД), или код операции.
Поле операндов содержит дополнительную информацию о команде. Оно определяется кодом операции. Каждому коду операции должно соответствовать определенное число операндов. Для команды popx требуется один операнд, для некоторых команд, например, команды pop, операнды вообще не нужны.
Метка и комментарии необязательны в команде. Поле метки позволяет обозначить какое-либо конкретное место в памяти компьютера. Говоря технически, поле метки содержит символический указатель расположения команды. Если мы хотим обратиться к этой команде позднее, то мы делаем это через символьное имя метки и нам не требуется указывать абсолютное расположение данной инструкции в памяти ВМ. Использование меток - одна из причин предпочтительности языка ассемблера перед машинным языком. Превращением же символических имен в реальные адреса занимается ассемблер.
Поле комментариев служит для удобства программиста. Программист может использовать это поле для сообщения дополнительной информации о команде. Комментарий не обязательно жестко связан с командой. Вы можете отвести под комментарий целую строку, поставив в ее начале символ ";". Это позволяет программисту включить в текст программы блок собственной информации, к примеру, описание используемого алгоритма. У каждого есть собственное представление о том, как следует комментировать программы.
Рассмотрим теперь работу ассемблера в целом. Детали будут обсуждены позднее. Ассемблер читает программу, написанную на языке ассемблера, из текстового файла и превращает ее в бинарный файл, содержащий инструкции машинного языка ВМ Х3. Файл, который содержит программу на языке ассемблера, называют исходным файлом. Формируемый ассемблером файл является собственно машинным языком, готовым к исполнению ВМ Х3.
Исполняемый файл для ВМ Х3 x3story.obj, как и другие obj, лежащие в папочке 'X3TC\L', представляют собой некий контейнер, содержащий несколько секций. Основная часть - это секция исполняемого кода - CODE, содержащая инструкции ВМ Х3. Далее идут секции SYMB, VARS, CLAS, PTCH и STRG. Секция SYMB содержит описания глобальных функций, VARS - глобальных переменных, CLAS - описание классовых функций и переменных, PTCH - таблицы замещения для системы патчей, STRG - список строк. Для программиста, разрабатывающего программу для ВМ Х3 ассемблер предоставляет полный доступ лишь к секции CODE и STRG. Информация в секциях SYMB, VARS, CLAS модифицируется ассемблером автоматически. Это сделано по той причине, что структуры этих секций достаточно сложны, а малейшая некорректная правка может привести к нестабильности ВМ и падению. В связи с полным отсутствием информации о работе системы патчей секция PTCH тоже недоступна для модификации.
Всякая программа для ВМ Х3, написанная на ассемблере, содержит несколько обязательных частей. Это заголовок программы, определение секций, в которых производится работа, и завершение программы. Программный модуль начинается с директивы .TITLE. Директива .TITLE <любая строка> предназначена фактически для программиста, ибо напоминает ему - что это за файл, для чего и по какому случаю создавался.
Директива .RADIX <num> задает систему счисления по умолчанию. <num> - число, которое задает систему счисления и может быть равно 2, 8, 10 и 16. Нужно отметить, что при инициализации ассемблера устанавливается 16-ричная система счисления, поэтому, чтобы не было глюков и недопонимания, нужно всегда указывать квалификатор системы счисления для <num>. Например .RADIX 10d. При использовании в программе чисел можно использовать квалификаторы системы счисления - буквы, следующие за числом: b (binary), o (octal), d (decimal) или h (hex). Директива .RADIX может быть в любом месте программы.
Пример - Использование директивы .RADIX (кликните здесь для просмотра)
Код: |
1 .title Example RADIX
2
3 .section code, con
4
5 .radix 2d
6 00000001: 0002 0008 000A 0010 .word 10b, 10o, 10d, 10h, 10, 100, 1000, 10000
0002 0004 0008 0010
7
8 .radix 8d
9 00000011: 0002 0008 000A 0010 .word 10b, 10o, 10d, 10h, 10, 100, 1000, 10000
0008 0040 0200 1000
10
11 .radix 10d
12 00000021: 0002 0008 000A 0010 .word 10b, 10o, 10d, 10h, 10, 100, 1000, 10000
000A 0064 03E8 2710
13
14 .radix 16d
15 00000031: 0002 0008 000A 0010 .word 10b, 10o, 10d, 10h, 10, 100, 1000, 10000
0010 0100 1000 0000
pass2:(07.asm:16) Err: Value of word can be within range 0..FFFF.
16
17 00000041: .end
Пример - Использование директивы .RADIX
(В строке 16 намеренно сделана ошибка)
|
|
Директива определения программной секции .section <name>,
<mod>
Как было сказано ранее, ассемблерной программе доступны для модификации две секции - секция кода (CODE) и секция строк (STRG). В зависимости от того, что будет модифицировать патч (код или строковые ресурсы), вы должны указать в поле <name> - code или strg. Модификатор секции <mod> указывает режим, в котором будет производится правка. Если модификатор секции CON (concatenated), то все изменения, внесенные программой будут дописываться в конец секции. Если модификатор ABS (absolute), то внесенные вами данные будут накладываться на те, которые уже были там. То есть перекрывать.
Например, объявление .section code, abs определяет начало секции, в которой размещается код скрипта. Модификатор abs указывает, что мы будем писать поверх кода, который там уже был размещен. При этом счетчик команд устанавливается на начало программной секции. В Х3TC это адрес 00000001h.
Объявление секции .section code, con дает возможность установить счетчик команд в конец секции. Т.е. код, который будет набираться ниже этого объявления, расположится в конце секции CODE, после самой последней команды, которая уже была в этой секции.
.section strg, con - объявляет начало секции строковых ресурсов. Для секции STRG не рекомендуется устанавливать модификатор ABS, потому как в эту секцию автоматически сливаются все имена функций при их объявлении, а также строки, используемые в программе. При ошибке найти причину будет весьма непросто.
Отсутствие директивы .section в программе приведет к ошибке, так как ассемблер не знает куда запихивать код, который написал программист.
Программный модуль всегда должен заканчиваться директивой .end. .end - не команда ВМ Х3. Это инструкция ассемблеру о том, что нужно прекратить трансляцию. Строка, содержащая .end, не транслируется в машинный код.
Помимо преобразования исходного кода в машинный, ассемблер создает несколько других выходных файлов. Один из них - ассемблерный листинг. Он содержит сообщения о действиях ассемблера. Этот файл содержит исходный код вместе с комментариями, а также машинный код, сформированный ассемблером.
Пример - Небольшая часть листинга ассемблера (кликните здесь для просмотра)
Код: |
3997 ;
3998 ; === Patch21 ===================================================
3999 ; function PATCH.Patch21();
4000 ;
4001 00001DB1: PATCH.Patch21:
4002 00001DB1: 6E 0002 enter 0, 2
4003 00001DB4: 0E 000A readvar global.ga_GalaxyMapName ; [10d ; 0Ah]
4004 00001DB7: 0B 0001467C get_strg S00002E17 ; ; "x3_universe"
4005 00001DBC: 5B if sp[0]<>sp[1] then push 0 else push 1
4006 00001DBD: 34 00001DCA if sp[0]=0 then jump L00001DE8
4007 00001DC2: 01 push 0
4008 00001DC3: 2E get_object
4009 00001DC4: 85 0000445E call85 PATCH.Patch21_FixATFStations ; 0001A7AA
4010 00001DC9: 24 pop
4011 00001DCA: 01 L00001DE8: push 0
4012 00001DCB: 83 ret
Пример - Небольшая часть листинга ассемблера,
иногда называемого распечаткой.
|
|
Взяв кусок листинга некой программы, рассмотрим результаты работы ассемблера. В правой части распечатки находятся исходные команды. В левой части - информация, сгенерированная ассемблером. Первая колонка содержит номер каждой строки распечатки. Ассемблер устанавливает эти номера для исходного файла. Как правило они соотносятся с номерами строк в исходном файле, сформированном текстовым редактором. Во второй колонке содержатся адреса инструкций. Следующая колонка - код команды на машинном языке. Поскольку команды ВМ имеют длину от 1 до 9 байт, это поле будет изменяться в размере.
В большинстве примеров программ мы будем использовать листинг ассемблера.
Другой создаваемый ассемблером файл - файл ошибок. В этот файл ассемблер складывает только сообщения об ошибках. Для больших исходных файлов в несколько тысяч строк довольно трудно отыскать место ошибки в файле листинга.
При обнаружении ошибки в исходном тексте программы, ассемблер не создает файл с бинарным кодом.
Байты, слова и строки
"Битом" называют двоичную цифру, единичное значение 0 или 1. Группу из 8 бит принято называть "байтом". Как мы увидим позднее, отдельные команды ВМ Х3 могут оперировать с байтами. Кроме того, все команды ВМ Х3 - байтовые, то есть имеют опкод размером один байт. Во время тонкого моддинга и правки кода, необходимо иметь возможность для размещения определенного значения в байт памяти. Для таких случаев ассемблер располагает специальным механизмом - директивой .byte. .byte не является командой ВМ Х3. Это команда ассемблеру поместить в память определенное значение.
Директива .byte 10d дает ассемблеру задание сохранить десятичное значение 10 в текущий байт памяти. А директива .byte 1,2,3,4 - сохраняет значения от 1 до 4 по четырем последовательным адресам в памяти.
Аналогично директиве определения байта памяти, существует инструкция для определения слова. Для ассемблера ВМ Х3 слово составляет 16 бит. Первое утверждение .word в нижеприведенном примере определяет 16-битовое слово со значением 1000d.
Еще один тип данных, который может использоваться в программах на языке ассемблера - это двойное слово, значение длиной в 32 бита. ВМ Х3 пользуется двойными словами для хранения адресов и очень больших чисел. Чтобы определить область, содержащую значение двойного слова, нужно использовать директиву ассемблера .long.
Директивы .byte, .word и .long могут использоваться только в секции CODE. Для определения данных в секции STRG нужно использовать директиву .ascii:
.ascii "string"
Директива .ascii размещает в секции STRG указанную строку как последовательный набор байт. В конце строки автоматически добавляется нулевой байт, символизирующий конец строки.
Примеры определения байт, слов, длинных слов, строк (кликните здесь для просмотра)
Код: |
1 .title Example byte,word,long,ascii
2 .radix 10d
3
4 .section code, con
5
6 00000001: 0A .byte 10d
7 00000002: 01 02 03 04 43 .byte 1, 2, 3, 4, 'C'
8
9 00000007: 03E8 .word 1000d
10 00000009: 0001 0002 0003 0004 .word 1, 2, 3, 4, 'C'
0043
11
12 00000013: 000186A0 .long 100000d
13 00000017: 00000001 00000002 .long 1, 2, 3, 4, 'D'
00000003 00000004
00000044
14
15 .section strg, con
16
17 00000001: 58 33 20 46 6F 72 .ascii "X3 Force"
63 65
18 0000000A: 4E 65 78 74 20 73 .ascii "Next string"
74 72 69 6E 67
19
20 00000000: .end
Примеры определения байт, слов, длинных слов, строк
|
|
Если атрибут секции STRG установлен ABS, но можно поправить строку. Удобно, если нужно поправить буквально несколько байт в строке без перекомпиляции всего obj-а. Например нужно, чтобы при выводе какой-либо строки вместо маленьких букв выводились заглавные.
Пример - патч строки (кликните здесь для просмотра)
Код: |
.title test
.radix 16d
.loadobj "x3story.obj"
.section strg, abs
; была такая строка
; 0001AC7E : \t\t<current_patch value=\"%d\" />\r\n
; слово value заменим
$ = 0001AC7Eh
.ascii "\t\t<current_patch VALUE=\"%d\" />\r\n"
.end
|
|
Действуют следующие ограничения:
1. адрес начала строки, задаваемый в примере командой $=0001AC7Eh, должен точно соответствовать началу строки в obj-файле. Адреса всех строк можно посмотреть в файле с расширением .str
2. длина строки в патче должна быть не больше, чем в obj-файле. Если строка короче - дополняется нулевыми байтами. Увеличить длину - нельзя.
Работа ВМ Х3 (продолжение)
Как и любая вычислительная машина, ВМ Х3 имеет счётчик инструкций, который хранит адрес следующей выполняемой инструкции. Счётчик может быть загружен для реализации переходов (по команде перехода), или может быть инкрементирован для выборки следующей по порядку инструкции.
При старте ВМ Х3 движек ищет в исполняемом obj-файле функцию "_Init"
и передает ей управление.
Работа ВМ Х3 состоит в последовательной выборке команд из памяти команд и их выполнении. Каждая команда проходит через этот двухшаговый процесс. Выборкой очередной команды в этом цикле управляет один из регистров ВМ. Этот регистр называют счетчиком команд - PC. Место в памяти команд, на которое указывает этот регистр, содержит очередную команду, которую должна будет выбрать и выполнить ВМ. ВМ читает один или несколько байтов, в зависимости от длины команды, и выполняет ее. Затем ВМ увеличивает PC в соответствии с числом байтов в команде. Нормальное выполнение программы является последовательным, от одной команды к другой, расположенной следом.
Для управления процессом трансляции ассемблерной программы в машинный код ВМ Х3 ассемблер тоже имеет счетчик команд. Идентификатор $ ассемблера предназначен для работы с текущим счетчиком команд. Можно в любой момент определить, запомнить или установить новое значение счетчику команд. Например.
Пример - работа со счетчиком команд (кликните здесь для просмотра)
Код: |
1 .title Example PC
2 .radix 10d
3 .section code, abs
4
5 ;... писали что-то
6 00000001 temp_pc = $ ; запомнили место положение
7 00000101: $ = $ + 100h ; сместились на 100h
8 ;... еще что-то написали
9 00000001: $ = temp_pc ; восстановили запомненный счетчик команд
10
11 ;Для установки текущего счетчика команд можно использовать директиву .org
12 000000C9: .org temp_pc + 200
13 ;Это эквивалентно выражению
14 000000C9: $ = temp_pc + 200
15
16 000000C9: .end
Пример - работа со счетчиком команд
|
|
Следует заметить, что программный счетчик ассемблера - это указатель на текущую ассемблируемую команду. Это не тоже самое, что и программный счетчик ВМ Х3.
ВМ Х3 может изменить последовательность при выполнении команды, которая помещает в PC новое значение. Такие команды являются командами передачи управления, поскольку выполнение программы переходит в новую область. Инструкция перехода или выбора варианта является самым распространенным способом передачи управления. Команда перехода задает адрес команды, которая должна выполняться следующей.
Пример - Команда безусловного перехода (кликните здесь для просмотра)
Код: |
1 .title Example
2 .radix 10d
3
4 .section code, con
5
6 .func ExFunc
7
8 00000001: ExFunc:
9 00000001: 6E 0001 enter 0, 1
10 00000004: 32 0000000C jump $01
11 ; эти команды никогда не выполняются
12 00000009: 05 00 $00: pushb 0d
13 0000000B: 83 ret
14 ; а эти - выполняются
15 0000000C: 05 01 $01: pushb 1d
16 0000000E: 83 ret
17
18 0000000F: .end
Пример - Команда безусловного перехода
|
|
Обратите внимание, что в команде jump для определения следующего выполняемого адреса используется текстовая метка, хотя в машинном языке ВМ требуется абсолютный адрес команды. Ассемблер сам вычисляет адрес и ставит правильное значение в команду машинного языка.
Команда перехода не обязательно должна быть безусловной, как в приведенном выше примере. ВМ Х3 имеется несколько команд условного перехода, которые проверяют значение в стеке данных и выполняют переход.
Пример - Команда условного перехода (кликните здесь для просмотра)
Код: |
.title Example
.radix 10d
.section code, con
; global procedures
; function ExFunc(arg1);
;
00000001: | .func ExFunc
00000001: 6E 0001 | 0 ExFunc: enter 1, 1
00000004: | ; проверяем значение аргумента, переданного функции
00000004: 0D 0004 | 0 push sp[3] ; arg1
00000007: 33 0000000F | 1 if sp[0]<>0 then jump $01
0000000C: | ; значение аргумента == 0
0000000C: 05 00 | 0 pushb 0
0000000E: 83 | 1 ret
0000000F: | ; значение аргумента != 0
0000000F: 05 01 | 0 $01: pushb 1
00000011: 83 | 1 ret
.end
Пример - Команда условного перехода
|
|
Команда условного перехода (if sp[0]<>0 then jump) выполняет переход по указанному адресу, если значение в стеке не равно 0. Если условие не выполняется, ВМ Х3 выполняет команду, следующую за условным переходом, в данном случае команду pushb 0. Команда условного перехода позволяет проверить значения данных в процессе выполнения программы. Ход выполнения программы может меняться в зависимости от результатов этой проверки. Другая команда условного перехода (if sp[0]=0 then jump) выполняет переход по указанному адресу, если значение в стеке равно 0.
Команды выбора варианта (xjump и switch) являются более сложным случаем условного перехода по таблице.
Команда xjump вытаскивает из стека проверяемое значение и имеет два операнда. Первый операнд - число вариантов, не считая варианта по умолчанию (в С эквиволент - "default"). Второй операнд - "начальное значение варианта". Второй операнд придуман ES вероятно для уменьшения размера кода и ускорения работы программы, ибо экономит несколько команд.
Пример - Команда выбора варианта XJUMP (кликните здесь для просмотра)
Код: |
.title Example
.radix 10d
.section code, con
; global procedures
; function ExFunc(arg1);
;
00000001: | .func ExFunc
00000001: 6E 0001 | 0 ExFunc: enter 1, 1
00000004: 0D 0004 | 0 push sp[3] ; arg1
00000007: 78 0003 0000000A | 1 xjump 3, 10d
0000000E: 0000001E | 0 .long case_else ; default
00000012: 00000021 | 0 .long case_10 ; arg1 = 10
00000016: 00000024 | 0 .long case_11 ; arg1 = 11
0000001A: 00000027 | 0 .long case_12 ; arg1 = 12
0000001E: 05 FF | 0 case_else: pushb -1d ; 0FFh
00000020: 83 | 1 ret
00000021: 05 0A | 0 case_10: pushb 10d ; 0Ah
00000023: 83 | 1 ret
00000024: 05 14 | 0 case_11: pushb 20d ; 14h
00000026: 83 | 1 ret
00000027: 05 1E | 0 case_12: pushb 30d ; 1Eh
00000029: 83 | 1 ret
.end
Пример - Команда выбора варианта XJUMP
|
|
Команда switch вытаскивает из стека проверяемое значение и просматривает всю таблицу переходов. Если первый операнд команды не равен 0, то он определяет число вариантов, не считая варианта по-умолчанию. При этом таблица переходов состоит из двух чисел - проверяемого значения варианта и адреса перехода.
Пример - Команда выбора варианта SWITCH (вариант 1) (кликните здесь для просмотра)
Код: |
.title Example 04
.radix 10d
.section code, con
; global procedures
; function ExFunc(arg1);
;
00000001: | .func ExFunc
00000001: 6E 0001 | 0 ExFunc: enter 1, 1
00000004: 0D 0004 | 0 push sp[3] ; arg - число
00000007: 79 0004 0000 | 1 switch 4, 0
0000000C: 00000030 | 0 .long case_else ; default
00000010: 00000261 00000033 | 0 .long 609d, case_609
00000018: 00000263 00000036 | 0 .long 611d, case_611
00000020: 000002F8 00000039 | 0 .long 760d, case_760
00000028: 000002F9 0000003C | 0 .long 761d, case_761
00000030: 05 FF | 0 case_else: pushb -1d ; 0FFh
00000032: 83 | 1 ret
00000033: 05 0A | 0 case_609: pushb 10d ; 0Ah
00000035: 83 | 1 ret
00000036: 05 14 | 0 case_611: pushb 20d ; 14h
00000038: 83 | 1 ret
00000039: 05 1E | 0 case_760: pushb 30d ; 1Eh
0000003B: 83 | 1 ret
0000003C: 05 28 | 0 case_761: pushb 40d ; 28h
0000003E: 83 | 1 ret
.end
Пример - Команда выбора варианта SWITCH
(Первый вариант - сравнение чисел)
|
|
Если второй операнд команды не равен 0, то он определяет число вариантов, не считая варианта по-умолчанию. При этом таблица переходов также состоит из двух чисел, но первым числом будут адреса строк в секции STRG с которыми будет сравниваться проверяеме значение варианта, вторым числом - адреса перехода.
Пример - Команда выбора варианта SWITCH (вариант 2) (кликните здесь для просмотра)
Код: |
1 .title Example 1
2 .radix 10d
3
4 .section code, con
5
6 .func ExFunc
7
8 00000001: ExFunc:
9 00000001: 6E 0001 enter 1, 1
10 00000004: 0D 0004 push sp[3] ; argument
11 00000007: 79 0000 0004 switch 0, 4
12 0000000C: 00000030 .long $00
13 00000010: 00000008 00000033 .long str_center, $01 ; "center"
14 00000018: 0000000F 00000036 .long str_justify, $02 ; "justify"
15 00000020: 00000017 00000039 .long str_left, $03 ; "left"
16 00000028: 0000001C 0000003C .long str_right, $04 ; "right"
17
18 00000030: 05 FF $00: pushb -1d
19 00000032: 83 ret
20
21 00000033: 05 0A $01: pushb 10d
22 00000035: 83 ret
23
24 00000036: 05 14 $02: pushb 20d
25 00000038: 83 ret
26
27 00000039: 05 1E $03: pushb 30d
28 0000003B: 83 ret
29
30 0000003C: 05 28 $04: pushb 40d
31 0000003E: 83 ret
32 0000003F: end:
33
34 .section strg, con
35
36 00000008: 63 65 6E 74 65 72 str_center: .ascii "center"
37 0000000F: 6A 75 73 74 69 66 str_justify: .ascii "justify"
79
38 00000017: 6C 65 66 74 str_left: .ascii "left"
39 0000001C: 72 69 67 68 74 str_right: .ascii "right"
40
41 00000000: .end
Пример - Команда выбора варианта SWITCH
(Второй вариант - сравнение строк)
|
|
Локальные метки.
Сложные программы, включающие большое число команд переходов и подпрограмм, могут служить хорошей тренировкой изобретательности программиста в создании имен. В языке ассемблера ВМ Х3 имеются специальные "повторноиспользуемые" метки. Рассмотрим еще раз предыдущий пример, в котором используется команда switch. Метки $00, $01 и т.д. являются такого рода "локальнымми метками". Локальные метки должны иметь вид - символ '$', за которым следует число. На локальные метки можно ссылаться точно так же, как и на "обычные метки", но только в том случае, если между строкой, содержащей локальную метку, и командой, на нее ссылающейся, нет обычной метки. В нашем примере метки $00, $01, $02, $03, $04 являются локальными по отношению к части программы между метками ExFunc и end. Вне их они вообще восприниматься не будут. Поэтому в командах, расположенных после метки end или до метки ExFunc (или и там, и там), можно использовать другие метки $00 (и пр.) без боязни двусмысленности. Любая подобная ссылка интерпретируется ассемблером как ссылка на метку, расположенную в текущем локальном блоке программы, и поэтому соответствующим образом кодируется.
Взаимодействие ВМ Х3 с внешним миром.
ВМ Х3 взаимодействует с внешним миром исключительно через специально организованное API. С точки зрения ассемблера основой этого интерфейса является специализированная команда ВМ - callasm <name_func>, которая вызывает встроенные в игровой движек служебные функции. Для использования этих специализированных функций программист должен сначала объявить функции специальной директивой .extern.
Например .extern SE_ArrayAlloc заносит имя системной функции SE_ArrayAlloc в таблицы ассемблера и секцию STRG, позволяя в дальнейшем выполнить команду callasm SE_ArrayAlloc
Пример - Использование функций API (кликните здесь для просмотра)
Код: |
801 ;
802 ; === IncreaseTimeWarp ==========================================
803 ; procedure DEBUG.IncreaseTimeWarp();
804 ;
805 .extern TI_SetTimeWarpFactor, TI_GetTimeWarpFactor
806 00000468: DEBUG.IncreaseTimeWarp:
807 00000468: 6E 0002 setmem 2
808 0000046B: 01 push 0
809 0000046C: 82 00003123 callasm TI_GetTimeWarpFactor ; 00000270
810 00000471: 07 00100000 pushd 1048576d ; 00100000h
811 00000476: 5C if SP[0]<=SP[1] then push 0 else push 1
812 00000477: 34 0000048B if SP[0]=0 then jump L00000490
813 0000047C: 01 push 0
814 0000047D: 82 00003123 callasm TI_GetTimeWarpFactor ; 00000270
815 00000482: 03 push 2
816 00000483: 50 mul SP[0],SP[1]
817 00000484: 02 push 1
818 00000485: 82 00003145 callasm TI_SetTimeWarpFactor ; 00000285
819 0000048A: 24 pop
820 0000048B: 01 L00000490: push 0
821 0000048C: 83 ret
Пример - Использование функций API TI_SetTimeWarpFactor, TI_GetTimeWarpFactor
|
|
Директивы объявления переменных
ВМ Х3 - стековая машина и бОльшая часть команд этой машины ориентирована на работу с данными, расположенными в стеке данных. Однако хранение и обработка переменных и больших массивов данных, расположенных в стеке не очень эффективна, а доступ к ним затруднен из-за отсутствия в ВМ базовых указателей. Для решения этой проблемы ВМ Х3 имеет ряд специализированных команд для доступа к этой, специально организованной памяти данных. Переменные в ВМ Х3 типизированные. Это означает, что ВМ знает какой тип переменной лежит в стеке, соответственно несколько изменяется и поведение команд в зависимости от ее типа. Например, команда add для целых чисел будет выполнять целочисленное сложение, а для строковых - выполняется конкатенация. В ассемблере присутствуют специальные директивы, которые объявляют переменные в памяти данных:
.var <name> [= <expr>] ; объявление переменной
.vfixed <name> [= <expr>] ; объявление вещественной переменной
.vint <name> [= <expr>] ; целая переменная
.varray <name> [= <expr>] ; указатель на массив
.vtable <name> [= <expr>] ; указатель на таблицу
.vstring <name> [= <label>] ; указатель на строку
.vstring <name> [= <string>] ; строка
Часть, которая определяет значение переменной при инициализации можно опускать. Информация о глобальной переменной, объявленной одной из этих директив, заносится ассемблером в таблицы VARS. Информация о классовой переменной - в таблицы CLAS. Значения строковых переменных будут размещаться в таблицах STRG автоматически.
Работа с переменными.
В ВМ Х3 определены 6 типов переменных - целые, вещественные, строковые, массивы, таблицы и еще какие-то. Эти "еще какие-то" появились в v3.0, но что собой представляют - пока не понятно.
Пример - использование глобальных и классовых переменных (кликните здесь для просмотра)
Код: |
1 .title Example VARS
2 .radix 10d
3 .section code, con
4
5 .vint i1 ; объявляем глобальную переменную
6 .vstring s1 = "string" ; глобальная строковая переменная
7
8 ;
9 ; простая функция, которая читает значение глобальной переменной
10 ;
11 .func GetValue
12 00000001: 6E 0001 GetValue: enter 0, 1 ;
13 00000004: 0E 0000 readvar i1 ; читаем глобальную переменную в стек
14 00000007: 83 ret ; функция возвращает значение i1
15 ;
16 ; Эта функция записывает число 100 в глобальную переменную
17 ; и возвращает его в качестве результата
18 ;
19 .func SetValue
20 00000008: 6E 0001 SetValue: enter 0, 1 ;
21 0000000B: 05 64 push 100d ; sp[0] <- 100d
22 0000000D: 15 0000 writevar i1 ; i1 <- sp[0], команде не изменяет указатель стека
23 00000010: 24 pop ; поэтому восстанавливаем указатель стека ручками
24 00000011: 0E 0000 readvar i1 ; читаем значение из переменной
25 00000014: 83 ret ; функция возвращает его же
26 ;
27 ; Теперь рассмотрим как работать с переменными, объявленными в классе
28 ;
29 .classdef Test, 10000d
30 .vint i1 ; объявили целочисленную переменную
31 .func GetValue ; эта функция будет читать переменную
32 .func SetValue ; а эта записывать в нее
33 .endclass Test
34 ;
35 ; функция, принадлежащая классу Test, которая просто читает число,
36 ; записанное в классовой переменной Test.i1
37 ;
38 00000015: Test.GetValue:
39 00000015: 6E 0001 enter 0, 1
40 00000018: 0F 0000 read Test.i1 ; sp[0] <- i1
41 0000001B: 83 ret ; result = Test.i1
42 ;
43 ; функция записывает 100 в классовую переменную,
44 ; но читает глобальную переменную и возвращает ее значение.
45 ;
46 0000001C: Test.SetValue:
47 0000001C: 6E 0001 enter 0, 1 ;
48 0000001F: 05 64 push 100d ; sp[0] <- 100d
49 00000021: 16 0000 write Test.i1 ; Test.i1 <- sp[0]
50 00000024: 24 pop ; восстанавливаем стек
51 00000025: 0E 0000 readvar i1 ; читаем глобальную i1 в sp[0]
52 00000028: 83 ret ; result = global.i1
53
54 00000029: .end
Пример - использование глобальных и классовых переменных
|
|
Команды write, writevar не изменяют стек.
Команда сохранения значения в переменной write/writevar не изменяет указатель стека. Это может быть использовано для присвоения одного и того же значения нескольким переменным.
Пример - быстрая инициализация переменных (кликните здесь для просмотра)
Код: |
1 .title Example MultiInit
2 .radix 10d
3 .section code, con
4
5 ; обявляем класс TClass, который имеет
6 ; уникальный идентификационный номер 10000d
7 .classdef TClass, 10000d
8 .vint test1
9 .vint test2
10 .vint test3
11 .vint test4
12 .func prc1 ; объявляем функцию класса TClass.prc1
13 .endclass TClass
14
15 00000001: TClass.prc1:
16 00000001: 6E 0001 enter 0, 1
17 00000004: 01 push 0h ; значение 0h засылается в стек
18 00000005: 16 0000 write TClass.test1 ; переменные test1, test2, test3, test4
19 00000008: 16 0001 write TClass.test2 ; объекта TClass инициализируются
20 0000000B: 16 0002 write TClass.test3 ; одним значением
21 0000000E: 16 0003 write TClass.test4 ;
22 00000011: 24 pop ; очистить стек
23
24 00000012: 01 push 0
25 00000013: 83 ret
26
27 00000014: .end
|
|
Этот же способ может быть использован для инициализации глобальных переменных с помощью команды writevar.
При объявлении переменной ей может быть назначено начальное значение. Начальные значения всех переменных заносятся в таблицы VARS (или CLAS/VARS для классовых переменных). Изменить начальное значение переменной, уже существующей в obj-файле, можно, использовав следующий трюк.
Пример - правка начальных значений переменных (кликните здесь для просмотра)
Код: |
1 .title Example MultiInit
2 .radix 10d
3 .loadobj "x3story.obj"
4 ; объявляем секцию code как абсолютную!
5 .section code, abs
6
7 ; меняем начальное значение глобальной переменной
8 .vint TGlobal_3 = 101d
9
10 .classdef X_AUDIO
11 ; меняем начальное значение классовой переменной
12 .vint au_ErrorCount = 103d
13 .endclass X_AUDIO
14
15 00000001: .end
|
|
Секция CODE должна быть с атрибутом ABS. Числовой идентификатор класса задавать не нужно. Он сам вытаскивается из обжа. Имена класса и переменной должны строго соответствовать тем, что нашел дизассемблер. Важно также, что бы у дизассемблера и ассемблера были одинаковые форматы вывода, например DEC - десятичный. Для этого в .ini-файле дизассемблера проверить и установить ключ OutFmt = DEC, а в .ini-файле ассемблера TypeName = DEC.
Функции.
Под функцией понимают некоторую последовательность команд, оформленную в отдельный логический блок. Эта последовательность команд, которая может выполняться в программе неоднократно и в самых разных местах или вызываться единственный раз за все время работы программы. Очень часто большие и сложные программы специально разбивают на небольшие куски кода, которые гораздо легче понять и отладить. Однако, основное достоинство функций заключается в том, что однажды отлаженная функция может многократного использоваться в разных местах программы. Такая часть программы становится подпрограммой или функцией.
В ВМ Х3 процедуры, как таковые отсутствуют, поэтому будем стараться избегать этого термина. В случае, если он встретится в тексте, то можете считать, что речь идет о функции ВМ Х3.
Каждый раз, когда программе потребуется выполнить работу, возложенную на некоторую функцию, она передает ей управление командой вызова. Переход на функцию называется вызовом функции или командой вызова. Вызов функции отличается от команды перехода, рассмотренный нами ранее. Команда вызова функции сохраняет адрес следующей за ней команды в стеке. Этот адрес, называемый адресом возврата, указывает дорогу обратно к исходной последовательности команд.
В ассемблере существует специальная директива объявления функции .func <name>. Эта директива заносит указанный идентификатор (имя) в таблицу STRG. Дополнительные параметры функции (адрес, размер стека, число фактических параметров), которые необходимы для полноценного формирования структур SYMB, ассемблер получает от команды enter <num_args>,<size_stack>. Только после этого считается, что функция полностью определена. Команда enter - первая команда любой функции. Отсутствие команды enter рассматривается ассемблером как ошибка. Кроме того, это единственная команда, которая требует наличия метки. Идетификатор метки команды enter интерпретируется как имя функции, а ее адрес - как адрес функции.
Передача параметров функции: Глобальные функции.
В ВМ Х3 функции бывают разные. И вызов функции зависит от ее специфики. Если функция глобальная, то она должна вызываться командой call84 <addr>.
Давайте посмотрим, как работает вызов функции. Предположим, например, нам надо написать программу, которая к какому-либо числу прибавляет 100 в нескольких местах. Не будем пока задумываться над тем, насколько убога данная функция. Все-таки мы рассматриваем умозрительный пример. А на простых примерах учиться проще. В примере ниже написана глобальная функция "Add100". Число, к которому эта функция будет прибавлять 100, является аргументом этой функции и для его передачи мы используем стек. Стек часто используется для передачи информации в подпрограммы и из них. Для доступа к аргументам функция использует регистр ВМ Х3 sp, как указатель на область стека. Все аргументы доступны как смещения относительно регистра sp[x], где x - смещение.
При выполнении функции Add100 команда push sp[3] извлекает из стека аргумент, следующая команда добавляет в стек число 100, затем команда add извлекает из стека эти два числа и складывает их, полученный результат записывается в стек.
Последняя команда функции ret является специальной командой и называется возвратом. Но прежде чем выполнение вернется к основной программе, команда ret запоминает значение из вершины стека (результат работы функции), находит в стеке число аргументов функции (хранится в sp[2]), удаляет из стека все аргументы и записывает в стек результат работы функции. После этого команда возврата берет адрес, который был сохранен командой вызова функции и помещает его в указатель команд ВМ Х3. Это заставляет программу вернуться к команде, следующей за вызовом процедуры.
Пример - вызов глобальной функции (кликните здесь для просмотра)
Код: |
;
; Вход в функцию Add100
; Простая функция, которая вытаскивает значение переданного ей параметра
; и увеличивает его на 100
;
.func Add100
Add100:
enter 1, 2 ; функция имеет 1 аргумент, использует 2 ячейки стека
;
; вытащим на вершину стека аргумент функции
push SP[3] ; это значение <arg1>, переданное нашей функции
push 100d ; число 100
add SP[0], SP[1] ; сложить SP[1] + SP[0] -> SP[0]
;
; число в стеке SP[0] будет возвращено вызывающей программе
ret ; result = arg1 + 100
;
; Вход в функцию Const100
; Простая функция, которая не имеет аргументов
; возвращает число 100
;
.func Const100
Const100:
enter 0, 1 ; функция не имеет аргументов, использует 1 ячейку стека
push 100d ; число 100
; число в стеке SP[0] будет возвращено вызывающей программе
ret ; result = 100
; основная программа
; вызов объявленной ранее функции, будет выглядеть так:
.....
push <arg1> ; аргумент (параметр) функции
push 1 ; число фактических аргументов функции
call84 global.Add100
pop ; в стеке был результат функции
; если функция не имеет аргументов, то она должна вызываться вот так:
push 0 ; у функции нет аргументов
call84 global.Const100
pop ; в стеке был результат функции
.....
|
|
Если функция ничего не должна возвращать, все равно перед возвратом из функции нужно в стек записать что-нибудь.
Минимально возможная функция выглядит так:
Код: |
.func SuperSimple
SuperSimple:
enter 0, 1
; функция всегда возвращает результат, даже когда он никому не нужен.
push 0 ;
ret ; возврат из функции
|
Не бывает функций, которые "ничего" не возвращают. Все функции возвращают "что-то" в стеке. Поэтому после вызова функции нужно как минимум извлечь это "что-то" из стека.
Продолжение следует...
---
Edit: Исправлены грамматические ошибки и необоснованные измышлизмы по замечаниям
mistaqur : |
В SP[0] и SP[1] расположены адрес возврата и указатель на объект, которые имеют собственный тип, с которым нельзя работать.
|
Последний раз редактировалось: CheckerTwo (20:03 26-01-2012), всего редактировалось 8 раз(а) |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
Передача параметров функции: Глобальные функции. Продолжение.
Для того, чтобы разобраться как передаются аргументы в функцию напишем довольно большую и, может быть, сложноватую для новичков программу. Но она может быть использована как стартовая площадка для экспериментов. Итак, в качестве основы наших исследований возьмем файл "x3config.obj", который лежит в папке "X3TC\L\". Именно этот файл как нельзя лучше подходит для небольших исследований. Причин две. Первая - это файл маленький! Его модификация не требует глубоких знаний устройства ВМ Х3. А вторая - он срабатывает сразу же, как только запустили exe файл. Так что можно тут же выходить из игры! Т.е. все происходит очень быстро. Для отладки небольших функций, которым не нужны ресурсы игры, просто идеально. В этом obj-модуле есть две функции - "_Init" и "LoadText". При старте игры ВМ Х3 загружает этот модуль и запускает функцию "_Init". Добавим в нее вызов своей функции "TestStack". "TestStack" заполняет стек числами и вызывает небольшую подпрограмму, которая аккуратно переписывает значения, хранящиеся в стеке в глобальные переменные t0..t6. После чего содержимое этих переменных уже функцией "SaveFile" записывается в файл на диске, рядом с exe.
Пример - программа анализа стека в функциях (кликните здесь для просмотра)
Код: |
1 .title x3config.obj
2 .radix 10d
3 .extern SE_ReadFile, SE_SPrintf, SE_TableAlloc, SE_XMLImportText
4 .section code, con
5
6 ; global variables
7 .vint FONT_OCR;
8 .vtable FONT_LARGE;
9 .vint gs_TextFileTable = -2147483648d;
10 .vint TGlobal_3 = -2147483648d;
11 .vint TGlobal_4 = -2147483648d;
12 .vint gs_DelayFactor = -2147483648d;
13 .vint ga_Sectors = -2147483648d;
14 .vint ga_Races = -2147483648d;
15 .vint ga_Player = -2147483648d;
16 .vint ga_PlayerShip = -2147483648d;
17 .vint ga_GalaxyMapName = -2147483648d;
18 .vint gs_DebugDump = -2147483648d;
19 .vint gs_DebugInit = -2147483648d;
20 .vint gs_DebugInput = -2147483648d;
21 .vint gs_DebugLogic = -2147483648d;
22 .vint gs_DebugHTML = -2147483648d;
23 .vint gs_DebugMonitors = -2147483648d;
24 .vint gs_DebugComp = -2147483648d;
25 .vint gs_DebugTrade = -2147483648d;
26 .vint gs_DebugCreateObject = -2147483648d;
27 .vint gs_DebugKilled;
28 .vint gs_DebugHangarStart = -2147483648d;
29
30 ; global procedures
31 .func _Init;
32 .func LoadText;
33 ;
34 ; === _Init =====================================================
35 ; function _Init(arg1);
36 ;
37 00000001: _Init:
38 00000001: 6E 0003 enter 1, 3
39 00000004: 01 push 0
40 00000005: 82 00000018 callasm SE_TableAlloc ; 00000007
41 0000000A: 15 0001 writevar global.FONT_LARGE ; [1]
42 0000000D: 24 pop
43 0000000E: 03 push 2
44 0000000F: 0D 0005 push SP[4] ; arg1
45 00000012: 03 push 2
46 00000013: 84 00000022 call84 global.LoadText ; 0000001B
47 00000018: 24 pop
48
49 00000019: 01 push 0
50 0000001A: 84 00000158 call84 global.TestStack
51 0000001F: 24 pop
52
53 00000020: 01 push 0
54 00000021: 83 ret
55 ;
56 ; === LoadText ==================================================
57 ; function LoadText(arg1, arg2);
58 ;
59 00000022: LoadText:
60 00000022: 6E 0005 enter 2, 5
61 00000025: 0D 0005 push SP[4] ; arg2
62 00000028: 0B 000000B9 get_strg S0000001E ; "t/%04d.xml"
63 0000002D: 03 push 2
64 0000002E: 82 0000000D callasm SE_SPrintf ; 00000029
65 00000033: 0D 0001 push SP[0] ; loc1
66 00000036: 02 push 1
67 00000037: 82 00000001 callasm SE_ReadFile ; 00000034
68 0000003C: 0D 0001 push SP[0] ; loc2
69 0000003F: 64 if SP[0]=0 then push 1 else push 0
70 00000040: 34 0000004D if SP[0]=0 then jump L00000046
71 00000045: 01 push 0
72 00000046: 14 0003 mov SP[2],SP[0] ; loc1
73 00000049: 23 0002 popx 2
74 0000004C: 83 ret
75
76 0000004D: L00000046:
77 0000004D: 0D 0001 push SP[0] ; loc2
78 00000050: 02 push 1
79 00000051: 82 00000026 callasm SE_XMLImportText ; 00000040
80 00000056: 24 pop
81 00000057: 0D 0002 push SP[1] ; loc1
82 0000005A: 0D 0008 push SP[7] ; arg2
83 0000005D: 0E 0001 readvar global.FONT_LARGE ; [1]
84 00000060: 17 put_to_array
85 00000061: 24 pop
86 00000062: 02 push 1
87 00000063: 14 0003 mov SP[2],SP[0] ; loc1
88 00000066: 23 0002 popx 2
89 00000069: 83 ret
90
91 ;
92 ; в эти глобальные переменные мы будем
93 ; складывать то, что хранилось в стеке
94 ;
95 .vint t0 = -1
96 .vint t1 = -1
97 .vint t2 = -1
98 .vint t3 = -1
99 .vint t4 = -1
100 .vint t5 = -1
101 .vint t6 = -1
102 ;
103 ; функция сохранения стека в глобальных переменных
104 ;
105 .func SaveStack
106 0000006A: SaveStack:
107 0000006A: 6E 0001 enter 3, 1
108
109 0000006D: 0D 0001 push SP[0]
110 00000070: 15 0016 writevar global.t0
111 00000073: 24 pop
112
113 00000074: 0D 0002 push SP[1]
114 00000077: 15 0017 writevar global.t1
115 0000007A: 24 pop
116
117 0000007B: 0D 0003 push SP[2] ; число аргументов функции
118 0000007E: 15 0018 writevar global.t2
119 00000081: 24 pop
120
121 00000082: 0D 0004 push SP[3] ;
122 00000085: 15 0019 writevar global.t3
123 00000088: 24 pop
124
125 00000089: 0D 0005 push SP[4] ;
126 0000008C: 15 001A writevar global.t4
127 0000008F: 24 pop
128
129 00000090: 0D 0006 push SP[5] ;
130 00000093: 15 001B writevar global.t5
131 00000096: 24 pop
132
133 00000097: 0D 0007 push SP[6] ; не хорошо читать данные, которые не передавались
134 0000009A: 15 001C writevar global.t6 ; функции - можно налететь на необнаруживаемую ошибку
135 0000009D: 24 pop
136
137 0000009E: 05 00 pushb 0
138 000000A0: 83 ret
139 ;
140 ; запись значений переменных в файл
141 ;
142 .extern SE_WriteFile, SE_SPrintf
143 .func SaveFile
144 000000A1: SaveFile:
145 000000A1: 6E 0005 enter 0, 5
146 000000A4: 0B 00000070 get_strg S_XC_B_1 ; loc1 <-- ""
147
148 000000A9: 0D 0001 push SP[0] ; <-- loc1
149 000000AC: 0E 0016 readvar t0
150 000000AF: 0B 00000071 get_strg S_XC_B_2 ; <-- "t0: %d\r\n"
151 000000B4: 03 push 2
152 000000B5: 82 0000000D callasm SE_SPrintf
153 000000BA: 46 add SP[0],SP[1]
154 000000BB: 14 0002 mov SP[1],SP[0] ; --> loc1
155 000000BE: 24 pop
156
157 000000BF: 0D 0001 push SP[0] ; <-- loc1
158 000000C2: 0E 0017 readvar t1
159 000000C5: 0B 0000007A get_strg S_XC_B_3 ; <-- "t1: %d\r\n"
160 000000CA: 03 push 2
161 000000CB: 82 0000000D callasm SE_SPrintf
162 000000D0: 46 add SP[0],SP[1]
163 000000D1: 14 0002 mov SP[1],SP[0] ; --> loc1
164 000000D4: 24 pop
165
166 000000D5: 0D 0001 push SP[0] ; <-- loc1
167 000000D8: 0E 0018 readvar t2
168 000000DB: 0B 00000083 get_strg S_XC_B_4 ; <-- "t2: %d\r\n"
169 000000E0: 03 push 2
170 000000E1: 82 0000000D callasm SE_SPrintf
171 000000E6: 46 add SP[0],SP[1]
172 000000E7: 14 0002 mov SP[1],SP[0] ; --> loc1
173 000000EA: 24 pop
174
175 000000EB: 0D 0001 push SP[0] ; <-- loc1
176 000000EE: 0E 0019 readvar t3
177 000000F1: 0B 0000008C get_strg S_XC_B_5 ; <-- "t3: %d\r\n"
178 000000F6: 03 push 2
179 000000F7: 82 0000000D callasm SE_SPrintf
180 000000FC: 46 add SP[0],SP[1]
181 000000FD: 14 0002 mov SP[1],SP[0] ; --> loc1
182 00000100: 24 pop
183
184 00000101: 0D 0001 push SP[0] ; <-- loc1
185 00000104: 0E 001A readvar t4
186 00000107: 0B 00000095 get_strg S_XC_B_6 ; <-- "t4: %d\r\n"
187 0000010C: 03 push 2
188 0000010D: 82 0000000D callasm SE_SPrintf
189 00000112: 46 add SP[0],SP[1]
190 00000113: 14 0002 mov SP[1],SP[0] ; --> loc1
191 00000116: 24 pop
192
193 00000117: 0D 0001 push SP[0] ; <-- loc1
194 0000011A: 0E 001B readvar t5
195 0000011D: 0B 0000009E get_strg S_XC_B_7 ; <-- "t5: %d\r\n"
196 00000122: 03 push 2
197 00000123: 82 0000000D callasm SE_SPrintf
198 00000128: 46 add SP[0],SP[1]
199 00000129: 14 0002 mov SP[1],SP[0] ; --> loc1
200 0000012C: 24 pop
201
202 0000012D: 0D 0001 push SP[0] ; <-- loc1
203 00000130: 0E 001C readvar t6
204 00000133: 0B 000000A7 get_strg S_XC_B_8 ; <-- "t6: %d\r\n"
205 00000138: 03 push 2
206 00000139: 82 0000000D callasm SE_SPrintf
207 0000013E: 46 add SP[0],SP[1]
208 0000013F: 14 0002 mov SP[1],SP[0] ; --> loc1
209 00000142: 24 pop
210
211 00000143: 0D 0001 push SP[0] ; <-- loc1
212 00000146: 0B 000000B0 get_strg S_XC_B_9 ; <-- "test.log"
213 0000014B: 03 push 2
214 0000014C: 82 00000050 callasm SE_WriteFile
215 00000151: 24 pop
216 00000152: 01 push 0
217 00000153: 14 0002 mov SP[1],SP[0]
218
219 00000156: 24 pop
220 00000157: 83 ret
221 ;
222 ; тестовая функция, на базе которой мы исследуем
223 ; содержимое стека при вызове функции
224 ;
225 .func TestStack
226 00000158: TestStack:
227 00000158: 6E 000B enter 0, 11
228
229 0000015B: 07 A0B0C0D0 pushd 0A0B0C0D0h
230 00000160: 07 A1B1C1D1 pushd 0A1B1C1D1h
231 00000165: 07 A2B2C2D2 pushd 0A2B2C2D2h
232 0000016A: 07 A3B3C3D3 pushd 0A3B3C3D3h
233 0000016F: 07 A4B4C4D4 pushd 0A4B4C4D4h
234 00000174: 07 A5B5C5D5 pushd 0A5B5C5D5h
235 00000179: 07 A6B6C6D6 pushd 0A6B6C6D6h
236 0000017E: 23 0002 popx 2
237 00000181: 07 A7B7C7D7 pushd 0A7B7C7D7h ; sp[5]
238 00000186: 07 A8B8C8D8 pushd 0A8B8C8D8h ; sp[4]
239 0000018B: 07 A9B9C9D9 pushd 0A9B9C9D9h ; sp[3]
240 00000190: 04 push 3 ; sp[2]
241 00000191: 84 0000006A call84 global.SaveStack
242 00000196: 24 pop
243 00000197: 23 0004 popx 4
244
245 0000019A: 01 push 0
246 0000019B: 84 000000A1 call84 global.SaveFile
247 000001A0: 24 pop
248
249 000001A1: 83 ret
250
251 .section strg, con
252
253 00000070: S_XC_B_1: .ascii ""
254 00000071: 74 30 3A 20 25 78 S_XC_B_2: .ascii "t0: %x\r\n"
0D 0A
255 0000007A: 74 31 3A 20 25 78 S_XC_B_3: .ascii "t1: %x\r\n"
0D 0A
256 00000083: 74 32 3A 20 25 78 S_XC_B_4: .ascii "t2: %x\r\n"
0D 0A
257 0000008C: 74 33 3A 20 25 78 S_XC_B_5: .ascii "t3: %x\r\n"
0D 0A
258 00000095: 74 34 3A 20 25 78 S_XC_B_6: .ascii "t4: %x\r\n"
0D 0A
259 0000009E: 74 35 3A 20 25 78 S_XC_B_7: .ascii "t5: %x\r\n"
0D 0A
260 000000A7: 74 36 3A 20 25 78 S_XC_B_8: .ascii "t6: %x\r\n"
0D 0A
261 000000B0: 74 65 73 74 2E 6C S_XC_B_9: .ascii "test.log"
6F 67
262
263 000000B9: 74 2F 25 30 34 64 S0000001E: .ascii "t/%04d.xml"
2E 78 6D 6C
264
265 00000000: .end
|
|
После запуска exe получаем файл "test.log" вот с таким содержанием:
Код: |
t0: 0 = sp[0]
t1: 0 = sp[1]
t2: 3 = sp[2]
t3: a9b9c9d9 = sp[3]
t4: a8b8c8d8 = sp[4]
t5: a7b7c7d7 = sp[5]
t6: a4b4c4d4 = sp[6] - функция не должна обращаться к этому смещению! только для теста
|
Я немного модифицировал этот файлик, добавив комментарии "sp[n]", для того чтобы было удобно разбираться.
Итак, при вызове функции "SaveStack" для доступа к первому аргументу мы должны использовать смещение указателя стека sp[3], для второго - sp[4] и так далее. В стеке со смещение sp[2] содержится число аргументов функции. sp[1] содержит нечитаемое значение идентификатора класса, sp[0] - адрес возврата, значение которого тоже нельзя прочитать (читаются всегда нулевые значения).
Методы классов.
Директива объявления класса
.CLASSDEF <name> , <num>
.CLASSDEF <name> ( <name_parent> ) , <num>
.ENDCLASS <name>
Директива .CLASSDEF объявляет класс, имя которого задается идентификатором <name>. В скобках может указываться родительский класс - <name_parent>. Кроме того, обязательно должен быть указан уникальный числовой идентификационный номер класса.
Пример - объявление классовых функций (кликните здесь для просмотра)
Код: |
1 .title Example FUNC
2 .radix 10d
3 .loadobj "x3story.obj"
4 .section code, con
5
6 .vint test1 ; объявлена глобальная переменная
7
8 ; обявляем класс TClass, который является наследником от TSHIP и имеет
9 ; уникальный идентификационный номер 10000d
10 .classdef TClass(TSHIP), 10000d
11 .vint var1 ; объявляем переменную класса TClass.var1
12 .func prc1 ; объявляем функцию класса TClass.prc1
13 .endclass TClass
14
15 ; Теперь обязательно должна быть определена точка входа в функцию
16 ; TClass.prc1. Причем первой командой должна стоять enter
17 ; Команда enter проверяет наличие метки, имени класса и имени функции
18 00188EF0: TClass.prc1:
19 00188EF0: 6E 0001 enter 0, 1
20 00188EF3: 01 push 0
21 00188EF4: 83 ret
22
23 ;Для модификации (дополнения) существующих классов новыми переменными и/или
24 ;функциями нужно использовать директиву .CLASSDEF без параметров. Например:
25
26 .classdef GONER
27 .vint NewVariable
28 .func NewFuction
29 .endclass GONER
30
31 ; Новая функция в ужесуществующем классе GONER
32 00188EF5: GONER.NewFuction:
33 00188EF5: 6E 0001 enter 0, 1
34 00188EF8: 0F 000A read GONER.NewVariable ; читаем из классовой переменной
35 00188EFB: 15 0033 writevar global.test1 ; записываем в глобальную переменную
36 00188EFE: 83 ret ; result = GONER.NewVariable
37
38 00188EFF: .end
Пример - объявление классовых функций
|
|
Вызов функции-метода внутри одного класса немного отличается от вызовов глобальных функций. Для этого испльзуются команды call85 и call86.
call85 - вызывает виртуальные методы классов, при этом id класса передается параметром функции. Этот id может быть вычислен и где-то храниться. Например в массиве, содержащем список шипов.
call86 - вызывает статические методы классов, адреса которых точно известны на момент компиляции и не изменятся.
Пример - вызов классовых функций (кликните здесь для просмотра)
Код: |
1 .title Example
2 .radix 10d
3 .loadobj "x3story.cod"
4 .section code, con
5
6 ;
7 ; Простая функция, которая вытаскивает значение переданного ей аргумента
8 ; и увеличивает его на 100
9 ;
10 .func Add100
11 00188EF0: Add100:
12 00188EF0: 6E 0002 enter 1, 2 ; функция имеет 1 аргумент, использует 2 ячейки стека
13 00188EF3: 0D 0004 push SP[3] ; это значение <arg1>, переданное нашей функции
14 00188EF6: 05 64 push 100d ; число 100
15 00188EF8: 46 add SP[0], SP[1] ; сложить SP[1] + SP[0] -> SP[0]
16 00188EF9: 83 ret ; result = arg1 + 100
17 ;
18 ; пример объявления класса с единственной функцией
19 ;
20 .classdef TAdd, 10000d
21 .func Addition
22 .endclass TAdd
23
24 ;
25 ; функция, которая складывает аргумент с числом 200 и возвращает его
26 ;
27 00188EFA: TAdd.Addition:
28 00188EFA: 6E 0002 enter 1, 2
29 ; вытащим на вершину стека аргумент функции
30 00188EFD: 0D 0004 push SP[3] ; это значение <arg1>, переданное нашей функции
31 00188F00: 06 00C8 push 200d ; число 200
32 00188F03: 46 add SP[0], SP[1] ; сложить SP[1] + SP[0] -> SP[0]
33 00188F04: 83 ret ; result = arg1 + 200
34 ;
35 ; объявление более сложного класса
36 ;
37 .classdef TTestClass, 10001d
38 .func Addition
39 .func Test
40 .func TestGlobal
41 .func TestAnother
42 .endclass TTestClass
43
44 ;
45 ; функция, которая складывает аргумент с числом 300 и возвращает его
46 ;
47 00188F05: TTestClass.Addition:
48 00188F05: 6E 0002 enter 1, 2
49 00188F08: 0D 0004 push SP[3] ; это значение <arg1>, переданное нашей функции
50 00188F0B: 06 012C push 300d ; число 300
51 00188F0E: 46 add SP[0], SP[1] ; сложить SP[1] + SP[0] -> SP[0]
52 00188F0F: 83 ret ; result = arg1 + 300
53 ;
54 ; функция Test вызывает метод этого же класса
55 ;
56 00188F10: TTestClass.Test:
57 00188F10: 6E 0003 enter 1, 3 ;
58 00188F13: 05 1E push 30d ; передаем нашей функции Addition параметр = 30
59 00188F15: 02 push 1 ; один параметр
60 00188F16: 2E get_object ; записать в стек идентификатор нашего объекта
61 ;
62 ; при вызове функции-метода имя класса можно не писать, или написать
63 ; (TTestClass.Addition), но оно будет игнорироваться.
64 ;
65 00188F17: 85 0001B606 call85 Addition ; складываем (+300)
66 00188F1C: 83 ret ; результат в стеке, его и возвращаем
67 ;
68 ; функция TestGlobal вызывает глобальную функцию
69 ;
70 00188F1D: TTestClass.TestGlobal:
71 00188F1D: 6E 0002 enter 1, 2 ;
72 00188F20: 05 1E push 30d ; передаем нашей функции Addition параметр = 30
73 00188F22: 02 push 1 ; один параметр
74 00188F23: 84 00188EF0 call84 global.Add100 ; складываем (+ 100)
75 00188F28: 83 ret ; результат в стеке, его и возвращаем
76 ;
77 ; функция TestAnother вызывает метод чужого класса
78 ;
79 00188F29: TTestClass.TestAnother:
80 00188F29: 6E 0003 enter 1, 3 ;
81 00188F2C: 05 1E push 30d ; передаем нашей функции Addition параметр = 30
82 00188F2E: 02 push 1 ; один параметр
83 00188F2F: 06 2710 push 10000d ; записать в стек идентификатор чужого класса
84 00188F32: 86 00188EFA call86 TAdd.Addition ; складываем (+200)
85 00188F37: 83 ret ; результат в стеке, его и возвращаем
86
87 00188F38: .end
|
|
Дополнительные особенности ассемблера.
Как и в любом ассмблере, в xa_asm существуют директивы, котрые не транслируются непосредственно в машинные команды. Директивы предназначены в первую очередь для управления процессом трансляции и прочих вспомогательных функций.
Условная трансляция.
.IF <boolean_expr>
.ELSE
.ENDIF
Директива .if требует одного аргумента - выражение. Если значение выражения не нулевое, то трансляция продолжается до директивы .else или .endif. Если значение выражения равно нулю - следом идущие команды пропускаются. Директива .if всегда должна иметь свой собственный .endif. Допускается вложенность директив условной трансляции. При написании сложных схем конфигурации исходного текста необходимо проверять граничные значения констант. В случае, если какая-либо константа или условие принимают недопустимое значение, программист может использовать специальную директиву ассемблера, которая принудительно генерирует состояние ошибки .error "Any message". Эта директива может использоваться в любом месте совместно с директивами условной трансляции и макроопределениями. Она выводит в листинг сообщение об ошибке.
Макроопределения.
.MACRO <name> <arg1>,<arg2>,..,<argN>
.ENDM
Функции, которые определены для макросов
.EMPTY(<num>) - возвращает 1, если аргумент под номером <num> не определен во время вызова макрорасширения. Аргументы в макроопределении нумеруются с нуля.
.NARG - возвращает число аргументов, переданных макросу.
Директива загрузки дополнительных файлов .INCLUDE <str_name_file>.
Разделение единого исходника на множество мелких позволяет немного упростить разработку сложных программ.
Например .include "myconst.inc" будет подгружать и ассемблировать дополнительный файл "myconst.inc". Количество директив .include не ограничено для одного файла, но максимальная глубина вложенности include-файлов не может превышать пяти. Макросы, настроечные константы и нечасто используемый код - основные кандидаты на вынос в отдельный файл.
В примере ниже приведена небольшая программа, которая использует дополнительные возможности ассемблера - макроподстановки и условную трансляцию. Все конечно "надумано" и только для демонстрации...
Пример - использование препроцессора ассемблера (кликните здесь для просмотра)
Код: |
1 .title Example - macros
2 .radix 10d
3
4 .section code, abs
5
6 .macro je addr
7 .if .empty(0)
8 .error "JE: Empty argument"
9 .endif
10 if sp[0]=sp[1] then push 0 else push 1
11 if sp[0]=0 then jump addr
12 .endm
13
14 .macro div$
15 div sp[0],sp[1]
16 .endm
17
18 .macro add$
19 add sp[0],sp[1]
20 .endm
21
22 .macro neg$
23 neg sp[0]
24 .endm
25
26 ; Задаем значения ПОЛЬЗОВАТЕЛЬСКИХ констант:
27 00001388 StoreFactor = 5000d
28 00000001 fStoreFactor = (StoreFactor * 2) > 2000d
29
30 ; Проверка значения StoreFactor на предельно допустимые значения
31 .if StoreFactor < 32d
32 .error "StoreFactor too small!"
33 .endif
34 .if StoreFactor > 3FFFFFFFh
35 .error "StoreFactor too big!"
36 .endif
37
38 ; Установим значение максимального размера склада
39 00029850: $ = 00029850h
40 00029850: 06 2710 push StoreFactor * 2
41 ; Проверим необходимость записи в дополнительную область
42 .if fStoreFactor
43 00029853: 0D 0003 push sp[2]
44 00029856: 0D 0006 push sp[5]
45 div$
00029859: 51 div sp[0],sp[1]
46 neg$
0002985A: 65 neg sp[0]
47 je wrStoreFactor0
.if .empty(0)
.error "JE: Empty argument"
.endif
0002985B: 5A if sp[0]=sp[1] then push 0 else push 1
0002985C: 34 0002A671 if sp[0]=0 then jump 173681d
48 00029861: 32 0002B632 jump wrStoreFactor1
49 .else
50 push sp[2]
51 push sp[5]
52 add$
add sp[0],sp[1]
53 push sp[0]
54 .endif
55
56 0002A671: $ = 0002A671h
57 0002A671: wrStoreFactor0:
58
59 0002B632: $ = 0002B632h
60 0002B632: wrStoreFactor1:
61
62 0002B632: .end
Пример использование препроцессора ассемблера
(Директивы условной трансляции и макроопределения)
|
|
Макроопределения div$, add$, neg$ "доопределяют" систему команд ассмблера, добавляя более короткие в написании псевдокоманды (не нужно писать операнды команд). Более сложное и, возможно, чуть более полезное макроопределение команды je (jump if eq 0) рассмотрим подробнее.
Код: |
.macro je addr
.if .empty(0)
.error "JE: Empty argument"
.endif
if sp[0]=sp[1] then push 0 else push 1
if sp[0]=0 then jump addr
.endm
|
Это макроопределение заменяет две команды ВМ Х3 и, кроме того, выполняет проверку на допустимость вызова макроса. Директива ассемблера .empty(0) проверяет наличие аргумента макроса. Если нулевой аргумент отсутствует (отсчет аргументов ведется с 0), то теоретически должен сработать .if и выполнится директива .error, ассемблер выведет ошибку. На самом деле, для данной конструкции это все не нужно, так как отсутствие фактического аргумента у макроса сформирует неверную команду if sp[0]=0 then jump, с отсутствующим адресом перехода и ассемблер все равно выдаст ошибку. Но, несмотря на то, что это всего лишь демонстрация возможностей ассемблера, программист может сам придумать для себя более выразительные и удобные команды, чем изначально заложено в ассемблере.
Директива .SETTIME <str_date_time>.
Директива принудительно устанавливает в секциях SYMB, VARS, CLAS obj-файла указанную дату и время. Может быть использована для того, чтобы определенные версии патчей всегда компилировались одинаково, с точностью до байта.
Например: .settime "05-10-2006 00:59:59" ; 5 октября 2006 года
добавлено спустя 15 минут:
Чой-то я выдохся...
Последний раз редактировалось: CheckerTwo (20:06 26-01-2012), всего редактировалось 4 раз(а) |
|
|
vivere
230 EGP
   Рейтинг канала: 4(57) Репутация: 56 Сообщения: 171
Зарегистрирован: 17.02.2011
 |
|
Огромное спасибо за описание, хотя оно несколько запоздало, так как я уже все эти вещи выяснила на собственном опыте. Но новичкам будет очень полезно.
У меня есть просьба по ассемблеру, вернее даже две:
во первых, сделать возможным написание следующей команды "push sp[arg1]". тоесть чтобы при сборке он сам подставлял нужное число.
во вторых, сделать доступным чтение текущего состояния стека на этапе сборки для передачи этого значения в макрос.
предполагаю, что все это достаточно сложно осуществить ввиду того, что на этапе сборки ассемблер еще не знает уровня стека.
|
|
|
Artaazar
|
|
Господа, у меня есть взрыв мозга (Разрывная - сказал штирлиц пораскинув мозгами)
Я так понимаю класс здесь наследует свойства, параметры, и прочую хрень от своего "родителя". Но до меня не доходит, почему тот же М7 не имеет прав на захват кораблей, ведь:
; Obj_2134 = class(SHIP)
; function CanLaunchMarines();
; SHIP_BIG = class(Obj_2134)
; SHIP_CARRIER = class(SHIP_BIG)
; Obj_2019 = class(SHIP_CARRIER)
; SHIP_M7 = class(Obj_2019)
Объясните пожалуйста, мою ошибку
|
|
|
XT3_Traktor
180 EGP
   Рейтинг канала: 6(483) Репутация: 34 Сообщения: 531 Откуда: Украина Зарегистрирован: 21.11.2008
 |
|
Тебе наверное проще в Файле Tships назначить твоему
кораблю абордажную капсулу и будет тебе счастье.
Если конечно я тебя правильно понял.
Последний раз редактировалось: XT3_Traktor (09:03 01-07-2011), всего редактировалось 1 раз |
|
|
Artaazar
|
|
Нет, неправильно.
1) Я хочу понять где я ошибся в своих рассуждениях. благодаря этому наследию и м7 и м1 и пр.. должны высаживать десант...
2) ...только в том случае если есть на борту телепортатор. В одной из тем обсуждалось, что логично будет, если корабли которые могут таскать десант, но почему-то лишены возможности высаживать его, благодаря моду смогут все же отправлять людей на абордаж.
|
|
|
ULiX
320 EGP
      Репутация: 146 Сообщения: 573 Откуда: Комсомольск на-Амуре Зарегистрирован: 12.07.2005
 |
|
CheckerTwo : |
Итак, при вызове функции "SaveStack" для доступа к первому аргументу мы должны использовать смещение указателя стека sp[3], для второго - sp[4] и так далее. В стеке со смещение sp[2] содержится число аргументов функции. Для чего нужны sp[0] и sp[1] - это тайна, закрытая ES, факт тот, что в них хранятся нули. Это еще раз подтверждает предположение о двустековости ВМ Х3.
|
Сделал вывод с недосказкой для непосвященных. Тут прям каждый второй занимался отладкой программ на ассемблере. Для "тех, кто в танке" CheckerTwo хотел видимо сказать, что для "одностековой" структуры в стек помещается адрес возврата из функции в аккурат рядом с аргументами, а мы его там не наблюдаем.
Приведенный пример демонстрирует вызов глобальной функции из глобальной же функции. А проверялось ли значение sp[0] и sp[1] при вызовах классовой функции из глобальной, или глобальной из классовой?
Я что-то устал по форуму рыскать. Дайте кто-нибудь ответ, транслятор XCtoXAsm для Земного конфликта имеется? Или не судьба родиться моим проектом на Земле?
_________________ Новое ещё не значит лучшее |
|
|
CheckerTwo
550 EGP
     Рейтинг канала: 6(486) Репутация: 103 Сообщения: 412 Откуда: Tomsk Зарегистрирован: 18.08.2004
 |
|
О, давно не видно было. Привет
ULiX : |
Сделал вывод с недосказкой для непосвященных. Тут прям каждый второй занимался отладкой программ на ассемблере. Для "тех, кто в танке" CheckerTwo хотел видимо сказать, что для "одностековой" структуры в стек помещается адрес возврата из функции в аккурат рядом с аргументами, а мы его там не наблюдаем.
|
Да-да, все верно.
ULiX : |
Приведенный пример демонстрирует вызов глобальной функции из глобальной же функции. А проверялось ли значение sp[0] и sp[1] при вызовах классовой функции из глобальной, или глобальной из классовой?
|
Ага, к сожалению эта мысль пришла уже позже, а времени проверить ее не было. Потом начались с работой заморочки... Вполне возможно, что при вызовах классовых функций в стеке есть что-то интересное.
ULiX : |
Я что-то устал по форуму рыскать. Дайте кто-нибудь ответ, транслятор XCtoXAsm для Земного конфликта имеется? Или не судьба родиться моим проектом на Земле?
|
Увы, Darth пропал куда-то. Больше никто не решается...
PS: Поздравляю с сынишкой
|
|
|
ULiX
320 EGP
      Репутация: 146 Сообщения: 573 Откуда: Комсомольск на-Амуре Зарегистрирован: 12.07.2005
 |
|
Не мог бы кто-нибудь выложить оригинальный obj файл.
Дизассемблю, гляну, что изменилось.
Раз некому взяться за компилятор, то возможно мне удастся осилить эту задачу. Сейчас осиливаю теорию. Пока что научился только выполнять простую арифметику. Странное дело, в голове я прекрасно представляю как заменить структуры KC в XASM, но вот чтоб автоматизировать все нюансы и компилировать без ошибок...
В общем я понял, что компилировать в XASM одинаково приятно, что компилировать напрямую в байткод. Но в связи с отсутствием у меня информации о работе с секциями символов неизбежно придётся использовать как промежуточный язык XASM.
to CheckerTwo
Может поделишься вкратце, как находятся границы секций в obj файле и информацией по структуру секций. И о методе шифровки секций. Надеюсь шифровка производится не использованием XOR с фиксированным числом, как в dat файлах
_________________ Новое ещё не значит лучшее |
|
|
|
|
|
Канал X3: Terran Conflict ->
Модовый и скриптовый отсек X3: Terran Conflict: «X3TC Моддинг: obj-файлы и маленькие утилитки» |
|
К списку каналов | Наверх страницы |
Цитата не в тему: А ты не вчитывайся, а для ускорения - читай только каждое третье слово, гарантирую - смысл постов останется тем же. (советует Harley)
|
» X3TC Моддинг: obj-файлы и маленькие утилитки | страница 4 |
|