ROM Hacking / Romhacking / Ромхакинга тред

⭕️ Romhacker.0 ROM Hacking / Romhacking / Ромхакинга тред 25.04.2023 20:12 #37557

post media
post media
post media

В один момент я захотел заняться реверс инжинирингом ПО, научиться читать ассемблер, крякать програмки и всё такое. Но все мои попытки научиться никогда не получали консистентного сосредоточения. Тогда я решил взяться за игры. Я решил, что займусь портированием моей любимой игры с Sega Megadrive под названием "Dune 2: The battle for Arrakis". Целевая платформа - это Linux на любой современной архитектуре в виде обычного приложения, а точнее реверс игры вплоть до воссоздания исходников, из которых можно собрать программу под Linux. Мне кажется, что ковыряние в моей любимой игре - это единственное, что могло бы удерживать мой интерес достаточно долго и непрерывно, чтобы в самом деле этим заниматься, а не забрасывать на три года после пары недель. И, хотя, я всё равно иногда это дело забрасываю, но не на три, а всего лишь на полгода. Игровая мотивация работает! К тому же игры на сегу не очень большие и архитектура мегадрайва не самая сложная, так что должно быть не убийственно тяжело, в отличие от игр на ПК.

Основная моя цель - это поизучать инструменты и методы обратного инжиниринга. Ромхакинг в данном случае - это всего лишь форма, призванная удержать и направить моё стремление.

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

Ответы:
>>37575
>>41214

🔰 Romhacker.0 Дизассемблер m68k 25.04.2023 21:04 #37575

post media
>>37557

Сильно перескочу, но расскажу про то, чем я занят сейчас. Я пишу дизассемблер для машинного кода архитектуры Motorola 68000 - именно этой архитектурой обладает главный процессор мегадрайва. Там есть ещё Zilog Z80 и какой-то мелкий кастомный PSG для звука, ввода-вывода и совместимости с играми Sega Master System, но я собираюсь их тщательно игнорировать.

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

Я решил, что буду использовать привычный мне ембеддерский инструментарий - GCC и Binutils, потому что я их очень люблю и считаю во многом правильно выполненными инструментами. Исходники постараюсь воссоздать на языке C или даже C++, но, скорее всего, не обойдётся и без кусков на ассемблере.

Существующие дизассемблеры для m68k ни разу не совместимы с GNU AS с триплом m68k-*-* - я пробовал Capstone, Ghidra и даже m68k-*-*-objdump. Кроме того, не все из них понимают когда нужно остановиться и не превращать дизассемблирование в балаган, то есть не пытаться интепретировать данные как инструкции. Это и не удивительно, потому что без динамического анализа это никак не понять, а как к ним прикрутить динамический анализ я так и не придумал.

Зато я придумал как использовать динамический анализ, если я напишу свой дизассемблер! Мне нужно всего лишь собрать все места по которым прошёлся Program Counter в эмуляторе, пока я играю в игру и просто дизассемблеровать все инструкции по этим адресам. Некоторый proof of concept у меня уже готов - я реализовал дизассемблирование всех инструкций прыжков и вызовов функций и сделал расставление меток в тех местах, куда они прыгают.

В аргументах инструкций всего лишь сырые числа - относительные или абсолютные адреса. Чтобы связать их с метками, нужно сделать граф вызовов и тщательно его обойти, дизассемблируя всё по пути. А граф может быть циклическим. А ещё прыжки в теории могут быть не на начало инструкции, а на середину длинной инструкции, хотя процессор от такого скорее всего в какой-нибудь bus-fault выпадет через несколько циклов, когда наткнётся на illegal инструкцию, но это всё-таки возможно. Пока вместо линковки у меня просто комменты с адресами.

Получающийся disasm.S я могу собрать обратно в бинарь и он будет неотличим от оригинала!


/tmp/a $ m68k-none-elf-as disasm.S -o a.o && m68k-none-elf-objcopy -O binary a.o a.bin

/tmp/a $ cmp a.bin dune2u.bin

/tmp/a $ sha1sum dune2u.bin a.bin

0f7c1c130cb39abc97f57545933e1ef6c481783d  dune2u.bin

0f7c1c130cb39abc97f57545933e1ef6c481783d  a.bin

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

Ответы:
>>42857

🔰 Romhacker.0 04.05.2023 20:46 #41214

