Split project

This commit is contained in:
2023-05-07 02:18:48 +02:00
parent 85cdecca12
commit 82135428d0
19 changed files with 384 additions and 751 deletions

View File

@@ -0,0 +1,9 @@
[package]
name = "springylib"
version = "0.1.0"
edition = "2021"
[dependencies]
binrw = "0.11.1"
serde = {version = "1.0.160", features = ["derive"]}
serde-xml-rs = "0.6.0"

View File

@@ -0,0 +1,3 @@
# SpringyLib
A library for reading data from the Sproing Engine.

View File

@@ -0,0 +1,34 @@
use std::fmt::{Display, Formatter};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
BinRw(binrw::Error),
Io(std::io::Error),
Unsupported { reason: String },
}
impl From<binrw::Error> for Error {
fn from(value: binrw::Error) -> Self {
Error::BinRw(value)
}
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::Io(value)
}
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Error::BinRw(e) => write!(f, "{}", e),
Error::Io(e) => write!(f, "{}", e),
Error::Unsupported { reason } => write!(f, "Unsupported Archive: {}", reason),
}
}
}

View File

@@ -0,0 +1,48 @@
use crate::archive::{Archive, FilePointer};
use binrw::prelude::binread;
use binrw::NullString;
#[binread]
#[br(little)]
#[derive(Debug)]
pub struct Container {
#[br(align_after = 0x20)]
pub name: NullString,
#[br(temp)]
pub count: u32,
#[br(align_after = 0x20)]
pub unk1: u32,
#[br(count = count)]
pub files: Vec<FileEntry>,
}
impl From<Container> for Archive {
fn from(value: Container) -> Self {
Archive(
value
.files
.into_iter()
.map(|it| (it.name.to_string(), it.into()))
.collect(),
)
}
}
#[derive(Debug)]
#[binread]
pub struct FileEntry {
#[br(pad_size_to = 0x68)]
pub name: NullString,
pub pos: u32,
#[br(pad_after = 0x10)]
pub len: u32,
}
impl From<FileEntry> for FilePointer {
fn from(value: FileEntry) -> Self {
FilePointer {
position: value.pos as usize,
length: value.len as usize,
}
}
}

View File

@@ -0,0 +1,96 @@
use binrw::prelude::BinRead;
use binrw::NullString;
use std::collections::HashMap;
use std::io::{Read, Seek};
use std::ops::Deref;
pub mod error;
mod mhjnr;
mod standard;
use crate::archive::error::{Error, Result};
/// Archive info
pub struct Archive(HashMap<String, FilePointer>);
/// Pointer to the file inside the archive
pub struct FilePointer {
pub position: usize,
pub length: usize,
}
impl Deref for Archive {
type Target = HashMap<String, FilePointer>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<HashMap<String, FilePointer>> for Archive {
fn from(value: HashMap<String, FilePointer>) -> Self {
Archive(value)
}
}
/// These are all slightly divergent data layouts
enum ArchiveKind {
/// Appears in a variety of Moorhuhn Shoot 'em Up
/// games, starting with Moorhuhn Winter.
///
/// The name can have a max length of 0x30, however the header
/// does not store the amount of files and instead is delimited
/// by a final entry with the name `****`
///
/// File Entries have a max path length of 0x30 with a total entry
/// size of 0x40.
V1,
/// Appears in Moorhuhn Jump 'n Run games as well
/// as Moorhuhn Kart 2, starting with Moorhuhn Kart 2.
///
/// This one has a name with a max length of 0x20,
/// with a total header size of 0x40
///
/// File Entries have a max path length of 0x68,
/// with a total entry size of 0x80
V2,
/// Appears in later Moorhuhn Shoot 'em Up games, starting with Moorhuhn
/// Invasion.
///
/// Works the same as V2, but has the maximum header and path string size
/// increased from 0x30 to 0x40
V3,
}
impl ArchiveKind {
/// Guesses the archive type based on the file type
pub fn guess<R>(reader: &mut R) -> Result<ArchiveKind>
where
R: Read + Seek,
{
let name = NullString::read(reader)?.to_string();
reader.rewind()?;
match name.as_str() {
"MHJNR-XXL" | "MHJNR-XS" | "Moorhuhn Kart 2" => Ok(ArchiveKind::V1),
"MH-W V1.0" | "MH3 V1.0 " | "MH 1 REMAKE" => Ok(ArchiveKind::V2),
"MHP XXL" | "MHINV XXL V1.0" => Ok(ArchiveKind::V3),
name => Err(Error::Unsupported {
reason: name.to_string(),
}),
}
}
}
impl Archive {
/// Reads the archive info from a binary stream
pub fn read<R>(reader: &mut R) -> Result<Archive>
where
R: Read + Seek,
{
match ArchiveKind::guess(reader)? {
ArchiveKind::V1 => Ok(standard::Container::read_args(reader, (0x30,))?.into()),
ArchiveKind::V2 => Ok(mhjnr::Container::read(reader)?.into()),
ArchiveKind::V3 => Ok(standard::Container::read_args(reader, (0x40,))?.into()),
}
}
}

View File

@@ -0,0 +1,52 @@
use crate::archive::{Archive, FilePointer};
use binrw::{binread, parser, until_exclusive, BinResult, NullString};
#[binread]
#[br(little, import(string_size: usize))]
#[derive(Debug)]
pub struct Container {
#[br(temp, args(string_size))]
pub header: FileEntry,
#[br(parse_with = until_end, args_raw(string_size))]
pub entries: Vec<FileEntry>,
}
#[parser(reader, endian)]
fn until_end(string_size: usize) -> BinResult<Vec<FileEntry>> {
until_exclusive(|entry: &FileEntry| entry.name.to_string().as_str() == "****")(
reader,
endian,
(string_size,),
)
}
impl From<Container> for Archive {
fn from(value: Container) -> Self {
Archive(
value
.entries
.into_iter()
.map(|entry| (entry.name.to_string(), entry.into()))
.collect(),
)
}
}
#[binread]
#[br(little, import(string_size: usize))]
#[derive(Debug)]
pub struct FileEntry {
#[br(pad_size_to = string_size)]
pub name: NullString,
#[br(pad_size_to = 0x10)]
pub pointer: [u32; 2],
}
impl From<FileEntry> for FilePointer {
fn from(value: FileEntry) -> Self {
FilePointer {
position: value.pointer[0] as usize,
length: value.pointer[1] as usize,
}
}
}

View File

@@ -0,0 +1 @@
pub mod archive;