mirror of
https://github.com/redstrate/Physis.git
synced 2025-05-16 15:07:45 +00:00
This is an easy format to add write support for, and we can recreate the "GCShop" EXH which is super simple as it's just one page and one language.
184 lines
4.2 KiB
Rust
184 lines
4.2 KiB
Rust
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#![allow(clippy::unnecessary_fallible_conversions)] // This wrongly trips on binrw code
|
|
|
|
use std::io::BufWriter;
|
|
use std::io::Cursor;
|
|
|
|
use binrw::BinRead;
|
|
use binrw::BinWrite;
|
|
use binrw::binrw;
|
|
|
|
use crate::ByteBuffer;
|
|
use crate::ByteSpan;
|
|
use crate::common::Language;
|
|
|
|
#[binrw]
|
|
#[brw(magic = b"EXHF")]
|
|
#[brw(big)]
|
|
#[allow(dead_code)]
|
|
#[derive(Debug)]
|
|
pub struct EXHHeader {
|
|
pub(crate) version: u16,
|
|
|
|
pub data_offset: u16,
|
|
pub(crate) column_count: u16,
|
|
pub(crate) page_count: u16,
|
|
pub(crate) language_count: u16,
|
|
|
|
pub unk1: u16,
|
|
|
|
#[br(temp)]
|
|
#[bw(calc = 0x010000)] // always this value??
|
|
pub unk2: u32,
|
|
|
|
#[brw(pad_after = 8)] // padding
|
|
pub row_count: u32,
|
|
}
|
|
|
|
#[binrw]
|
|
#[brw(repr(u16))]
|
|
#[repr(u16)]
|
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
pub enum ColumnDataType {
|
|
String = 0x0,
|
|
Bool = 0x1,
|
|
Int8 = 0x2,
|
|
UInt8 = 0x3,
|
|
Int16 = 0x4,
|
|
UInt16 = 0x5,
|
|
Int32 = 0x6,
|
|
UInt32 = 0x7,
|
|
Float32 = 0x9,
|
|
Int64 = 0xA,
|
|
UInt64 = 0xB,
|
|
|
|
PackedBool0 = 0x19,
|
|
PackedBool1 = 0x1A,
|
|
PackedBool2 = 0x1B,
|
|
PackedBool3 = 0x1C,
|
|
PackedBool4 = 0x1D,
|
|
PackedBool5 = 0x1E,
|
|
PackedBool6 = 0x1F,
|
|
PackedBool7 = 0x20,
|
|
}
|
|
|
|
#[binrw]
|
|
#[brw(big)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct ExcelColumnDefinition {
|
|
pub data_type: ColumnDataType,
|
|
pub offset: u16,
|
|
}
|
|
|
|
#[binrw]
|
|
#[brw(big)]
|
|
#[allow(dead_code)]
|
|
#[derive(Debug)]
|
|
pub struct ExcelDataPagination {
|
|
pub start_id: u32,
|
|
pub row_count: u32,
|
|
}
|
|
|
|
#[binrw]
|
|
#[brw(big)]
|
|
#[allow(dead_code)]
|
|
#[derive(Debug)]
|
|
pub struct EXH {
|
|
pub header: EXHHeader,
|
|
|
|
#[br(count = header.column_count)]
|
|
pub column_definitions: Vec<ExcelColumnDefinition>,
|
|
|
|
#[br(count = header.page_count)]
|
|
pub pages: Vec<ExcelDataPagination>,
|
|
|
|
#[br(count = header.language_count)]
|
|
#[brw(pad_after = 1)] // \0
|
|
pub languages: Vec<Language>,
|
|
}
|
|
|
|
impl EXH {
|
|
pub fn from_existing(buffer: ByteSpan) -> Option<EXH> {
|
|
EXH::read(&mut Cursor::new(&buffer)).ok()
|
|
}
|
|
|
|
pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
|
|
let mut buffer = ByteBuffer::new();
|
|
|
|
{
|
|
let cursor = Cursor::new(&mut buffer);
|
|
let mut writer = BufWriter::new(cursor);
|
|
|
|
self.write_args(&mut writer, ()).unwrap();
|
|
}
|
|
|
|
Some(buffer)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::fs::read;
|
|
use std::path::PathBuf;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_invalid() {
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
d.push("resources/tests");
|
|
d.push("random");
|
|
|
|
// Feeding it invalid data should not panic
|
|
EXH::from_existing(&read(d).unwrap());
|
|
}
|
|
|
|
// simple EXH to read, just one page
|
|
#[test]
|
|
fn test_read() {
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
d.push("resources/tests");
|
|
d.push("gcshop.exh");
|
|
|
|
let exh = EXH::from_existing(&read(d).unwrap()).unwrap();
|
|
|
|
// header
|
|
assert_eq!(exh.header.version, 3);
|
|
assert_eq!(exh.header.data_offset, 4);
|
|
assert_eq!(exh.header.column_count, 1);
|
|
assert_eq!(exh.header.page_count, 1);
|
|
assert_eq!(exh.header.language_count, 1);
|
|
assert_eq!(exh.header.row_count, 4);
|
|
|
|
// column definitions
|
|
assert_eq!(exh.column_definitions.len(), 1);
|
|
assert_eq!(exh.column_definitions[0].data_type, ColumnDataType::Int8);
|
|
assert_eq!(exh.column_definitions[0].offset, 0);
|
|
|
|
// pages
|
|
assert_eq!(exh.pages.len(), 1);
|
|
assert_eq!(exh.pages[0].start_id, 1441792);
|
|
assert_eq!(exh.pages[0].row_count, 4);
|
|
|
|
// languages
|
|
assert_eq!(exh.languages.len(), 1);
|
|
assert_eq!(exh.languages[0], Language::None);
|
|
}
|
|
|
|
// simple EXH to write, only one page
|
|
#[test]
|
|
fn test_write() {
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
d.push("resources/tests");
|
|
d.push("gcshop.exh");
|
|
|
|
let expected_exh_bytes = read(d).unwrap();
|
|
let expected_exh = EXH::from_existing(&expected_exh_bytes).unwrap();
|
|
|
|
let actual_exh_bytes = expected_exh.write_to_buffer().unwrap();
|
|
|
|
assert_eq!(actual_exh_bytes, expected_exh_bytes);
|
|
}
|
|
}
|