>>37557

В ромах игры "Dune 2: The Battle for Arrakis" можно найти любопытную строчку про "Math library" и "C standard library", которая как бы намекает, на то, что при разработке использовался язык си и сишный компилятор. Вот так я грепаю с помощью radare2 и grep два варианта бинаря - USA и Europe:


$ r2 -nqc 'izz' dune2u.bin | grep 'Math library'

5073  0x000492e7 0x000492e7 124  125          ascii   dN^Nu\nMath library must precede standard C library on linker command line\n to access printf() that supports floating point.\n

$ r2 -nqc 'izz' dune2e.bin | grep 'Math library'

5006  0x000492df 0x000492df 124  125          ascii   \N^Nu\nMath library must precede standard C library on linker command line\n to access printf() that supports floating point.\n

Немного по разным смещениям, но одна и та же строчка находится в обоих бинарях.

Недолго думая, гуглю "sega genesis c compiler" и нахожу такой веб-сайт с некоторыми инструментами разработки для мегадрайва: http://techdocs.exodusemulator.com/Console/SegaMegaDrive/Software.html.

Скачиваю "Sierra C Compiler 3.1b", распаковываю и грепаю. Это не первый сайт и компилятор, что я проверил, а где-то третий, наверно. Тем не менее вот что получается:


SIERRA $ grep -rnI . | grep 'Math library'

