( боже, кто запрограммировал .iris так, чтобы его нельзя было закоммитить? )
Текстовая RPG, написанная на ассемблере RISC-V 32-bit, симулируемая через QEMU.
«Вселенная решила, что не хочет, чтобы я занимался чем-то лучшим, например, обзавелся жизнью.»
Зависимости
riscv32-elf-as— ассемблер RISC-Vriscv32-elf-ld— компоновщик RISC-Vqemu-riscv32— эмулятор пользовательского режима RISC-V
На Arch Linux:
sudo pacman -S riscv64-elf-binutils qemu-user
Сборка и запуск
make build # ассемблировать и скомпоновать в main.bin
make run # собрать + запустить через qemu-riscv32
make debug # запустить с -strace для просмотра системных вызовов
Структура проекта
Extremis/
├── main.s # Точка входа (_start), вызывает run_chapter_1
├── src/
│ ├── include/
│ │ └── function.s # Макросы стекового фрейма startF / endF
│ ├── engine/
│ │ ├── print.s # print — печать одной строки с нулевым завершителем (a0)
│ │ └── utils.s # printl — печать списка строк (a0=адрес таблицы, a1=количество)
│ ├── chapters/
│ │ └── chapter_1.s # Логика главы 1; загружает строки вступления и вызывает printl
│ └── dialogue/
│ └── intro.s # Строковые данные для вступительной сцены
└── build/ # Скомпилированные .o файлы (повторяет структуру src/)
Архитектура
Движок написан на чистом 32-битном ассемблере RISC-V, ориентированном на ABI Linux через эмуляцию пользовательского режима QEMU.
Соглашение о стековом фрейме
Каждая функция использует макросы startF / endF из src/include/function.s для сохранения и восстановления ra, s0, s1, s2 в стеке:
startF # push: выделить 16 байт, сохранить ra/s0/s1/s2
...
endF # pop: восстановить ra/s0/s1/s2, освободить 16 байт
ret
Системные вызовы
Предупреждение: Номера системных вызовов среды RARS/MARS не работают под
qemu-riscv32. В этом проекте используются номера системных вызовов ABI Linux для RISC-V.
Номер системного вызова помещается в a7, вызов выполняется инструкцией ecall. Возвращаемое значение приходит в a0.
Вывод
| Назначение | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
Примечания |
|---|---|---|---|---|---|---|
| Печать строки | 4 |
64 |
1 (stdout fd) |
адрес буфера | длина | Linux НЕ останавливается на нуле — передавайте длину в байтах |
| Печать символа | 11 |
64 |
1 |
буфер символа | 1 |
сохраните символ в памяти, передайте его адрес |
| Печать целого числа | 1 |
— | целое число | — | — | нет аналога в Linux; сначала преобразуйте в строку |
| Печать шестнадцатеричного числа | 34 |
— | целое число | — | — | нет аналога в Linux; преобразуйте вручную |
| Печать беззнакового целого | 36 |
— | целое число | — | — | нет аналога в Linux; преобразуйте вручную |
Ввод
| Назначение | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
Примечания |
|---|---|---|---|---|---|---|
| Чтение строки | 8 |
63 |
0 (stdin fd) |
адрес буфера | макс. байт | Linux возвращает сырые байты, включая перевод строки |
| Чтение символа | 12 |
63 |
0 |
адрес буфера | 1 |
возвращает a0 = прочитано байт; символ в буфере |
| Чтение целого числа | 5 |
— | — | — | — | нет аналога в Linux; прочитайте строку, распарсите её |
Процесс
| Назначение | RARS a7 |
Linux a7 |
a0 |
Примечания |
|---|---|---|---|---|
| Выход | 10 |
93 |
— | RARS игнорирует a0; Linux читает его как код выхода |
| Выход(код) | 17 |
93 |
код выхода | Linux exit_group |
Память
| Назначение | RARS a7 |
Linux a7 |
a0 |
Возвращает | Примечания |
|---|---|---|---|---|---|
| Выделение (sbrk) | 9 |
214 |
байты (RARS) / новый адрес brk (Linux) | a0 = начало выделенного блока |
Linux brk работает иначе — вы устанавливаете новую вершину, а не размер |
Файловый ввод/вывод
| Назначение | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
a3 |
Примечания |
|---|---|---|---|---|---|---|---|
| Открыть файл | 13 |
56 |
dirfd (-100=CWD) |
адрес имени файла | флаги | режим | Linux использует openat |
| Чтение файла | 14 |
63 |
fd | адрес буфера | макс. байт | — | возвращает количество прочитанных байт |
| Запись в файл | 15 |
64 |
fd | адрес буфера | количество байт | — | возвращает количество записанных байт |
| Закрыть файл | 16 |
57 |
fd | — | — | — | возвращает 0 при успехе |
Время
| Назначение | RARS a7 |
Linux a7 |
a0 |
a1 |
Примечания |
|---|---|---|---|---|---|
| Время | 30 |
113 |
id часов (1=REALTIME) |
timespec* buf |
RARS возвращает разделенные lo/hi в a0/a1; Linux записывает структуру в buf |
| Сон | 32 |
115 |
id часов | timespec* |
RARS принимает миллисекунды в a0; Linux использует clock_nanosleep со структурой |
Добавление контента
- Новые строки диалогов помещаются в
src/dialogue/. - Новые главы помещаются в
src/chapters/и подключаются через.includeв файл главы, который их использует. - Makefile автоматически находит все
.sфайлы вsrc/(исключаяsrc/include/).
