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:
parent
da673cbadf
commit
ca4962c876
5 changed files with 327 additions and 6 deletions
207
src/exd.rs
Normal file
207
src/exd.rs
Normal 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
82
src/exh.rs
Normal 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()?)
|
||||||
|
}
|
||||||
|
}
|
10
src/exl.rs
10
src/exl.rs
|
@ -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!
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -32,4 +32,6 @@ 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;
|
Loading…
Add table
Reference in a new issue