LIB/SRC/SCANF.C:610:            fputs("\nMath library must precede standard C library on "

LIB/SRC/PRINTF.C:433:           fputs("\nMath library must precede standard C library on "

Очень похоже. В исходнике "LIB/SRC/PRINTF.C" это выглядит примерно так:


static int _do_prnt(register FILE *fp, const char *fmt, va_list ap)

{

// ...

#ifdef FLOAT

// ...

#else

fputs("\nMath library must precede standard C library on "

"linker command line\n to access printf() that supports "

"floating point.\n", stderr);

exit(1);

#endif

// ...

}

Как видно, строка идентичная. Просто дюну кодили без софтверной поддержки чисел с плавающей запятой (они там и не нужны). Из чего я делаю вывод, что для разработки дюны использовался именно этот тулчейн. Это, конечно, не значит, что разрабы писали только на си и только с помощью этого тулчейна. Они могли взять из него только реализацию функции printf и продолжкить кодить на смеси асма и си вообще с другими компилятором и ассемблером. Но я буду полагать, что большая часть кода была написана именно на си и с использованием этого тулчейна "Sierra 3.1b". Хотя даже версия компилятора у разрабов дюны могла отличаться.

Собственно, есть идея при ручной декомпиляции для проверки компилить этим компилятором получающиеся сишные куски и посмотреть, получится ли буквально такой же машинный код или нет. Если это именно тот компилятор, что был у разрабов дюны, то я смогу в теории воссоздать исходники так, чтобы у меня после их компиляции получится буквально такой же ром, как тот, что я реверсю - это будет просто оргазм ОКРщика.

Ответы:
>>51052
>>54322

🔰 Romhacker.0 08.05.2023 21:41 #42857

post media
>>37575

Тем временем я реализовал все инструкции в дизассемблере. Нужно было снять риск, что я наткнусь на непреодолимые баги GNU AS. Я всё-таки наткнулся на парочку моментов, когда пришлось костылить из-за своеобразного поведения GNU AS, но ничего критичного - всё обошёл на чилле. Теперь нет риска с самими инструкциями. В роме Dune 2 все инструкции, которые прошёл эмулятор, успешно дизассемблированы и из листинга от дизассемблера собирается в точно такой же бинарь рома с точностью до битика.

Пикрил - то же самое место в роме Dune 2. Как видно, метка .L000015f8 пропала - это потому, что я немного неправильно строю граф прыжков, о котором я писал ранее, а точнее вообще не строю граф как таковой, а надо его строить, поэтому я убрал генерацию меток в некоторых кейсах (как, например, в данном случае для PC-relative прыжков). Всё будет, только вот порефакторю и потом сделаю нормальный обход графа прыжков с учётом закольцованности и прочими перделками.

🔰 Romhacker.0 20.05.2023 17:42 #47639

post media

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

🔰 Romhacker.0 Phantasy Star 03.06.2023 10:44 #50801

Вчера решил посмотреть на две другие мои любимые игры: "Phantasy Star II" и "Phantasy Star - The End of the Millenium" (по-другому "Phantasy Star IV"), которые упомянуты в ОП-посте в виде пикч.


sha1sum 0711080e968490a6b8c5fafbb9db3e62ba597231 Phantasy Star II (UE) (REV02) [!].bin

sha1sum bc7ff6d6a8408f38562bc610f24645cad6c42629 Phantasy Star - The End of the Millenium (U) [!].bin

Эти игры разрабатывались самой компанией SEGA для своей же консоли. Я провёл элементарный поверхностный анализ: прогнал ромы через команду strings и не увидел почти никаких адекватных срок - сплошное разочарование. Интерес представляет разве что вот эта строка:


SEGA MEGA DRIVE (C)SEGA 1988.NOVPHANTASY STAR 2       BACKUP RAMPROGRAMMED BY          NAKA YUJI

Вообще, я надеялся увидеть там, как в дюне, артефакты процесса разработки. К примеру, как я писал в >>41214, в дюне можно найти строку, указывающую на то, что вероятно использовался сишный компилятор и я даже смог его определить. Кроме того, в ром дюны попали имена файлов ресурсов, типа SCENA001.INI или SCENO009.INI и даже их содержимое плейнтекстом! Типа того:


[Harkonnen]

Decay=2

Special=Missile

Recharge=60

Weakness=89

LemonFactor=33

Voice=H

Frigate=10

Слабо верится, что в игру встроен парсер init файлов. Это, конечно, могло быть сделано нарочно, как психологический приём - забить часть рома мусором, чтобы потом, когда игра не будет помещаться в мебибайт, даже со значительными оптимизациями, выкинуть мусор и продолжить добавлять контент.

Но в играх "Phantasy Star" нет ничего подобного. Аккуратные и педантичные японцы запаковали весь текст, видимо, с целью его пожать и сэкономить на размере ROM чипа, а может просто потому-что могут, кто их знает. К слову, размер образа рома "Phantasy Star II" составляет меньше мегабайта - всего 768 КиБ. А вот образ "Phantasy Star IV" весит уже ровно три мебибайта (3072 КиБ).

То, что я не нашёл следов компилятора си, наталкивает на мысли, что игры "Phantasy Star" написаны на ассемблере и тогда нет смысла пытаться их декомпилировать. Там, конечно, найдутся функции, и calling convention у них, скорее всего, будет унифицированный для всего рома по меньшей мере, и некоторые из них я даже смогу воспроизвести компилятором из кода на си. Но всё равно осадочек остаётся и интерес во мне эта ситуация не подогревает. Но покопаться ещё стоит. В крайнем случае дизассемблирую и оставлю всё в ассемблере.

А вот что подогревает мой интерес, так это используемый в игре алгоритм упаковки и распаковки ресурсов, таких как текст. В игре очень много диалогов, всё-таки обе эти игры являются большими JRPG (на несколько десятков часов прохождения) по моим меркам. Буду надеться, что в этих играх хотя бы процедуры в оперативную память не загружаются для выполнения их оттуда.

🔰 Romhacker.0 SLEIGH 04.06.2023 22:35 #50815

У меня есть идея, что я мог бы кроме естественной трассы program counter'а статическим анализом обнаружить ещё больше достижимого кода. Например, switch-case таблицы, у которых не все случаи были покрыты в ходе игры на эмуляторе, но очевидно, что эти не пройденные кейсы достижимы при определённых условиях и компилятор для них сгенерировал код.

Для такого анализа можно воспользоваться библиотекой SLEIGH, которая является сердцем проекта Ghidra - инструмента реверс инжиниринга, который, кстати (сейчас будет страшно), создан американским агентством нацбезопасности (АНБ). Во-первых я мог бы помочь SLEIGH анализатору, указав ему на места, по которым эмулятор прошёлся во время реального исполнения и тогда SLEIGH на основе этих данных динамического анализа нашёл бы ещё больше потенциально достижимого кода. Во-вторых, я мог бы извлечь трассу того, что SLEIGH проанализировал, что является потенциально достижимым кодом, но ни разу на практике у меня не исполнялся.

На выходных я в очередной раз поковырял бинарь дюны в GHIDRA и пришёл к выводу, что использовать полустатический анализ из SLEIGH может быть опасно. Всё потому, что по смещению 0x00029ab4 там запихана буквальная копия фрагмента бинаря с самого начала неизвестной длины и она, естественно, никогда не исполняется при игре на эмуляторе. В роме может быть ещё куча неиспользуемых функций, вплетённых в код среди настоящих функций или другого мусора, добавленного с непонятной целью. Я пока что боюсь ложно позитивных результатов, так как я расцениваю образ рома как экспонат и ищу пути раздербанить его максимально аккуратно, так сказать, посчитав все спички в каждом коробке. Если так, то мне всё равно придётся весь образ рома вручную откомментировать (примерно как сделано с играми Sonic и Sonic 2) и декомпилировать до си всё, что возможно. Тогда мне и статический анализ не сдался - я проанализирую всё вручную.

Но тем не менее статический анализ должен упростить жизнь и вряд ли там будут ложные срабатывания на коде, произведённом компилятором, если придерживаться одного правила: не пытаться вручную указывать дизассемблеру на что попало, а строить анализ исключительно на достоверных данных, полученных от эмулятора в ходе игры. Это значит, что моему кастомному анализатору на базе SLEIGH быть!

Ещё одна идея - это трассировать доступ на чтение к данным, которые не являются инструкциями, то есть program counter по ним не проходит, но данные читаются во время выполнения инструкций, типа MOVE, ADD и так далее. Таким образом у меня будет уже три класса данных: однозначно инструкции, однозначно данные и неведомая ерунда, которой будет уже значительно меньше.

🔰 Romhacker.0 09.06.2023 06:25 #50945

post media
post media
post media

Вчера немного покрыл комментариями и смыслом некоторые функции в дизассембле бинаря Dune II. Параллельно реверсил и допиливал/отлаживал свой эмулятор gut, который я строю специально для глубокого динамического анализа и отладки при реверсе игр на сегу. Выглядит это примерно вот так:


.globl	prepare_ram

.type	prepare_ram, @function

| It looks to me like this is a stack coloring routine alongside with cleaning

| RAM with zeros once again.

prepare_ram:

| Fill almost all RAM with zeros, except for 40 (0x28) last bytes

movew #0x3ff6,%d0 | 303c 3ff6 @00000496

moveql #0x0,%d1 | 7200 @0000049a

leal 0xffff0000:l,%a0 | 41f9 ffff 0000 @0000049c

1:	movel %d1,%a0@+ | 20c1 @000004a2

dbf %d0,1b | 51c8 fffc @000004a4

| At this point a7 = 0xfffffff2, which is set to 0xfffffffa at @000017a4 and

| then decremented twice by two function calls to become 0xfffffff2.

| Some bitshift fuckery with stack pointer address to set d0 as following:

| d0 = ((((a7 & 0xffff) - 0xf3b0 + 1) >> 1) - 1) = 0x624

leal 0xfffff3b0:w,%a0 | 41f8 f3b0 @000004a8

movew %a7,%d0 | 300f @000004ac

subw %a0,%d0 | 9048 @000004ae

addqw #0x1,%d0 | 5240 @000004b0

lsrw #0x1,%d0 | e248 @000004b2

subqw #0x1,%d0 | 5340 @000004b4

| Fill 0xfff3b0..%a7 (0xfff3b0..0xfffff2) range with 0xffff

moveql #0xffffffff,%d1 | 72ff @000004b6

1:	movew %d1,%a0@+ | 30c1 @000004b8

dbf %d0,1b | 51c8 fffc @000004ba

rts | 4e75 @000004be

.size	prepare_ram, .-prepare_ram

И ещё:


.globl	vdp_vram_fill_zeros_dma

.type	vdp_vram_fill_zeros_dma, @function

vdp_vram_fill_zeros_dma:

movew %sr,%a7@- | 40e7 @000004e8

oriw #0x700,%sr | 007c 0700 @000004ea

leal 0xc00004:l,%a0 | 41f9 00c0 0004 @000004ee

movew g_vdp_modeset2_mirror:w,%d0 | 3038 e00c @000004f4

| VDP Mode Set 2 |= 0x10 (DMA Enabled)

oriw #0x10,%d0 | 0040 0010 @000004f8

movew %d0,%a0@ | 3080 @000004fc

| Busy wait until VDP DMA gets idle

1:	movew %a0@,%d0 | 3010 @000004fe

btstl #0x1,%d0 | 0800 0001 @00000500

bnes 1b | 66f8 @00000504

| VDP Auto Increment = 1

movew #0xffff8f01,%a0@ | 30bc 8f01 @00000506

| VDP DMA Counter Low = 0xff

movew #0xffff93ff,%a0@ | 30bc 93ff @0000050a

| VDP DMA Counter High = 0xff

movew #0xffff94ff,%a0@ | 30bc 94ff @0000050e

| VDP DMA Source Address High = 0x80

movew #0xffff9780,%a0@ | 30bc 9780 @00000512

| VDP Address Mode = 0x21 (DMA Memory to VRAM/VRAM fill, VRAM write)

movel #0x40000080,%a0@ | 20bc 4000 0080 @00000516

| VDP DMA Fill @000000..00ffff = 0x0000

movew #0x0,%a0@(-4:w) | 317c 0000 fffc @0000051c

| Busy wait until VDP DMA gets idle

1:	movew %a0@,%d0 | 3010 @00000522

btstl #0x1,%d0 | 0800 0001 @00000524

bnes 1b | 66f8 @00000528

| Restore VDP Mode Set 2 register state as it was at the start of the function

movew g_vdp_modeset2_mirror:w,%a0@ | 30b8 e00c @0000052a

movew #0xffff8f02,%a0@ | 30bc 8f02 @0000052e

movew %a7@+,%sr | 46df @00000532

rts | 4e75 @00000534

.size	vdp_vram_fill_zeros_dma, .-vdp_vram_fill_zeros_dma

Кусочек, откуда они вызываются:


.globl	L00000d54

.type	L00000d54, @function

L00000d54:

jsr check_rom_integrity:l | check_rom_integrity | 4eb9 0000 292c @00000d54

moveal #0xc00004,%a5 | 2a7c 00c0 0004 @00000d5a

moveal #0xc00000,%a6 | 2c7c 00c0 0000 @00000d60

| %a5 and %a6 are not used in the following BSR subroutine

bsrw prepare_ram | prepare_ram | 6100 f72e @00000d66

| VDP Mode Set 2 = 0x24 (Vertical interrupt enabled | V30 CELL mode)

movew #0xffff8124,0xc00004:l | 33fc 8124 00c0 0004 @00000d6a

movew #0xffff8124,g_vdp_modeset2_mirror:w | 31fc 8124 e00c @00000d72

bsrw vdp_vram_fill_zeros_dma | vdp_vram_fill_zeros_dma | 6100 f76e @00000d78

bsrw vdp_vsram_fill_zeros | vdp_vsram_fill_zeros | 6100 f7b8 @00000d7c

bsrw vdp_cram_fill_zeros | vdp_cram_fill_zeros | 6100 f69a @00000d80

| VDP reg 0x00(ModeSet1) = 0x04 (AllBitsFromColor(2))

movew #0xffff8004,%a5@ | 3abc 8004 @00000d84

| VDP reg 0x07(BackgroundColor) = 0x00

movew #0xffff8700,%a5@ | 3abc 8700 @00000d88

...

Эти функции вызываются сильно в начале ROM'а. По моим ощущениям смысла в листинге стало сильно больше. Наверно это потому что я только начал, потом приестся и буду искать способы это автоматизировать или привлекать помощь.

Скрины с раскраской синтаксиса:

🔰 Romhacker.0 Первая серьёзная попытка декомпиляции 17.06.2023 14:00 #51052

post media
post media
post media
>>41214

Настало время убедиться, что компилятор Sierra - это именно тот компилятор, что использовался при разработке Dune II и имеющаяся у меня версия будет выдавать нужный мне машинный код. Для этого я решил найти какую-нибудь достаточно сложную функцию и попробовать её декомпилировать. Она должна отвечать следующим требованиям:

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

  • Не иметь вызовов других функций, что значительно упростит работу по восстановлению её исходника на языке си.

Для начала стоит упомянуть как я запускаю компилятор:


LD_LIBRARY_PATH=$HOME/opt/dosemu2/usr/local/lib/fdpp DOSEMU2_COMCOM_DIR=$HOME/.dosemu/drive_c dosemu -dumb -K ./ -E "COM68.EXE -Of1 a.c a.asm"

Довольно криво собран dosemu2, поэтому используются хитрости с LD_LIBRARY_PATH. Кроме того, компилятору потребовалась опция -Of1, чтобы функции устанавливали свой стек инструкциями link и unlk - это я выяснил в процессе, описанном далее и через чтение документации по компилятору.

Выхлоп этой команды получается такой:


dosemu2 2.0pre9-dev-20230520-1135-g1677e03e1 Configured: 2023-05-29 19:52:34 +0000

Get the latest code at http://dosemu2.github.io/dosemu2

Submit Bugs via https://github.com/dosemu2/dosemu2/issues

Ask for help in mail list: linux-msdos@vger.kernel.org

This program comes with ABSOLUTELY NO WARRANTY.

This is free software, GPL v2 (or any later version) distribution conditions.

FDPP kernel 1.6 [GIT: 1.6-152-gb7169e9] (compiled May 29 2023)

Kernel compatibility 7.10 - clang - FAT32 support

Written by Stas Sergeev, FDPP project.

Based on FreeDOS sources (C) Pasquale J. Villani and The FreeDOS Project.

This program is free software: you can redistribute it and/or modify

it under the terms of the GNU General Public License as published by

the Free Software Foundation, either version 3 of the License, or

(at your option) any later version.

- InitDisk:

C: HD1, Pri[ 1], CHS=    0-1-1, start=     0 MB, size=  2000 MB

D: HD2, Pri[ 1], CHS=    0-1-1, start=     0 MB, size=  2000 MB

E: HD3, Pri[ 1], CHS=    0-1-1, start=     0 MB, size=  2000 MB

dosemu XMS 3.0 & UMB support enabled

dosemu EMS driver rev 0.9 installed.

EMUFS host file and print access available

dosemu CDROM driver installed (V0.2)

Process 0 starting: C:\command.com /e:512 /k %FDPP_AUTOEXEC%

BLASTER=A220 I5 D1 H5 P330 T6

MIDI=SYNTH:2 MAP:E MODE:0

Welcome to dosemu2!

Build 2.0pre9-dev-20230520-1135-g1677e03e1

68000 C Compiler 3.1b Copyright 1987-94 by Sierra Systems. All rights reserved.

DOS Extender Copyright 1990-93 by Rational Systems, Inc.

ERROR: alsa_midi:default (ALSA err 0): Cannot get card index for 1

snd-virmidi module not loaded or device "default" not configured

see "amidi -l" for the list of midi devices

ERROR: alsa_midi:hw:1,0 (ALSA err 0): Cannot get card index for 1

snd-virmidi module not loaded or device "hw:1,0" not configured

see "amidi -l" for the list of midi devices

ERROR: unknown window sizes li=0  co=0, setting to 80x25

Это ужасно. Это просто мусор, в котором очень трудно разглядеть вывод компилятора. Я подумываю о портировании этого компилятора на 64-битный Linux, потому что а) я не знаком толком ни с DOS ни с dosemu2, б) этот мусорный лог мне не нравится и в) эта команда выполняется полторы-две секунды, что очень долго. Это стало бы ещё одним большим проектом наряду с данными попытками декомпиляции Dune II. Но пока поживу так неопределённое время.

