Пишем на Ассемблере для Sega Genesis #1

Первая статья посвященная написанию игр для классической приставки Sega Genesis на Ассемблере Motorola 68000.

Напишем простейший бесконечный цикл для Сеги. Для этого нам понадобятся: ассемблер, эмулятор с дизассемблером, любимый текстовый редактор, базовое понимание строения рома Сеги.

Для разработки я использую собственный ассемблер/дизассемблер Gen68KryBaby:

https://gitlab.com/demensdeum/gen68krybaby/

Тул разработан на языке Python 3, для сборки на вход подается файл с расширением .asm либо .gen68KryBabyDisasm, на выходе получается файл с расширением.gen68KryBabyAsm.bin, который можно запустить в эмуляторе, либо на реальной приставке (осторожно, отойдите подальше, приставка может взорваться!)

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

Структура

Заголовок рома Сеги занимает первые 512 байт. В нем содержится информация об игре, название, поддерживаемая периферия, чексумма, прочие системные флаги. Предполагаю, что без заголовка приставка даже не будет смотреть на ром, подумав, что он некорректный, мол “что вы мне тут даете?”

После заголовка идет сабрутина/подпрограмма Reset, с нее начинается работа процессора m68K. Хорошо, дело за малым – найти опкоды (коды операций), а именно выполнение ничего(!) и переход на сабрутину по адресу в памяти. Погуглив, можно найти опкод NOP, которые не делает ничего и опкод JSR который осуществляет безусловный переход на адрес аргумент, то есть просто двигает каретку туда куда мы его просим, без всяких капризов.

Собираем все вместе

Донором заголовка для рома выступила одна из игр в Beta версии, на данный момент записывается в виде hex данных.

ROM HEADER: 

 00 ff 2b 52 00 00 02 00 00 00 49 90 00 00 49 90 00 00 49 90 00...и т.д. 

Код программы со-но представляет из себя объявление сабрутины Reset/EntryPoint в 512 (0x200) байте, NOP, возврат каретки к 0x00000200, таким образом мы получим бесконечный цикл.

Ассемблерный код сабрутины Reset/EntryPoint:

SUBROUTINE_EntryPoint:
    NOP
    NOP
    NOP 
    NOP
    NOP
    JSR 0x00000200  

Полный пример вместе с заголовком рома:

https://gitlab.com/demensdeum/segagenesissamples/-/blob/main/1InfiniteLoop/1infiniteloop.asm

Далее собираем:

python3 gen68krybaby.py 1infiniteloop.asm

Запускаем ром 1infiniteloop.asm.gen68KryBabyAsm.bin в режиме дебаггера эмулятора Exodus/Gens, смотрим что m68K корректно считывает NOP, и бесконечно прыгает к EntryPoint в 0x200 на JSR

Здесь должен быть Соник показывающий V, но он уехал на Вакен.

Ссылки

https://gitlab.com/demensdeum/gen68krybaby/

https://gitlab.com/demensdeum/segagenesissamples

https://www.exodusemulator.com/downloads/release-archive

Источники

ROM Hacking Demo – Genesis and SNES games in 480i

http://68k.hax.com/

https://www.chibiakumas.com/68000/genesis.php

https://plutiedev.com/rom-header

https://blog.bigevilcorporation.co.uk/2012/02/28/sega-megadrive-1-getting-started/

https://opensource.apple.com/source/cctools/cctools-836/as/m68k-opcode.h.auto.html

Flame Steel Engine Runner

Представляю вашему вниманию Flame Steel Engine Runner – платформа для запуска мультимедийных приложений на основе тулкита Flame Steel Engine. Поддерживаются платформы Windows, MacOS, Linux, Android, iOS, HTML 5. Акцент разработки кода приложений сместился в сторону скриптовых – на данный момент добавлена поддержка JavaScript с помощью TinyJS, сам тулкит и движок продолжит разрабатываться на языках близким к железу (C, C++, Rust и т.п.)
Flame Steel Engine Runner Demo
На странице ниже можно покрутить кубик, писать код на JavaScript, загружать модели, звуки, музыку, код с помощью кнопки Upload files, и стартовать из файла main.js с помощью кнопки Run.
https://demensdeum.com/demos/FlameSteelEngineRunner/

