148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
use anyhow::Result;
|
|
use prometeu_bytecode::assembler::assemble;
|
|
use prometeu_bytecode::model::{BytecodeModule, DebugInfo, Export, FunctionMeta, SyscallDecl};
|
|
use prometeu_hal::color::Color;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
const APP_ID: u32 = 2;
|
|
const TITLE: &str = "Dummy Boy";
|
|
const APP_VERSION: &str = "0.1.0";
|
|
|
|
fn asm(s: &str) -> Vec<u8> {
|
|
assemble(s).expect("assemble")
|
|
}
|
|
|
|
pub fn generate() -> Result<()> {
|
|
let out_dir = cartridge_dir();
|
|
fs::create_dir_all(&out_dir)?;
|
|
fs::write(out_dir.join("program.pbx"), build_program())?;
|
|
fs::write(out_dir.join("manifest.json"), manifest_json())?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn build_program() -> Vec<u8> {
|
|
let mut rom = Vec::new();
|
|
|
|
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 1\nADD\nSET_GLOBAL 0"));
|
|
rom.extend(asm(&format!("PUSH_I64 {}\nHOSTCALL 0", Color::BLACK.raw())));
|
|
|
|
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 3\nMOD\nPUSH_I32 0\nEQ"));
|
|
let jump_to_green_case_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
|
rom.extend(asm(&format!("PUSH_I64 {}\nSET_LOCAL 0", Color::RED.raw())));
|
|
let jump_to_draw_after_red_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP 0"));
|
|
|
|
let green_case = rom.len() as u32;
|
|
rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 3\nMOD\nPUSH_I32 1\nEQ"));
|
|
let jump_to_blue_case_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
|
rom.extend(asm(&format!("PUSH_I64 {}\nSET_LOCAL 0", Color::GREEN.raw())));
|
|
let jump_to_draw_after_green_offset = rom.len() + 2;
|
|
rom.extend(asm("JMP 0"));
|
|
|
|
let blue_case = rom.len() as u32;
|
|
rom.extend(asm(&format!("PUSH_I64 {}\nSET_LOCAL 0", Color::BLUE.raw())));
|
|
|
|
let draw = rom.len() as u32;
|
|
rom.extend(asm(&format!(
|
|
"PUSH_I32 180\n\
|
|
PUSH_I32 75\n\
|
|
PUSH_I32 120\n\
|
|
PUSH_I32 120\n\
|
|
PUSH_I64 {}\n\
|
|
GET_LOCAL 0\n\
|
|
HOSTCALL 1\n\
|
|
FRAME_SYNC\n\
|
|
RET",
|
|
Color::WHITE.raw()
|
|
)));
|
|
|
|
patch_jump(&mut rom, jump_to_green_case_offset, green_case);
|
|
patch_jump(&mut rom, jump_to_draw_after_red_offset, draw);
|
|
patch_jump(&mut rom, jump_to_blue_case_offset, blue_case);
|
|
patch_jump(&mut rom, jump_to_draw_after_green_offset, draw);
|
|
|
|
BytecodeModule {
|
|
version: 0,
|
|
const_pool: vec![],
|
|
functions: vec![FunctionMeta {
|
|
code_offset: 0,
|
|
code_len: rom.len() as u32,
|
|
param_slots: 0,
|
|
local_slots: 1,
|
|
return_slots: 0,
|
|
max_stack_slots: 8,
|
|
}],
|
|
code: rom,
|
|
debug_info: Some(DebugInfo {
|
|
pc_to_span: vec![],
|
|
function_names: vec![(0, "main".into())],
|
|
}),
|
|
exports: vec![Export { symbol: "main".into(), func_idx: 0 }],
|
|
syscalls: vec![
|
|
SyscallDecl {
|
|
module: "gfx2d".into(),
|
|
name: "clear".into(),
|
|
version: 1,
|
|
arg_slots: 1,
|
|
ret_slots: 0,
|
|
},
|
|
SyscallDecl {
|
|
module: "gfx2d".into(),
|
|
name: "draw_square".into(),
|
|
version: 1,
|
|
arg_slots: 6,
|
|
ret_slots: 0,
|
|
},
|
|
],
|
|
}
|
|
.serialize()
|
|
}
|
|
|
|
pub fn manifest_json() -> Vec<u8> {
|
|
format!(
|
|
"{{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": {APP_ID},\n \"title\": \"{TITLE}\",\n \"app_version\": \"{APP_VERSION}\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"gfx2d\"]\n}}\n"
|
|
)
|
|
.into_bytes()
|
|
}
|
|
|
|
fn cartridge_dir() -> PathBuf {
|
|
let mut out_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
out_dir.pop();
|
|
out_dir.pop();
|
|
out_dir.pop();
|
|
out_dir.push("test-cartridges");
|
|
out_dir.push("dummy-boy");
|
|
out_dir
|
|
}
|
|
|
|
fn patch_jump(buf: &mut [u8], imm_offset: usize, target: u32) {
|
|
buf[imm_offset..imm_offset + 4].copy_from_slice(&target.to_le_bytes());
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn generated_program_is_pbx_module_with_expected_syscalls() {
|
|
let bytes = build_program();
|
|
|
|
assert!(bytes.starts_with(b"PBS\0"));
|
|
assert!(bytes.len() > 64);
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_declares_distinct_game_identity() {
|
|
let manifest: serde_json::Value =
|
|
serde_json::from_slice(&manifest_json()).expect("manifest should parse");
|
|
|
|
assert_eq!(manifest["app_id"], APP_ID);
|
|
assert_eq!(manifest["title"], TITLE);
|
|
assert_eq!(manifest["app_mode"], "Game");
|
|
assert_eq!(manifest["capabilities"], serde_json::json!(["gfx2d"]));
|
|
}
|
|
}
|