Что касается функции для декомпиляции, мой взгляд упал на функцию по адресу 0x00049db8, так как она подходила под требования и я взялся за работу. Сначала я взял результат декомпиляции, который выдал Ghidra по этому коду. Он был очень сложным и непонятным и после компиляции Sierra давал совсем другой код, чем из оригинального ROM'а. Я сразу же решил отказаться от Ghidra и взяться за восстановление исходника вручную.

Я провёл за этим занятием пять часов, в итоге очень хорошо попал в структуру кода и в каждую инструкцию, за одним исключением - номера регистров, которые использовал компилятор, оказывались не теми. Это ужасное ощущение, когда логически всё сходится, но это всё равно не то. Однако, совпадение структуры в меня вселяло веру, что компилятор может выдать нужный мне код, только я не понимаю как его заставить это сделать. На картинке ниже можно видеть что получается после компиляции по сравнению с тем, что выдаёт дизассемблер из оригинального бинаря.

Вообще, свободы в данном случае не очень много, потому что компилятор поддерживает только стандарт C89, а он несколько ограничен. Например, переменные должны быть объявлены только в начале блока функции или другой конструкции типа if или for, в общем в начале любого блока, обозначаемого фигурными скобками {}. Таким образом создание нового блока на ровном месте может привести к другому результату компиляции.

