bQUARKz 3453494341
Some checks are pending
Test / Build
Intrepid/Prometeu/Runtime/pipeline/head This commit looks good
dev/jenkinsfile (#10)
Reviewed-on: #10
Co-authored-by: bQUARKz <bquarkz@gmail.com>
Co-committed-by: bQUARKz <bquarkz@gmail.com>
2026-04-08 07:39:33 +00:00

313 lines
8.9 KiB
Rust

use crate::asset::{AssetEntry, PreloadEntry};
use crate::syscalls::CapFlags;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{self, Cursor, Read, Seek, SeekFrom};
use std::path::PathBuf;
use std::sync::Arc;
pub const ASSETS_PA_MAGIC: [u8; 4] = *b"ASPA";
pub const ASSETS_PA_SCHEMA_VERSION: u32 = 1;
pub const ASSETS_PA_PRELUDE_SIZE: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum AppMode {
Game,
System,
}
#[derive(Debug, Clone)]
pub struct Cartridge {
pub app_id: u32,
pub title: String,
pub app_version: String,
pub app_mode: AppMode,
pub capabilities: CapFlags,
pub program: Vec<u8>,
pub assets: AssetsPayloadSource,
pub asset_table: Vec<AssetEntry>,
pub preload: Vec<PreloadEntry>,
}
#[derive(Debug, Clone)]
pub struct CartridgeDTO {
pub app_id: u32,
pub title: String,
pub app_version: String,
pub app_mode: AppMode,
pub capabilities: CapFlags,
pub program: Vec<u8>,
pub assets: AssetsPayloadSource,
pub asset_table: Vec<AssetEntry>,
pub preload: Vec<PreloadEntry>,
}
impl From<CartridgeDTO> for Cartridge {
fn from(dto: CartridgeDTO) -> Self {
Self {
app_id: dto.app_id,
title: dto.title,
app_version: dto.app_version,
app_mode: dto.app_mode,
capabilities: dto.capabilities,
program: dto.program,
assets: dto.assets,
asset_table: dto.asset_table,
preload: dto.preload,
}
}
}
#[derive(Debug, Clone)]
pub enum AssetsPayloadSource {
Memory(Arc<[u8]>),
File(Arc<FileAssetsPayloadSource>),
}
impl AssetsPayloadSource {
pub fn empty() -> Self {
Self::Memory(Arc::<[u8]>::from(Vec::<u8>::new()))
}
pub fn from_bytes(bytes: Vec<u8>) -> Self {
Self::Memory(Arc::<[u8]>::from(bytes))
}
pub fn from_file(path: PathBuf, payload_offset: u64, payload_len: u64) -> Self {
Self::File(Arc::new(FileAssetsPayloadSource { path, payload_offset, payload_len }))
}
pub fn is_empty(&self) -> bool {
match self {
Self::Memory(bytes) => bytes.is_empty(),
Self::File(source) => source.payload_len == 0,
}
}
pub fn open_slice(&self, offset: u64, size: u64) -> io::Result<AssetsPayloadSlice> {
match self {
Self::Memory(bytes) => {
let start =
usize::try_from(offset).map_err(|_| invalid_input("asset offset overflow"))?;
let len =
usize::try_from(size).map_err(|_| invalid_input("asset size overflow"))?;
let end =
start.checked_add(len).ok_or_else(|| invalid_input("asset range overflow"))?;
if end > bytes.len() {
return Err(invalid_input("asset range out of bounds"));
}
Ok(AssetsPayloadSlice::Memory { bytes: Arc::clone(bytes), start, len })
}
Self::File(source) => {
let end = offset
.checked_add(size)
.ok_or_else(|| invalid_input("asset range overflow"))?;
if end > source.payload_len {
return Err(invalid_input("asset range out of bounds"));
}
Ok(AssetsPayloadSlice::File { source: Arc::clone(source), offset, size })
}
}
}
}
#[derive(Debug)]
pub struct FileAssetsPayloadSource {
pub path: PathBuf,
pub payload_offset: u64,
pub payload_len: u64,
}
#[derive(Debug, Clone)]
pub enum AssetsPayloadSlice {
Memory { bytes: Arc<[u8]>, start: usize, len: usize },
File { source: Arc<FileAssetsPayloadSource>, offset: u64, size: u64 },
}
impl AssetsPayloadSlice {
pub fn open_reader(&self) -> io::Result<AssetsPayloadReader> {
match self {
Self::Memory { bytes, start, len } => {
let data = Arc::<[u8]>::from(bytes[*start..*start + *len].to_vec());
Ok(AssetsPayloadReader::Memory(Cursor::new(data)))
}
Self::File { source, offset, size } => {
let mut file = File::open(&source.path)?;
let absolute_start = source.payload_offset + offset;
file.seek(SeekFrom::Start(absolute_start))?;
Ok(AssetsPayloadReader::File(FileSliceReader {
file,
start: absolute_start,
len: *size,
position: 0,
}))
}
}
}
pub fn read_all(&self) -> io::Result<Vec<u8>> {
let mut reader = self.open_reader()?;
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)?;
Ok(buffer)
}
}
pub enum AssetsPayloadReader {
Memory(Cursor<Arc<[u8]>>),
File(FileSliceReader),
}
impl Read for AssetsPayloadReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Memory(reader) => reader.read(buf),
Self::File(reader) => reader.read(buf),
}
}
}
impl Seek for AssetsPayloadReader {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match self {
Self::Memory(reader) => reader.seek(pos),
Self::File(reader) => reader.seek(pos),
}
}
}
pub struct FileSliceReader {
file: File,
start: u64,
len: u64,
position: u64,
}
impl Read for FileSliceReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.position >= self.len {
return Ok(0);
}
let remaining = (self.len - self.position) as usize;
let to_read = remaining.min(buf.len());
let read = self.file.read(&mut buf[..to_read])?;
self.position += read as u64;
Ok(read)
}
}
impl Seek for FileSliceReader {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let next = match pos {
SeekFrom::Start(offset) => offset as i128,
SeekFrom::Current(delta) => self.position as i128 + delta as i128,
SeekFrom::End(delta) => self.len as i128 + delta as i128,
};
if next < 0 || next as u64 > self.len {
return Err(invalid_input("slice seek out of bounds"));
}
let next = next as u64;
self.file.seek(SeekFrom::Start(self.start + next))?;
self.position = next;
Ok(self.position)
}
}
fn invalid_input(message: &'static str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, message)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssetsPackPrelude {
pub magic: [u8; 4],
pub schema_version: u32,
pub header_len: u32,
pub payload_offset: u64,
pub flags: u32,
pub reserved: u32,
pub header_checksum: u32,
}
impl AssetsPackPrelude {
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < ASSETS_PA_PRELUDE_SIZE {
return None;
}
let mut magic = [0_u8; 4];
magic.copy_from_slice(&bytes[0..4]);
Some(Self {
magic,
schema_version: u32::from_le_bytes(bytes[4..8].try_into().ok()?),
header_len: u32::from_le_bytes(bytes[8..12].try_into().ok()?),
payload_offset: u64::from_le_bytes(bytes[12..20].try_into().ok()?),
flags: u32::from_le_bytes(bytes[20..24].try_into().ok()?),
reserved: u32::from_le_bytes(bytes[24..28].try_into().ok()?),
header_checksum: u32::from_le_bytes(bytes[28..32].try_into().ok()?),
})
}
pub fn to_bytes(self) -> [u8; ASSETS_PA_PRELUDE_SIZE] {
let mut bytes = [0_u8; ASSETS_PA_PRELUDE_SIZE];
bytes[0..4].copy_from_slice(&self.magic);
bytes[4..8].copy_from_slice(&self.schema_version.to_le_bytes());
bytes[8..12].copy_from_slice(&self.header_len.to_le_bytes());
bytes[12..20].copy_from_slice(&self.payload_offset.to_le_bytes());
bytes[20..24].copy_from_slice(&self.flags.to_le_bytes());
bytes[24..28].copy_from_slice(&self.reserved.to_le_bytes());
bytes[28..32].copy_from_slice(&self.header_checksum.to_le_bytes());
bytes
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AssetsPackHeader {
#[serde(default)]
pub asset_table: Vec<AssetEntry>,
#[serde(default)]
pub preload: Vec<PreloadEntry>,
}
#[derive(Debug)]
pub enum CartridgeError {
NotFound,
InvalidFormat,
InvalidManifest,
UnsupportedVersion,
MissingProgram,
MissingAssets,
IoError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Capability {
None,
System,
Gfx,
Audio,
Fs,
Log,
Asset,
Bank,
All,
}
#[derive(Deserialize)]
pub struct CartridgeManifest {
pub magic: String,
pub cartridge_version: u32,
pub app_id: u32,
pub title: String,
pub app_version: String,
pub app_mode: AppMode,
#[serde(default)]
pub capabilities: Vec<Capability>,
}