388 lines
15 KiB
Rust

use super::*;
use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value};
use prometeu_hal::asset::{BankType, LoadStatus, SlotRef};
use prometeu_hal::color::Color;
use prometeu_hal::log::{LogLevel, LogSource};
use prometeu_hal::sprite::Sprite;
use prometeu_hal::syscalls::Syscall;
use prometeu_hal::tile::Tile;
use prometeu_hal::vm_fault::VmFault;
use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int};
impl VirtualMachineRuntime {
fn syscall_log_write(&mut self, level_val: i64, tag: u16, msg: String) -> Result<(), VmFault> {
let level = match level_val {
0 => LogLevel::Trace,
1 => LogLevel::Debug,
2 => LogLevel::Info,
3 => LogLevel::Warn,
4 => LogLevel::Error,
_ => return Err(VmFault::Trap(TRAP_TYPE, format!("Invalid log level: {}", level_val))),
};
let app_id = self.current_app_id;
let count = *self.logs_written_this_frame.get(&app_id).unwrap_or(&0);
if count >= Self::MAX_LOGS_PER_FRAME {
if count == Self::MAX_LOGS_PER_FRAME {
self.logs_written_this_frame.insert(app_id, count + 1);
self.log(
LogLevel::Warn,
LogSource::App { app_id },
0,
"App exceeded log limit per frame".to_string(),
);
}
return Ok(());
}
self.logs_written_this_frame.insert(app_id, count + 1);
let mut final_msg = msg;
if final_msg.len() > Self::MAX_LOG_LEN {
final_msg.truncate(Self::MAX_LOG_LEN);
}
self.log(level, LogSource::App { app_id }, tag, final_msg);
Ok(())
}
pub(crate) fn get_color(&self, value: i64) -> Color {
Color::from_raw(value as u16)
}
}
impl NativeInterface for VirtualMachineRuntime {
fn syscall(
&mut self,
id: SyscallId,
args: &[Value],
ret: &mut HostReturn,
ctx: &mut HostContext,
) -> Result<(), VmFault> {
self.telemetry_current.syscalls += 1;
let syscall = Syscall::from_u32(id).ok_or_else(|| {
VmFault::Trap(TRAP_INVALID_SYSCALL, format!("Unknown syscall: 0x{:08X}", id))
})?;
match syscall {
Syscall::SystemHasCart => {
ret.push_bool(true);
return Ok(());
}
Syscall::SystemRunCart => return Ok(()),
_ => {}
}
let hw = ctx.require_hw()?;
match syscall {
Syscall::SystemHasCart => unreachable!(),
Syscall::SystemRunCart => unreachable!(),
Syscall::GfxClear => {
let color = self.get_color(expect_int(args, 0)?);
hw.gfx_mut().clear(color);
Ok(())
}
Syscall::GfxFillRect => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let w = expect_int(args, 2)? as i32;
let h = expect_int(args, 3)? as i32;
let color = self.get_color(expect_int(args, 4)?);
hw.gfx_mut().fill_rect(x, y, w, h, color);
Ok(())
}
Syscall::GfxDrawLine => {
let x1 = expect_int(args, 0)? as i32;
let y1 = expect_int(args, 1)? as i32;
let x2 = expect_int(args, 2)? as i32;
let y2 = expect_int(args, 3)? as i32;
let color = self.get_color(expect_int(args, 4)?);
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
Ok(())
}
Syscall::GfxDrawCircle => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let r = expect_int(args, 2)? as i32;
let color = self.get_color(expect_int(args, 3)?);
hw.gfx_mut().draw_circle(x, y, r, color);
Ok(())
}
Syscall::GfxDrawDisc => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let r = expect_int(args, 2)? as i32;
let border_color = self.get_color(expect_int(args, 3)?);
let fill_color = self.get_color(expect_int(args, 4)?);
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
Ok(())
}
Syscall::GfxDrawSquare => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let w = expect_int(args, 2)? as i32;
let h = expect_int(args, 3)? as i32;
let border_color = self.get_color(expect_int(args, 4)?);
let fill_color = self.get_color(expect_int(args, 5)?);
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
Ok(())
}
Syscall::GfxSetSprite => {
let asset_name = match args
.first()
.ok_or_else(|| VmFault::Panic("Missing asset_name".into()))?
{
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())),
};
let index = expect_int(args, 1)? as usize;
let x = expect_int(args, 2)? as i32;
let y = expect_int(args, 3)? as i32;
let tile_id = expect_int(args, 4)? as u16;
let palette_id = expect_int(args, 5)? as u8;
let active = expect_bool(args, 6)?;
let flip_x = expect_bool(args, 7)?;
let flip_y = expect_bool(args, 8)?;
let priority = expect_int(args, 9)? as u8;
let bank_id =
hw.assets().find_slot_by_name(&asset_name, BankType::TILES).unwrap_or(0);
if index < 512 {
*hw.gfx_mut().sprite_mut(index) = Sprite {
tile: Tile { id: tile_id, flip_x: false, flip_y: false, palette_id },
x,
y,
bank_id,
active,
flip_x,
flip_y,
priority,
};
}
Ok(())
}
Syscall::GfxDrawText => {
let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32;
let msg = match args
.get(2)
.ok_or_else(|| VmFault::Panic("Missing message".into()))?
{
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())),
};
let color = self.get_color(expect_int(args, 3)?);
hw.gfx_mut().draw_text(x, y, &msg, color);
Ok(())
}
Syscall::GfxClear565 => {
let color_val = expect_int(args, 0)? as u32;
if color_val > 0xFFFF {
return Err(VmFault::Trap(TRAP_OOB, "Color value out of bounds".into()));
}
hw.gfx_mut().clear(Color::from_raw(color_val as u16));
Ok(())
}
Syscall::AudioPlaySample => {
let sample_id = expect_int(args, 0)? as u32;
let voice_id = expect_int(args, 1)? as usize;
let volume = expect_int(args, 2)? as u8;
let pan = expect_int(args, 3)? as u8;
let pitch = expect_number(args, 4, "pitch")?;
hw.audio_mut().play(
0,
sample_id as u16,
voice_id,
volume,
pan,
pitch,
0,
prometeu_hal::LoopMode::Off,
);
Ok(())
}
Syscall::AudioPlay => {
let asset_name = match args
.first()
.ok_or_else(|| VmFault::Panic("Missing asset_name".into()))?
{
Value::String(s) => s.clone(),
_ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())),
};
let sample_id = expect_int(args, 1)? as u16;
let voice_id = expect_int(args, 2)? as usize;
let volume = expect_int(args, 3)? as u8;
let pan = expect_int(args, 4)? as u8;
let pitch = expect_number(args, 5, "pitch")?;
let loop_mode = match expect_int(args, 6)? {
0 => prometeu_hal::LoopMode::Off,
_ => prometeu_hal::LoopMode::On,
};
let bank_id =
hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS).unwrap_or(0);
hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode);
Ok(())
}
Syscall::FsOpen => {
let path = expect_string(args, 0, "path")?;
if self.fs_state != FsState::Mounted {
ret.push_int(-1);
return Ok(());
}
let handle = self.next_handle;
self.open_files.insert(handle, path);
self.next_handle += 1;
ret.push_int(handle as i64);
Ok(())
}
Syscall::FsRead => {
let handle = expect_int(args, 0)? as u32;
let path = self
.open_files
.get(&handle)
.ok_or_else(|| VmFault::Panic("Invalid handle".into()))?;
match self.fs.read_file(path) {
Ok(data) => ret.push_string(String::from_utf8_lossy(&data).into_owned()),
Err(_) => ret.push_null(),
}
Ok(())
}
Syscall::FsWrite => {
let handle = expect_int(args, 0)? as u32;
let content = expect_string(args, 1, "content")?.into_bytes();
let path = self
.open_files
.get(&handle)
.ok_or_else(|| VmFault::Panic("Invalid handle".into()))?;
match self.fs.write_file(path, &content) {
Ok(_) => ret.push_bool(true),
Err(_) => ret.push_bool(false),
}
Ok(())
}
Syscall::FsClose => {
self.open_files.remove(&(expect_int(args, 0)? as u32));
Ok(())
}
Syscall::FsListDir => {
let path = expect_string(args, 0, "path")?;
match self.fs.list_dir(&path) {
Ok(entries) => {
let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
ret.push_string(names.join(";"));
}
Err(_) => ret.push_null(),
}
Ok(())
}
Syscall::FsExists => {
ret.push_bool(self.fs.exists(&expect_string(args, 0, "path")?));
Ok(())
}
Syscall::FsDelete => {
match self.fs.delete(&expect_string(args, 0, "path")?) {
Ok(_) => ret.push_bool(true),
Err(_) => ret.push_bool(false),
}
Ok(())
}
Syscall::LogWrite => {
self.syscall_log_write(
expect_int(args, 0)?,
0,
expect_string(args, 1, "message")?,
)?;
Ok(())
}
Syscall::LogWriteTag => {
self.syscall_log_write(
expect_int(args, 0)?,
expect_int(args, 1)? as u16,
expect_string(args, 2, "message")?,
)?;
Ok(())
}
Syscall::AssetLoad => {
let asset_id = expect_string(args, 0, "asset_id")?;
let asset_type = match expect_int(args, 1)? as u32 {
0 => BankType::TILES,
1 => BankType::SOUNDS,
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
};
let slot = SlotRef { asset_type, index: expect_int(args, 2)? as usize };
match hw.assets().load(&asset_id, slot) {
Ok(handle) => {
ret.push_int(handle as i64);
Ok(())
}
Err(e) => Err(VmFault::Panic(e)),
}
}
Syscall::AssetStatus => {
let status_val = match hw.assets().status(expect_int(args, 0)? as u32) {
LoadStatus::PENDING => 0,
LoadStatus::LOADING => 1,
LoadStatus::READY => 2,
LoadStatus::COMMITTED => 3,
LoadStatus::CANCELED => 4,
LoadStatus::ERROR => 5,
};
ret.push_int(status_val);
Ok(())
}
Syscall::AssetCommit => {
hw.assets().commit(expect_int(args, 0)? as u32);
Ok(())
}
Syscall::AssetCancel => {
hw.assets().cancel(expect_int(args, 0)? as u32);
Ok(())
}
Syscall::BankInfo => {
let asset_type = match expect_int(args, 0)? as u32 {
0 => BankType::TILES,
1 => BankType::SOUNDS,
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
};
let json =
serde_json::to_string(&hw.assets().bank_info(asset_type)).unwrap_or_default();
ret.push_string(json);
Ok(())
}
Syscall::BankSlotInfo => {
let asset_type = match expect_int(args, 0)? as u32 {
0 => BankType::TILES,
1 => BankType::SOUNDS,
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
};
let slot = SlotRef { asset_type, index: expect_int(args, 1)? as usize };
let json = serde_json::to_string(&hw.assets().slot_info(slot)).unwrap_or_default();
ret.push_string(json);
Ok(())
}
}
}
}
fn expect_string(args: &[Value], index: usize, field: &str) -> Result<String, VmFault> {
match args.get(index).ok_or_else(|| VmFault::Panic(format!("Missing {}", field)))? {
Value::String(value) => Ok(value.clone()),
_ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected string {}", field))),
}
}
fn expect_number(args: &[Value], index: usize, field: &str) -> Result<f64, VmFault> {
match args.get(index).ok_or_else(|| VmFault::Panic(format!("Missing {}", field)))? {
Value::Float(f) => Ok(*f),
Value::Int32(i) => Ok(*i as f64),
Value::Int64(i) => Ok(*i as f64),
_ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected number for {}", field))),
}
}