Extremis
Assembly

Extremis

Przygoda RPG napisana w asemblerze RISC-V

( 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-V
  • riscv32-elf-ld — linker RISC-V
  • qemu-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 .include w pliku rozdziału, który ich potrzebuje.
  • Makefile automatycznie znajduje wszystkie pliki .s w src/ (z wyłączeniem src/include/).