Эту увлекательную подробность о стандарте C89 я узнал только вчера, за что благодарен данной затее с декомпиляцией. Теперь я понимаю почему во многих исходниках на си можно встретить объявление всех переменных в начале функции - в С89 просто нельзя иначе по правилам синтаксиса. Этот подход с объявлением переменных в начале фукнции до сих пор сохранился в привычках у некоторых программистов с многолетним опытом.

Так же в C89 было популярно ключевое слово register, объявленное устаревшим в стандарте C11 - оно должно служить подсказкой компилятору о том, что переменную стоит поместить в выделенный регистр на время жизни блока, а не на стек. Так что в теории спецификатор register может влиять на результат компиляции. Это наоборот добавляет степеней свободы к возможным вариантам исходного кода.

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


$ man 3p memcpy

void *memcpy(void dest[restrict .n], const void src[restrict .n],

size_t n);

В архиве с компилятором Sierra я видел исходники LibC. Взглянув туда я, конечно же нахожу MEMCPY.C, открываю, и обнаруживаю структурно идентичную функцию той, что восстановил я. Быстренько компилирую и сравниваю код на ассемблере:

Чуть слёзы на глаза не наворачиваются от радости - это идентичный код, даже номера регистров совпадают! Да, я нашёл его! Я на правильном пути! Этот компилятор мне подходит, потому что такая довольно сложная функция при правильно восстановленном исходнике даёт в результате компиляции идентичный машинный код. Я в восторге!

