Skip to content

Commit

Permalink
feat: image data reading/writing using biff
Browse files Browse the repository at this point in the history
  • Loading branch information
francisdb committed Jul 13, 2023
1 parent e4f1bb3 commit 2d9aaa2
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 244 deletions.
10 changes: 8 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ fn extract_images(comp: &mut CompoundFile<File>, records: &[Record], root_dir_pa
.unwrap()
.read_to_end(&mut input)
.unwrap();
let (_, img) = image::read(path.to_owned(), &input).unwrap();
let img = image::read(path.to_owned(), &input);
match &img.jpeg {
Some(jpeg) => {
let ext = img.ext();
Expand Down Expand Up @@ -695,7 +695,7 @@ fn extract_fonts(comp: &mut CompoundFile<File>, records: &[Record], root_dir_pat
.unwrap()
.read_to_end(&mut input)
.unwrap();
let (_, font) = font::read(&input).unwrap();
let font = font::read(&input);

let ext = font.ext();
let mut font_path = fonts_path.clone();
Expand All @@ -713,7 +713,13 @@ fn extract_binaries(comp: &mut CompoundFile<std::fs::File>, root_dir_path: &Path
.filter(|entry| {
entry.is_stream()
&& !entry.path().starts_with("/TableInfo")
&& !entry.path().starts_with("/GameStg/MAC")
&& !entry.path().starts_with("/GameStg/Version")
&& !entry.path().starts_with("/GameStg/GameData")
&& !entry.path().starts_with("/GameStg/CustomInfoTags")
&& !entry.path().to_string_lossy().starts_with("/GameStg/Font")
&& !entry.path().to_string_lossy().starts_with("/GameStg/Image")
&& !entry.path().to_string_lossy().starts_with("/GameStg/Sound")
})
.map(|entry| {
let path = entry.path();
Expand Down
64 changes: 37 additions & 27 deletions src/vpx/biff.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::from_utf8;

use encoding_rs::mem::{decode_latin1, encode_latin1_lossy};
use nom::bytes::streaming::take;
use nom::number::complete::{
Expand All @@ -14,6 +12,7 @@ pub struct BiffReader<'a> {
bytes_in_record_remaining: usize,
record_start: usize,
tag: String,
warn_remaining: bool,
}
// TODO make private
/**
Expand All @@ -31,10 +30,15 @@ impl<'a> BiffReader<'a> {
bytes_in_record_remaining: 0,
record_start: 0,
tag: "".to_string(),
warn_remaining: true,
};
reader
}

pub fn pos(&self) -> usize {
self.pos
}

pub fn tag(&self) -> String {
self.tag.to_string()
}
Expand Down Expand Up @@ -285,11 +289,23 @@ impl<'a> BiffReader<'a> {
d
}

pub fn get_data(&mut self, count: usize) -> &[u8] {
let d = &self.data[self.pos..self.pos + count];
self.pos += count;
self.bytes_in_record_remaining = 0;
d
}

pub fn skip(&mut self, count: usize) {
self.pos += count;
self.bytes_in_record_remaining -= count;
}

pub fn skip_end_tag(&mut self, count: usize) {
self.pos += count;
self.bytes_in_record_remaining = 0;
}

pub fn skip_tag(&mut self) {
self.pos += self.bytes_in_record_remaining;
self.bytes_in_record_remaining = 0;
Expand All @@ -309,6 +325,20 @@ impl<'a> BiffReader<'a> {
self.bytes_in_record_remaining = self.get_u32_no_remaining_update().to_usize();
let tag = self.get_str(RECORD_TAG_LEN.try_into().unwrap());
self.tag = tag;
if self.warn_remaining && self.tag == "ENDB" && self.pos < self.data.len() {
panic!("{} Remaining bytes after ENDB", self.data.len() < self.pos);
}
}

pub fn child_reader(&mut self) -> BiffReader {
BiffReader {
data: &self.data[self.pos..],
pos: 0,
bytes_in_record_remaining: 0,
record_start: 0,
tag: "".to_string(),
warn_remaining: false,
}
}
}

Expand Down Expand Up @@ -486,14 +516,7 @@ impl BiffWriter {
}
}

pub fn read_tag_start(input: &[u8]) -> IResult<&[u8], (&str, u32)> {
let (input, len) = le_u32(input)?;
let (input, name_bytes) = take(4u8)(input)?;
let tag = from_utf8(name_bytes).unwrap();
let n_rest = len - RECORD_TAG_LEN;
Ok((input, (tag, n_rest)))
}

#[deprecated]
pub fn read_string_record(input: &[u8]) -> IResult<&[u8], String> {
let (input, len) = le_u32(input)?;
let (input, data) = take(len)(input)?;
Expand All @@ -505,36 +528,23 @@ pub fn read_string_record(input: &[u8]) -> IResult<&[u8], String> {
Ok((input, string.to_string()))
}

#[deprecated]
pub fn read_bytes_record(input: &[u8]) -> IResult<&[u8], &[u8]> {
let (input, len) = le_u32(input)?;
take(len)(input)
}

#[deprecated]
pub fn read_byte(input: &[u8]) -> IResult<&[u8], u8> {
le_u8(input)
}

#[deprecated]
pub fn read_u32(input: &[u8]) -> IResult<&[u8], u32> {
le_u32(input)
}

#[deprecated]
pub fn read_u16(input: &[u8]) -> IResult<&[u8], u16> {
le_u16(input)
}

pub fn read_float(input: &[u8], n_rest: u32) -> IResult<&[u8], f32> {
assert!(n_rest == 4, "A float record should be 4 bytes long");
let (input, data) = le_f32(input)?;
//let string = String::from_utf8(chars.to_vec()).unwrap();
Ok((input, data))
}

pub fn read_empty_tag(input: &[u8], len: u32) -> IResult<&[u8], ()> {
assert!(len == 0, "a tag should have not have any data");
Ok((input, ()))
}

pub fn drop_record(input: &[u8], len: u32) -> IResult<&[u8], ()> {
let (input, _) = take(len)(input)?;
Ok((input, ()))
}
93 changes: 60 additions & 33 deletions src/vpx/font.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
use nom::{bytes::complete::take, IResult};
use std::fmt;

use super::biff::{read_empty_tag, read_string_record, read_tag_start, read_u32};
use super::biff::{self, BiffReader, BiffWriter};

#[derive(Debug)]
// TODO comment here a vpx file that contains font data

#[derive(PartialEq)]
pub struct FontData {
pub name: String,
pub path: String, // patho of original file for easy re-importing
pub data: Vec<u8>,
}

impl fmt::Debug for FontData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// avoid writing the data to the debug output
f.debug_struct("FontData")
.field("name", &self.name)
.field("path", &self.path)
.field("data", &format!("<{} bytes>", self.data.len()))
.finish()
}
}

impl FontData {
pub(crate) fn ext(&self) -> String {
// TODO we might want to also check the jpeg fsPath
Expand All @@ -19,54 +32,68 @@ impl FontData {
}
}

pub fn read(input: &[u8]) -> IResult<&[u8], FontData> {
let mut input = input;
pub fn read(input: &[u8]) -> FontData {
let mut reader = BiffReader::new(input);
let mut name: String = "".to_string();
let mut path: String = "".to_string();
let mut size_opt: Option<u32> = None;
let mut data: &[u8] = &[];
while !input.is_empty() {
let (i, (tag, len)) = read_tag_start(input)?;
input = match tag {
let mut data: Vec<u8> = vec![];
loop {
reader.next(biff::WARN);
if reader.is_eof() {
break;
}
let tag = reader.tag();
let tag_str = tag.as_str();
match tag_str {
"NAME" => {
let (i, string) = read_string_record(i)?;
name = string.to_owned();
i
name = reader.get_string();
}
"PATH" => {
let (i, string) = read_string_record(i)?;
path = string.to_owned();
i
path = reader.get_string();
}
"SIZE" => {
let (i, num) = read_u32(i)?;
size_opt = Some(num);
i
size_opt = Some((reader.get_u32()).to_owned());
}
"DATA" => match size_opt {
Some(size) => {
let (i, d) = take(size)(i)?;
data = d;
i
let d = reader.get_data(size.try_into().unwrap());
data = d.to_owned();
}
None => {
panic!("DATA tag without SIZE tag");
}
},
"ENDB" => {
// ENDB is just a tag, it should have a remaining length of 0
// dbg!(tag, len);
let (i, _) = read_empty_tag(i, len)?;
i
}
_ => {
println!("Skipping font tag: {} len: {}", tag, len);
let (i, _) = take(len)(i)?;
i
println!("Skipping font tag: {}", tag_str);
reader.skip_tag();
}
}
}
let rest = &[];
let data = data.to_vec();
Ok((rest, FontData { name, path, data }))
FontData { name, path, data }
}

pub fn write(font_data: &FontData) -> Vec<u8> {
let mut writer = BiffWriter::new();
writer.write_tagged_string("NAME", &font_data.name);
writer.write_tagged_string("PATH", &font_data.path);
writer.write_tagged_u32("SIZE", font_data.data.len().try_into().unwrap());
writer.write_tagged_data("DATA", &font_data.data);
writer.close(true);
writer.get_data().to_owned()
}

#[test]
fn read_write() {
use pretty_assertions::assert_eq;

let font = FontData {
name: "test_name".to_string(),
path: "/tmp/test".to_string(),
data: vec![1, 2, 3, 4],
};
let bytes = write(&font);
let font_read = read(&bytes);

assert_eq!(font, font_read);
}
52 changes: 25 additions & 27 deletions src/vpx/gamedata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,59 +92,57 @@ pub fn read_all_gamedata_records(input: &[u8]) -> Vec<Record> {
fn read_record(tag_str: &str, reader: &mut BiffReader<'_>) -> Record {
let rec = match tag_str {
"LEFT" => {
let n = &reader.get_u32();
Record::PlayfieldLeft(*n)
let n = reader.get_u32();
Record::PlayfieldLeft(n)
}
"TOPX" => {
let n = &reader.get_u32();
Record::PlayfieldTopX(*n)
let n = reader.get_u32();
Record::PlayfieldTopX(n)
}
"RGHT" => {
let n = &reader.get_u32();
Record::PlayfieldRight(*n)
let n = reader.get_u32();
Record::PlayfieldRight(n)
}
"BOTM" => {
let n = &reader.get_u32();
Record::PlayfieldBottom(*n)
let n = reader.get_u32();
Record::PlayfieldBottom(n)
}
"NAME" => {
let s = &reader.get_wide_string();
Record::Name(s.to_string())
let s = reader.get_wide_string();
Record::Name(s)
}
"CODE" => {
let len = reader.get_u32_no_remaining_update();
let s = &reader.get_str_no_remaining_update(len as usize);
Record::Code {
script: s.to_string(),
}
let script = reader.get_str_no_remaining_update(len as usize);
Record::Code { script }
}
"MASI" => {
let n = &reader.get_u32();
Record::MaterialsSize(*n)
let n = reader.get_u32();
Record::MaterialsSize(n)
}
"SEDT" => {
let n = &reader.get_u32();
Record::GameItemsSize(*n)
let n = reader.get_u32();
Record::GameItemsSize(n)
}
"SSND" => {
let n = &reader.get_u32();
Record::SoundsSize(*n)
let n = reader.get_u32();
Record::SoundsSize(n)
}
"SIMG" => {
let n = &reader.get_u32();
Record::ImagesSize(*n)
let n = reader.get_u32();
Record::ImagesSize(n)
}
"SFNT" => {
let n = &reader.get_u32();
Record::FontsSize(*n)
let n = reader.get_u32();
Record::FontsSize(n)
}
"SCOL" => {
let n = &reader.get_u32();
Record::CollectionsSize(*n)
let n = reader.get_u32();
Record::CollectionsSize(n)
}
other => {
//dbg!(other);
let data = &reader.get_record_data(false);
let data = reader.get_record_data(false);
let tag_str = other.to_string();
Record::Unknown {
name: tag_str,
Expand Down
Loading

0 comments on commit 2d9aaa2

Please sign in to comment.