Rust Version:
Code: Select all
use binread::BinReaderExt;
use binread::{
derive_binread,
io::{Cursor, Read, Seek, SeekFrom},
BinRead, BinResult, NullString, ReadOptions,
};
// use serde::*;
use serde_json::Result as JsonResult;
use std::collections::HashMap;
fn get_string<R, BR, A>(reader: &mut R, ro: &ReadOptions, args: A) -> BinResult<BR>
where
R: Read + Seek,
BR: BinRead<Args = A>,
A: Copy + 'static,
{
let _pos = reader.seek(SeekFrom::Start(ro.offset))?;
BR::read_options(reader, &ro, args)
}
#[derive_binread]
#[derive(Debug, Clone, Copy)]
pub struct DatasheetHeader {
revision: u32,
unknown1: u32,
unique_id_offset: u32,
unknown2: u32,
type_offset: u32,
row_number: u32,
plain_text_length: u32,
unknown3: u32,
unknown4: u32,
unknown5: u32,
unknown6: u32,
unknown7: u32,
unknown8: u32,
unknown9: u32,
#[br(temp)]
_plain_text_offset: u32,
#[br(calc = 60 + _plain_text_offset)]
plain_text_offset: u32,
header_sig: u32,
unknown10: u32,
pub columns: u32,
pub rows: u32,
unknown11: u32,
unknown12: u32,
unknown13: u32,
unknown14: u32,
}
#[derive_binread]
#[derive(Debug, Clone)]
#[br(import(data_offset: u32))]
pub struct DatasheetColumn {
unknown15: u32,
#[br(temp)]
_column_name_offset: u32,
#[br(calc = data_offset + _column_name_offset)]
column_name_offset: u32,
column_type: u32,
#[br(parse_with = get_string, offset=column_name_offset as u64)]
#[br(restore_position)]
pub column_name: NullString,
}
#[derive_binread]
#[derive(Debug, Clone)]
#[br(import(data_offset: u32))]
pub struct DatasheetRow {
#[br(temp)]
_row_value_offset: u32,
#[br(calc = data_offset + _row_value_offset)]
row_value_offset: u32,
row_value_or_something: u32,
#[br(parse_with = get_string, offset=row_value_offset as u64)]
#[br(restore_position)]
pub value: NullString,
}
#[derive(Debug, BinRead, Clone)]
#[br(import(column_count: u32, data_offset: u32))]
#[br(assert(row.len() as u32 == column_count))]
pub struct DatasheetRows {
#[br(count = column_count)]
#[br(args(data_offset))]
pub row: Vec<DatasheetRow>,
}
#[derive(Debug, BinRead)]
#[br(assert(columns.len() as u32 == header.columns))]
#[br(assert(rows.len() as u32 == header.rows))]
pub struct Datasheet {
pub header: DatasheetHeader,
#[br(args(header.plain_text_offset))]
#[br(count = header.columns)]
pub columns: Vec<DatasheetColumn>,
#[br(count = header.rows)]
#[br(args(header.columns, header.plain_text_offset))]
pub rows: Vec<DatasheetRows>,
}
#[allow(dead_code)]
pub struct DatasheetParser {
pub datasheet: Datasheet,
}
#[allow(dead_code)]
impl DatasheetParser {
pub fn to_json(&self) -> JsonResult<String> {
let _json = serde_json::to_string_pretty(&self.get_data())?;
Ok(_json)
}
pub fn to_xml(&self) -> anyhow::Result<String> {
let xml = quick_xml::se::to_string(&self.get_data()).unwrap();
Ok(xml)
}
pub fn get_data(&self) -> Vec<HashMap<String, String>> {
let columns: Vec<String> = self
.datasheet
.columns
.iter()
.map(|c| c.column_name.clone().into_string())
.collect();
let mut combined: Vec<HashMap<_, _>> = Vec::new();
for n in &self.datasheet.rows {
let row_data: Vec<String> = n
.row
.iter()
.map(|v| v.value.clone().into_string())
.collect();
let data: HashMap<_, _> = columns
.clone()
.into_iter()
.zip(row_data.into_iter())
.collect();
combined.push(data)
}
combined
}
pub fn new(file: Vec<u8>) -> DatasheetParser {
DatasheetParser {
datasheet: Cursor::new(file).read_le().unwrap(),
}
}
}
Typescript:
Code: Select all
/* eslint-disable unicorn/filename-case */
import { Parser } from "binary-parser";
let textOffset = 0;
const DataSheetColumn = Parser.start()
.uint32le("unknown")
.uint32le("column_name_offset")
.uint32le("type");
const DataSheetData = Parser.start().array(null, {
type: Parser.start().array(null, {
type: Parser.start()
.uint32le("value_offset")
.uint32le("offset_or_something"),
formatter: (val) => {
return val.map((v) => v.value_offset);
},
length: (items) => items.column_count,
}),
length: (items) => items.row_count,
});
const DataSheetRow = Parser.start()
.saveOffset("offset", { formatter: (val) => val - textOffset })
.string("value", { zeroTerminated: true });
const GetString = Parser.start().string(null, { zeroTerminated: true });
const DataSheetHeader = new Parser()
.uint32le("revision")
.uint32le("unknown1")
.uint32le("unique_id_offset")
.uint32le("unknown2")
.uint32le("type_offset")
.uint32le("row_number")
.uint32le("plain_text_length")
.uint32le("unknown3")
.uint32le("unknown4")
.uint32le("unknown5")
.uint32le("unknown6")
.uint32le("unknown7")
.uint32le("unknown8")
.uint32le("unknown9")
.uint32le("plain_text_offset", {
formatter: (x) => {
const offs = (x as number) + 60;
textOffset = offs;
return offs;
},
})
.uint32be("header_sig", {
formatter: (val) => `0x${val.toString(16).toUpperCase()}`,
})
.uint32le("unknown10")
.uint32le("column_count")
.uint32le("row_count")
.uint32le("unknown11")
.uint32le("unknown12")
.uint32le("unknown13")
.uint32le("unknown14")
.pointer("unique_id", {
type: GetString,
offset: (items) => {
return items.plain_text_offset + items.unique_id_offset;
},
})
.pointer("data_type", {
type: GetString,
offset: (items) => {
return items.plain_text_offset + items.type_offset;
},
})
.array("column_names", {
type: DataSheetColumn,
length: (items) => {
return items.column_count;
},
formatter: (items) => {
return items.map((item) => item.column_name_offset);
},
})
.nest("rows", {
type: DataSheetData,
})
.array("plain_text", {
type: DataSheetRow,
readUntil: "eof",
zeroTerminated: true,
key: "offset",
});
export function parseDataSheet(data: Buffer) {
return new Parser().nest(null, { type: DataSheetHeader }).parse(data);
}
And I've attached a
https://kaitai.io/ struct file as well. Have fun