При более пристальном сравнении моих результатов декомпиляции и исходника memcpy оказывается, что я допустил пару ошибок в циклах копирования. Одна из них на скриншотах в данном посте исправлена, а вторая нет. Вторую ошибку я заметил уже только когда составлял коллажи из скриншотов к этому посту. Вполне возможно, что есть и ещё ошибки, которые я до сих пор не заметил. Вот сравнение исходников моего результата и оригинального кода memcpy.c:

Затем я взялся проверять получившийся код на ассемблере. Сложность состоит в том, что синтаксис асма Sierra не совместим с синтаксисом GNU AS. А именно - все регистры в синтаксисе GNU AS должны быть с префиксом %, то есть %d1, %a6 и %pc, а не просто d1, a6 и pc. Так же GNU AS не знает некоторые директивы Sierra, например .opt rngchk. Все эти различия довольно просто исправить с помощью sed или awk. В данном случае я сделал это вручную, чтобы убедиться, что с этот код можно встроить в ROM вместо оригинального и его хэш сумма ROM'а останется прежней.

Но тут меня ждала ещё одна мелкая неприятность: Sierra иногда генерирует bne без указания размера смещения прыжка, на что ассемблер Sierra генерирует bne.s, то есть короткий однобайтный прыжок. Однако GNU AS на такое всегда выдаёт bne.w, то есть прыжок с двухбайтным смещением, и получается расхождение в конечном бинаре. После исправлений вручную всё собирается и бинарь сохраняет свою хэш сумму.