KleyMoment – утилита для склеивания скриптовых файлов

Представляю вашему внимаю утилиту для склеивания скриптовых файлов – KleyMoment, также обратную утилиту для расклеивания файлов обратно. Утилиту можно использовать для склеивания JavaScript файлов в один.
Тул реализован на языке Python 3, имеет простейший интерфейс командной строки вида:

 
python3 KleyMoment.py расширениеФайлов директорияСодержащаяФайлы выходнойФайл 

Например рекурсивное склеивание js файлов из директории scripts в файл output.js

 
python3 KleyMoment.py js scripts output.js 

Также утилита для расклеивания файлов обратно AntiKleyMoment, на вход принимает склееный файл, например:

 
python3 AntiKleyMoment.py output.js 

Репозиторий:
https://gitlab.com/demensdeum/kleymoment/

Space Jaguar Action RPG 0.0.4

Первый прототип игры Space Jaguar Action RPG для Webassembly:

https://demensdeum.com/demos/SpaceJaguarActionRPG004/

https://demensdeum.com/demos/SpaceJaguarActionRPG004_NOTIMEOUT/

Загружаться будет долго (53мб) без индикации загрузки.

Space Jaguar Action RPG – симулятор жизни космического пирата. На данный момент доступны простейшие игровые механики:

  • летать в космосе
  • умирать
  • кушать
  • спать
  • нанимать команду
  • смотреть на неугомонный, быстролетящий поток времени

Из-за плохой оптимизации отрисовки 3D сцен в веб версии, недоступна возможность гулять в трехмерном окружении. Оптимизация будет добавлена в следующих версиях.

Исходный код движка, игры, скриптов доступен по лицензии MIT. Все предложения по улучшению принимаются крайне позитивно:

https://gitlab.com/demensdeum/space-jaguar-action-rpg

Скриншоты из нативной версии для Linux:

Как я не попал в мужика на шесте или история об удивительной изобретательности

В данной заметке я напишу о важности архитектурных решений при разработке, поддержке приложения, в условиях командной разработки.

Самодействующая салфетка профессора Люцифера Горгонзолы. Руб Голдберг

Во времена своей юности я работал над приложением для заказа такси. В проге можно было выбирать точку пикапа, точку дропа, рассчитывать стоимость поездки, тип тарифа, и собственно говоря, заказать такси. Приложение мне досталось на последнем этапе пред-запуска, после добавления нескольких фиксов приложение было выпущено в AppStore. Уже на том этапе вся команда понимала что реализована она очень плохо, паттерны проектирования не использовались, все компоненты системы были связаны намертво, в общем и целом, можно было ее записать в один большой сплошной класс (God object), ничего бы не изменилось, так как классы смешивали свои границы ответственности и в общей своей массе перекрывали друг друга мертвой сцепкой. Позже руководством было принято решение написать приложение с нуля, с использованием корректной архитектуры, что было выполнено и итоговый продукт был внедрен нескольким десяткам B2B клиентов.

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

Это был обычный рабочий день, от одного из заказчиков пришло задание немного доработать дизайн приложения – банально подвинуть на несколько пикселей вверх иконку в центре экрана выбора адреса пикапа. Что ж, профессионально оценив задачу в 10 минут я поднял иконку на 20 пикселей вверх, совершенно ничего не подозревая, я решил проверить заказ такси.

Что? Приложение больше не показывает кнопку заказа? Как это получилось?

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

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

Как это может происходить? Неужели *состояние* экрана зависит не от паттерна машины состояния, а от *представления* позиции мужика на шесте?

Все так и оказалось, при каждой отрисовке карты приложение *визуально тыкало* в середину экрана и проверяла что там, если там мужик на шесте то это значит что анимация сдвига карты закончилась и нужно показать кнопку. В случае когда мужика там нет — значит происходит сдвиг карты, и кнопку надо спрятать.

