1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-24 13:37:44 +00:00

Add EXH and EXD parsers

This commit is contained in:
Joshua Goins 2022-07-21 19:58:58 -04:00
parent da673cbadf
commit ca4962c876
5 changed files with 327 additions and 6 deletions

207
src/exd.rs Normal file
View file

@ -0,0 +1,207 @@
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Cursor, Seek, SeekFrom};
use crate::gamedata::MemoryBuffer;
use binrw::{binread, Endian, ReadOptions};
use crate::common::Language;
use binrw::BinRead;
use crate::exh::{ColumnDataType, ExcelColumnDefinition, ExcelDataPagination, EXH};
#[binread]
#[br(magic = b"EXDF")]
#[br(big)]
struct EXDHeader {
version : u16,
#[br(pad_before = 2)]
#[br(pad_after = 20)]
index_size : u32
}
#[binread]
#[br(big)]
struct ExcelDataOffset {
row_id : u32,
pub offset : u32
}
#[binread]
#[br(big)]
struct ExcelDataRowHeader {
data_size : u32,
row_count : u16
}
#[binread]
#[br(big)]
pub struct EXD {
header : EXDHeader,
#[br(count = header.index_size / core::mem::size_of::<ExcelDataOffset>() as u32)]
data_offsets : Vec<ExcelDataOffset>,
#[br(ignore)]
pub rows : Vec<ExcelRow>
}
#[derive(Debug)]
pub enum ColumnData {
String(String),
Bool(bool),
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Float32(f32),
Int64(i64),
UInt64(u64)
}
pub struct ExcelRow {
pub data : Vec<ColumnData>
}
impl EXD {
fn read_data_raw<Z : BinRead>(cursor: &mut Cursor<&MemoryBuffer>) -> Option<Z> where <Z as BinRead>::Args: Default {
Some(Z::read_options(cursor, &ReadOptions::new(Endian::Big) , <Z as BinRead>::Args::default()).unwrap())
}
fn read_column(cursor: &mut Cursor<&MemoryBuffer>, exh: &EXH, offset : u32, column : &ExcelColumnDefinition) -> Option<ColumnData> {
let mut read_packed_bool = | shift : i32 | -> bool {
let bit = 1 << shift;
let bool_data : i32 = Self::read_data_raw(cursor).unwrap();
(bool_data & bit) == bit
};
return match column.data_type {
ColumnDataType::String => {
let string_offset : u32 = Self::read_data_raw(cursor).unwrap();
cursor.seek(SeekFrom::Start((offset + exh.header.data_offset as u32 + string_offset).into())).ok()?;
let mut string = String::new();
let mut byte : u8 = Self::read_data_raw(cursor).unwrap();
while byte != 0 {
string.push(byte as char);
byte = Self::read_data_raw(cursor).unwrap();
}
Some(ColumnData::String(string))
}
ColumnDataType::Bool => {
// FIXME: i believe Bool is int8?
let bool_data : i32 = Self::read_data_raw(cursor).unwrap();
Some(ColumnData::Bool(bool_data == 1))
}
ColumnDataType::Int8 => {
Some(ColumnData::Int8(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::UInt8 => {
Some(ColumnData::UInt8(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::Int16 => {
Some(ColumnData::Int16(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::UInt16 => {
Some(ColumnData::UInt16(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::Int32 => {
Some(ColumnData::Int32(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::UInt32 => {
Some(ColumnData::UInt32(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::Float32 => {
Some(ColumnData::Float32(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::Int64 => {
Some(ColumnData::Int64(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::UInt64 => {
Some(ColumnData::UInt64(Self::read_data_raw(cursor).unwrap()))
}
ColumnDataType::PackedBool0 => {
Some(ColumnData::Bool(read_packed_bool(0)))
}
ColumnDataType::PackedBool1 => {
Some(ColumnData::Bool(read_packed_bool(1)))
}
ColumnDataType::PackedBool2 => {
Some(ColumnData::Bool(read_packed_bool(2)))
}
ColumnDataType::PackedBool3 => {
Some(ColumnData::Bool(read_packed_bool(3)))
}
ColumnDataType::PackedBool4 => {
Some(ColumnData::Bool(read_packed_bool(4)))
}
ColumnDataType::PackedBool5 => {
Some(ColumnData::Bool(read_packed_bool(5)))
}
ColumnDataType::PackedBool6 => {
Some(ColumnData::Bool(read_packed_bool(6)))
}
ColumnDataType::PackedBool7 => {
Some(ColumnData::Bool(read_packed_bool(7)))
}
}
}
pub fn from_existing(exh : &EXH, buffer : &MemoryBuffer) -> Option<EXD> {
let mut cursor = Cursor::new(buffer);
let mut exd = EXD::read(&mut cursor).ok()?;
for i in 0..exh.header.row_count {
for offset in &exd.data_offsets {
if offset.row_id == i {
cursor.seek(SeekFrom::Start(offset.offset.into())).ok()?;
let row_header = ExcelDataRowHeader::read(&mut cursor).ok()?;
let header_offset = offset.offset + 6;
let mut read_row = | offset : u32 | -> Option<ExcelRow> {
let mut subrow = ExcelRow { data : Vec::with_capacity(exh.column_definitions.len()) };
for column in &exh.column_definitions {
cursor.seek(SeekFrom::Start((offset + column.offset as u32).into())).ok()?;
subrow.data.push(Self::read_column(&mut cursor, exh, offset, column).unwrap());
}
Some(subrow)
};
if row_header.row_count > 1 {
for i in 0..row_header.row_count {
let subrow_offset = header_offset + (i * exh.header.data_offset + 2 * (i + 1)) as u32;
exd.rows.push(read_row(subrow_offset).unwrap());
}
} else {
exd.rows.push(read_row(header_offset).unwrap());
}
}
}
}
Some(exd)
}
pub fn calculate_filename(name : &str, language : Language, page : &ExcelDataPagination) -> String {
use crate::common::get_language_code;
return match language {
Language::None => {
format!("{name}_{}.exd", page.start_id)
}
lang => {
format!("{name}_{}_{}.exd", page.start_id, get_language_code(&lang))
}
}
}
}

82
src/exh.rs Normal file
View file

@ -0,0 +1,82 @@
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Cursor};
use crate::gamedata::MemoryBuffer;
use binrw::binread;
use crate::common::Language;
use binrw::BinRead;
#[binread]
#[br(magic = b"EXHF")]
#[br(big)]
pub struct EXHHeader {
version : u16,
pub(crate) data_offset : u16,
column_count : u16,
page_count : u16,
language_count : u16,
#[br(pad_before = 6)]
#[br(pad_after = 8)]
pub(crate) row_count : u32
}
#[binread]
#[br(repr(u16))]
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,
}
#[binread]
#[br(big)]
pub struct ExcelColumnDefinition {
pub data_type : ColumnDataType,
pub offset : u16,
}
#[binread]
#[br(big)]
pub struct ExcelDataPagination {
pub start_id : u32,
row_count : u32,
}
#[binread]
#[br(big)]
pub struct EXH {
pub(crate) header : EXHHeader,
#[br(count = header.column_count)]
pub(crate) column_definitions : Vec<ExcelColumnDefinition>,
#[br(count = header.page_count)]
pub(crate) pages : Vec<ExcelDataPagination>,
#[br(count = header.language_count)]
languages : Vec<Language>
}
impl EXH {
pub fn from_existing(buffer : &MemoryBuffer) -> Option<EXH> {
Some(EXH::read(&mut Cursor::new(&buffer)).ok()?)
}
}

View file

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader, Cursor};
use crate::gamedata::MemoryBuffer;
/// Represents an Excel List. /// Represents an Excel List.
pub struct EXL { pub struct EXL {
@ -12,15 +13,14 @@ pub struct EXL {
impl EXL { impl EXL {
/// Initializes `EXL` from an existing list. /// Initializes `EXL` from an existing list.
pub fn from_existing(path: &str) -> Option<EXL> { pub fn from_existing(buffer : &MemoryBuffer) -> Option<EXL> {
let mut exl = Self { let mut exl = Self {
version: 0, version: 0,
entries: HashMap::new(), entries: HashMap::new(),
}; };
let file = std::fs::File::open(path).unwrap(); let cursor = Cursor::new(buffer);
let reader = BufReader::new(cursor);
let reader = BufReader::new(file);
for (_, line) in reader.lines().enumerate() { for (_, line) in reader.lines().enumerate() {
// now parse the line! // now parse the line!

View file

@ -2,7 +2,11 @@ use std::ffi::OsStr;
use std::fs; use std::fs;
use std::fs::DirEntry; use std::fs::DirEntry;
use std::path::PathBuf; use std::path::PathBuf;
use crate::common::Language;
use crate::dat::DatFile; use crate::dat::DatFile;
use crate::exd::EXD;
use crate::exh::EXH;
use crate::exl::EXL;
use crate::index::IndexFile; use crate::index::IndexFile;
use crate::repository::{Category, Repository, string_to_category}; use crate::repository::{Category, Repository, string_to_category};
use crate::sqpack::calculate_hash; use crate::sqpack::calculate_hash;
@ -174,6 +178,32 @@ impl GameData {
Some((&self.repositories[0], string_to_category(tokens[0])?)) Some((&self.repositories[0], string_to_category(tokens[0])?))
} }
pub fn read_excel_sheet_header(&self, name : &str) -> Option<EXH> {
let root_exl_file = self.extract("exd/root.exl").unwrap();
let root_exl = EXL::from_existing(&root_exl_file).unwrap();
for (row, _) in root_exl.entries {
if row == name {
let new_filename = name.to_lowercase();
let path = format!("exd/{new_filename}.exh");
return EXH::from_existing(&self.extract(&path).unwrap())
}
}
None
}
pub fn read_excel_sheet(&self, name : &str, exh : &EXH, language : Language, page : usize) -> Option<EXD> {
let exd_path = format!("exd/{}", EXD::calculate_filename(name, language, &exh.pages[page]));
let exd_file = self.extract(&exd_path).unwrap();
EXD::from_existing(&exh, &exd_file)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -33,3 +33,5 @@ mod macros;
pub mod blowfish; pub mod blowfish;
mod blowfish_constants; mod blowfish_constants;
pub mod installer; pub mod installer;
pub mod exh;
pub mod exd;