mirror of
https://github.com/Theaninova/mhlib.git
synced 2026-01-09 19:42:53 +00:00
Split project
This commit is contained in:
9
rust/springylib/Cargo.toml
Normal file
9
rust/springylib/Cargo.toml
Normal 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"
|
||||
3
rust/springylib/README.md
Normal file
3
rust/springylib/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# SpringyLib
|
||||
|
||||
A library for reading data from the Sproing Engine.
|
||||
34
rust/springylib/src/archive/error.rs
Normal file
34
rust/springylib/src/archive/error.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
48
rust/springylib/src/archive/mhjnr.rs
Normal file
48
rust/springylib/src/archive/mhjnr.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
96
rust/springylib/src/archive/mod.rs
Normal file
96
rust/springylib/src/archive/mod.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
52
rust/springylib/src/archive/standard.rs
Normal file
52
rust/springylib/src/archive/standard.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
1
rust/springylib/src/lib.rs
Normal file
1
rust/springylib/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod archive;
|
||||
Reference in New Issue
Block a user