В примере выше прекрасно все, во первых это пример Машины Голдберга (заумные машины), во вторых пример нежелания разработчика как-то взаимодействовать с другими разработчиками в команде (попробуй разберись без меня), в третьих можно перечислить все проблемы по SOLID, паттернам (запахи кода), нарушение MVC и многое многое другое.

Старайтесь так не делать, развивайтесь во всех возможных направлениях, помогайте своим коллегам в работе. Всех с наступившим новым годом)

Ссылки

https://ru.wikipedia.org/wiki/Машина_Голдберга

https://ru.wikipedia.org/wiki/SOLID

https://refactoring.guru/ru/refactoring/smells

https://ru.wikipedia.org/wiki/Model-View-Controller

https://refactoring.guru/ru/design-patterns/state

Угадай группу

В данной заметке я опишу работу с текстовым классификатором fasttext.

Fasttext – библиотека машинного обучения для классификации текстов. Попробуем научить ее определять метал группу по названию песни. Для этого используем обучение с учителем при помощи датасета.

Создадим датасет песен с названиями групп:

__label__metallica the house jack built
__label__metallica fuel
__label__metallica escape
__label__black_sabbath gypsy
__label__black_sabbath snowblind
__label__black_sabbath am i going insane
__label__anthrax anthrax
__label__anthrax i'm alive
__label__anthrax antisocial
[и т.д.]

Формат обучающей выборки:

{__label__класс} {пример из класса}

Обучим fasttext и сохраним модель:

model = fasttext.train_supervised("train.txt")
model.save_model("model.bin")

Загрузим обученную модель и попросим определить группу по названию песни:

model = fasttext.load_model("model.bin")
predictResult = model.predict("Bleed")
print(predictResult)

В результате мы получим список классов на которые похож данный пример, с указанием уровня похожести цифрой, в нашем случае похожесть названия песни Bleed на одну из групп датасета.
Для того чтобы модель fasttext умела работать с датасетом выходящим за границы обучающей выборки, используют режим autotune с использованием файла валидации (файл тест). Во время автотюна fasttext подбирает оптимальные гиперпараметры модели, проводя валидацию результата на выборке из тест файла. Время автотюна ограничивается пользователем в самостоятельно, с помощью передачи аргумента autotuneDuration.
Пример создания модели с использованием файла тест:

model = fasttext.train_supervised("train.txt", autotuneValidationFile="test.txt", autotuneDuration=10000)

Источники

https://fasttext.cc
https://gosha20777.github.io/tutorial/2018/04/12/fasttext-for-windows

Исходный код

https://gitlab.com/demensdeum/MachineLearning/-/tree/master/6bandClassifier

x86_64 Assembler + C = One Love

В данной заметке я опишу процесс вызова функций Си из ассемблера.
Попробуем вызвать printf(“Hello World!\n”); и exit(0);

section .rodata
    message: db "Hello, world!", 10, 0

section .text
    extern printf
    extern exit
    global main

main:
    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

Все гораздо проще чем кажется, в секции .rodata мы опишем статичные данные, в данном случае строку “Hello, world!”, 10 это символ новой строки, также не забудем занулить ее.

В секции кода объявим внешние функции printf, exit библиотек stdio, stdlib, также объявим функцию входа main:

section .text
    extern printf
    extern exit
    global main

В регистр возврата из функции rax передаем 0, можно использовать mov rax, 0; но для ускорения используют xor rax, rax; Далее в первый аргумент передаем указатель на строку:

rdi, message

Далее вызываем внешнюю функцию Си printf:

main:
    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

По аналогии делаем передачу 0 в первый аргумент и вызов exit:

    xor rdi, rdi
    call exit

Как говорят американцы:
Кто никого не слушает
Тот плов кушает @ Александр Пелевин

Источники

https://www.devdungeon.com/content/how-mix-c-and-assembly
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html

Исходный код

https://gitlab.com/demensdeum/assembly-playground