( boże, kto zaprogramował .iris, żeby nie można było go zcommitować? )
Tekstowa gra RPG napisana w asemblerze RISC-V 32-bit, symulowana przez QEMU.
"Wszechświat zdecydował, że nie chce, żebym robił coś lepszego, na przykład może znalazł sobie życie."
Zależności
riscv32-elf-as— asembler RISC-Vriscv32-elf-ld— linker RISC-Vqemu-riscv32— emulator trybu użytkownika RISC-V
W Arch Linux:
sudo pacman -S riscv64-elf-binutils qemu-user
Budowanie i uruchamianie
make build # asembluj i linkuj do main.bin
make run # buduj + uruchom przez qemu-riscv32
make debug # uruchom z -strace, aby sprawdzić wywołania systemowe
Struktura projektu
Extremis/
├── main.s # Punkt wejścia (_start), wywołuje run_chapter_1
├── src/
│ ├── include/
│ │ └── function.s # Makra ramki stosu startF / endF
│ ├── engine/
│ │ ├── print.s # print — wypisz pojedynczy napis zakończony nullem (a0)
│ │ └── utils.s # printl — wypisz listę napisów (a0=adres tablicy, a1=liczba)
│ ├── chapters/
│ │ └── chapter_1.s # Logika rozdziału 1; ładuje napisy intro i wywołuje printl
│ └── dialogue/
│ └── intro.s # Dane napisów dla sekwencji intro
└── build/ # Skompilowane pliki .o (odzwierciedla strukturę src/)
Architektura
Silnik to czysty asembler RISC-V 32-bit, korzystający z ABI Linuksa przez emulację trybu użytkownika QEMU.
Konwencja ramki stosu
Każda funkcja używa makr startF / endF z src/include/function.s, aby zapisać i przywrócić ra, s0, s1, s2 na stosie:
startF # push: zaalokuj 16 bajtów, zapisz ra/s0/s1/s2
...
endF # pop: przywróć ra/s0/s1/s2, zwolnij 16 bajtów
ret
Wywołania systemowe
Ostrzeżenie: Numery wywołań środowiska RARS/MARS nie działają pod
qemu-riscv32. Ten projekt używa numerów wywołań systemowych ABI Linuksa RISC-V.
Numer wywołania systemowego umieszcza się w a7, wywołuje przez ecall. Wartość zwracana trafia do a0.
Wyjście
| Zamiar | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
Uwagi |
|---|---|---|---|---|---|---|
| Wypisz napis | 4 |
64 |
1 (fd stdout) |
adres buf | długość | Linux NIE zatrzymuje się na nullu — podaj długość w bajtach |
| Wypisz znak | 11 |
64 |
1 |
buf znaku | 1 |
zapisz znak w pamięci, przekaż jego adres |
| Wypisz liczbę | 1 |
— | liczba | — | — | brak odpowiednika w Linuxie; najpierw konwertuj na napis |
| Wypisz hex | 34 |
— | liczba | — | — | brak odpowiednika w Linuxie; konwertuj ręcznie |
| Wypisz unsigned | 36 |
— | liczba | — | — | brak odpowiednika w Linuxie; konwertuj ręcznie |
Wejście
| Zamiar | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
Uwagi |
|---|---|---|---|---|---|---|
| Czytaj napis | 8 |
63 |
0 (fd stdin) |
adres buf | maks. bajtów | Linux zwraca surowe bajty, włączając znak nowej linii |
| Czytaj znak | 12 |
63 |
0 |
adres buf | 1 |
zwraca a0 = liczba przeczytanych bajtów; znak jest w buf |
| Czytaj liczbę | 5 |
— | — | — | — | brak odpowiednika w Linuxie; czytaj napis, parsuj go |
Proces
| Zamiar | RARS a7 |
Linux a7 |
a0 |
Uwagi |
|---|---|---|---|---|
| Wyjście | 10 |
93 |
— | RARS ignoruje a0; Linux czyta go jako kod wyjścia |
| Wyjście(kod) | 17 |
93 |
kod wyjścia | Linux exit_group |
Pamięć
| Zamiar | RARS a7 |
Linux a7 |
a0 |
Zwraca | Uwagi |
|---|---|---|---|---|---|
| Alokuj (sbrk) | 9 |
214 |
bajty (RARS) / nowy adres brk (Linux) | a0 = początek zaalokowanego bloku |
Linux brk działa inaczej — ustawiasz nowy szczyt, nie rozmiar |
Operacje na plikach
| Zamiar | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
a3 |
Uwagi |
|---|---|---|---|---|---|---|---|
| Otwórz plik | 13 |
56 |
dirfd (-100=bieżący katalog) |
adres nazwy pliku | flagi | tryb | Linux używa openat |
| Czytaj plik | 14 |
63 |
fd | adres buf | maks. bajtów | — | zwraca liczbę przeczytanych bajtów |
| Zapisz plik | 15 |
64 |
fd | adres buf | liczba bajtów | — | zwraca liczbę zapisanych bajtów |
| Zamknij plik | 16 |
57 |
fd | — | — | — | zwraca 0 przy sukcesie |
Czas
| Zamiar | RARS a7 |
Linux a7 |
a0 |
a1 |
Uwagi |
|---|---|---|---|---|---|
| Czas | 30 |
113 |
id zegara (1=RZECZYWISTY) |
timespec* buf |
RARS zwraca podzielone lo/hi w a0/a1; Linux zapisuje strukturę do buf |
| Uśpij | 32 |
115 |
id zegara | timespec* |
RARS przyjmuje milisekundy w a0; Linux używa clock_nanosleep ze strukturą |
Dodawanie treści
- Nowe napisy dialogowe umieszczaj w
src/dialogue/. - Nowe rozdziały umieszczaj w
src/chapters/i dołączaj przez.includew pliku rozdziału, który ich potrzebuje. - Makefile automatycznie znajduje wszystkie pliki
.swsrc/(z wyłączeniemsrc/include/).