С этим коротким прыжком, я уверен, не всё так просто, когда-нибудь он окажется настолько длинным, что придётся использовать bne.w, потому что в один байт смещение для прыжка уже не будет помещаться. Таким образом, тупая замена bne на bne.s не подойдёт, или, точнее, подойдёт в большинстве случаев, а когда-нибудь не подойдёт. Как заставить GNU AS использовать bne.s по возможности, я не смог выяснить. Не знаю что с этим делать пока что, кроме тупой текстовой замены bne на bne.s. Да и сколько ещё меня ждёт таких подводных камней?

Следующей на очереди у меня должна быть функция do_print, о которой я писал ранее (>>41214). В ней используется строка текста, которую компилятор может запихнуть в какую-нибудь секцию типа .rodata и тогда я буду вынужден рассказывать линкеру как раскладывать .rodata куски вперемежку с кодом, чего я хотел бы избежать. Либо это будет ещё один шаг в конвертации ассемблерного листинга sed-ом или awk-ом.

Ответы:
>>54322

🔰 Romhacker.0 18.10.2023 18:43 #52804

Сегодня я допилил транслирующий ассемблер, чтобы преобразовывать асмовый выхлоп Sierra C Compiler в такй код на асме, который понимает GNU AS и производит по нему то же самое, что должен производить Sierra ASM68.EXE. Зачем я его пилил вообще? А затем, что реальной проблемой стало вот это, о чём я писал ранее:

