Extremis
Assembly

Extremis

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

GitHub|

(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/).