Extremis
Assembly

Extremis

Jogo de RPG de aventura escrito em linguagem de montagem RISC-V

(Meu Deus, quem programou o .iris para que não fosse possível fazer commit?)

Um RPG baseado em texto escrito em linguagem de montagem RISC-V de 32 bits, simulado via QEMU.

"O universo decidiu que não queria que eu fizesse nada melhor, como, talvez, arranjar uma vida."

Dependências

  • riscv32-elf-as — Assembler RISC-V
  • riscv32-elf-ld — Linker RISC-V
  • qemu-riscv32 — Emulador de modo de usuário RISC-V

No Arch Linux:

sudo pacman -S riscv64-elf-binutils qemu-user

Compilar e Executar

make build   # compila e vincula em main.bin
make run     # compila + executa via qemu-riscv32
make debug   # executa com -strace para inspecionar chamadas de sistema

Estrutura do projeto

Extremis/
├── main.s # Ponto de entrada (_start), chama run_chapter_1
├── src/
│   ├── include/
│   │   └── function.s # macros de estrutura de pilha startF / endF
│   ├── engine/
│   │   ├── print.s # print  — imprime uma única string terminada em null (a0)
│   │   └── utils.s # printl — imprime uma lista de strings (a0=tabelas de endereços, a1=contagem)
│   ├── chapters/
│   │   └── chapter_1.s # Lógica do Capítulo 1; carrega strings de introdução e chama printl
│   └── dialogue/
│ └── intro.s # Dados de string para a sequência de introdução
└── build/ # Arquivos .o compilados (espelha o layout de src/)

Arquitetura

O motor é uma linguagem assembly RISC-V de 32 bits pura, voltada para a ABI do Linux por meio da emulação em modo de usuário do QEMU.

Convenção de quadro de pilha

Todas as funções utilizam as macros startF / endF de src/include/function.s para salvar e restaurar ra, s0, s1, s2 na pilha:

startF    # push: alocar 16 bytes, salvar ra/s0/s1/s2
...
endF # pop:  restaurar ra/s0/s1/s2, desalocar 16 bytes
ret

Chamadas de sistema

Aviso: Os números de chamada de ambiente RARS/MARS não funcionam no qemu-riscv32. Este projeto usa os números de chamada de sistema da ABI RISC-V do Linux.

O número da chamada de sistema vai para a7, invocada com ecall. O valor de retorno é retornado em a0.

Saída

Intenção RARS a7 Linux a7 a0 a1 a2 Notas
Imprimir string 4 64 1 (fd stdout) endereço do buffer comprimento O Linux NÃO para no nulo — passe o comprimento em bytes
Imprimir caractere 11 64 1 buffer de caracteres 1 armazene o caractere na memória, passe seu endereço
Imprimir inteiro 1 inteiro sem equivalente no Linux; converter primeiro para string
Imprimir inteiro hexadecimal 34 inteiro sem equivalente no Linux; converter manualmente
Imprimir inteiro sem sinal 36 inteiro sem equivalente no Linux; converter manualmente

Entrada

Intenção RARS a7 Linux a7 a0 a1 a2 Notas
Ler string 8 63 0 (stdin fd) endereço do buffer bytes máximos O Linux retorna bytes brutos, incluindo quebra de linha
Ler caractere 12 63 0 endereço do buffer 1 retorna a0 = bytes lidos; o caractere está no buffer
Leitura de inteiro 5 sem equivalente no Linux; leia a string e analise-a

Processo

Intenção RARS a7 Linux a7 a0 Notas
Saída 10 93 RARS ignora a0; o Linux o lê como status de saída
Saída(código) 17 93 código de saída Linux exit_group

Memória

Intenção RARS a7 Linux a7 a0 Retorna Notas
Alocar (sbrk) 9 214 bytes (RARS) / novo endereço brk (Linux) a0 = início do bloco alocado O brk do Linux funciona de maneira diferente — você define o novo topo, não um tamanho

E/S de arquivo

Intenção RARS a7 Linux a7 a0 a1 a2 a3 Notas
Abrir arquivo 13 56 dirfd (-100=CWD) endereço do nome do arquivo sinalizadores modo O Linux usa openat
Ler arquivo 14 63 fd endereço do buffer bytes máximos retorna os bytes lidos
Gravar arquivo 15 64 fd endereço do buffer contagem de bytes retorna os bytes gravados
Fechar arquivo 16 57 fd retorna 0 em caso de sucesso

Tempo

Intenção RARS a7 Linux a7 a0 a1 Notas
Tempo 30 113 id do relógio (1=TEMPO REAL) timespec* buf RARS retorna lo/hi divididos em a0/a1; Linux grava a estrutura em buf
Sleep 32 115 id do relógio timespec* RARS recebe milissegundos em a0; Linux usa clock_nanosleep com uma estrutura

Adicionando Conteúdo

  • Novas strings de diálogo vão para src/dialogue/.
  • Novos capítulos vão para src/chapters/ e são incluídos via .include no arquivo de capítulo que os necessita.
  • O Makefile detecta automaticamente todos os arquivos .s em src/ (excluindo src/include/).