>>51952
> С этим коротким прыжком, я уверен, не всё так просто, когда-нибудь он окажется настолько длинным, что придётся использовать bne.w, потому что в один байт смещение для прыжка уже не будет помещаться. Таким образом, тупая замена bne на bne.s не подойдёт, или, точнее, подойдёт в большинстве случаев, а когда-нибудь не подойдёт. Как заставить GNU AS использовать bne.s по возможности, я не смог выяснить. Не знаю что с этим делать пока что, кроме тупой текстовой замены bne на bne.s.

Это стало проблемой, когда я полез декомпилировать do_print. Там я подтюнил флаги оптимизации компилятора, без которых получался немного другой выхлоп и там же обнаружилась проблема с bne.s и bne.w. Оказалось, что ASM68K.EXE вставляет прыжок по однобайтному смещению bne.s вместо bne, когда прыжок осуществляется назад на 126 или менее байт. В остальных случаях он ставит bne.w, то есть прыжок по двухбайтному смещению. Обычный скрипт на комбинации sed и awk для конвертации ассемблера никак не позволяет учесть таких особенностей.

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

Надеюсь, теперь я вернусь к делу и продолжу размечать libc в ближайшие недели.

🔰 Romhacker.0 08.01.2024 23:11 #54322

post media
>>41214
>>51052

На сегодняшний день меня по большей части покинула вера в то, что получится воссоздать текст программы на языке си, из которого компилятор произведёт буквально такой же код игры, что имеется в роме дюны. С фукнциями из стандартной библиотеки всё вышло так хорошо, а вот с остальным ничего не получается. Я пытался восстановить две простые функции. С одной из них у меня получилось сгенерить в разной степени близкий код (либо регистры не те, либо лишнии инструкции вылазиют), но другая не поддаётся никаким сишным конструкциям, даже с использованием goto. Мне кажется, что значительная часть кода игры всё-таки написана на асме и тогда затея восстановления кода на си априори невыполнима с данным компилятором.

Придётся искать другие способы портировать код рома. Или вообще забить нафиг. Пока предпочту не думать об этом совсем какое-то время.

Ответы:
>>56323

🔰 Romhacker.0 PC-relative JSR оптимизация в линковщике LINK68.EXE 24.03.2024 19:41 #56323

>>54322

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

Теперь я столкнулся с другой проблемой: некоторые вызовы функций (инструкции jsr) в оригинальном ROM-е сделаны относительно PC (Program Counter), то есть относительно того адреса, с которого осуществляется джамп. И получились они из-за того, что линковщик link68.exe из тулчейна Sierra умеет так оптимизировать jsr вызовы, когда они находиятся в диапазоне +-64K отностиельно текущего адреса и при этом в разных объектниках. Относительный jsr выходит короче абсолютного на два байта, поэтому использование pc-relative jsr - это оптимизация по размеру. В рамках одного объектника такие оптимизации мог бы делать и ассемблер, но почему-то он этого не делает. И GNU LD из GNU Binutils таких оптимизаций тоже не делает.

Получается, что мне теперь нужно либо сделать утилиту, которая будет делать такую оптимизацию после трансляции в машинный код, либо реализовать такую оптимизацию в GNU Binutils LD. Всё это для того, чтобы получать идентичный ROM образ после компиляции из кода на си.

Ещё я мог бы полностью переключиться на тулчейн Sierra, но я решительно не хочу этого делать, потому что придётся запускать весь процесс сборки в dosemu, что очень громоздко и долго, мне и компилятора в dosemu хватает по горло. Так же не понятно как из COFF формата получить сырой ROM образ. Разбираться в синтаксисе линкер-скриптов Sierra я тоже не хочу. Но в принципе в COFF формате хранятся символы как и в ELF, а значит я мог бы удобно декомпилить COFF вместе со всеми символами, как я регулярно делаю с ELF-ом. В общем, полный переход на тулчейн Sierra - это нежелательный вариант, но его тоже можно рассмотреть.

⭕️ Anonymous 20.02.2026 23:37 #1771630660477518

Оп, ты куда пропал?