feat: add support for ttmp v1
This commit is contained in:
parent
065b6cc8e2
commit
1a4588bb8d
|
@ -16,7 +16,6 @@ pub fn main() {
|
|||
let files = extractor.all_files_sorted();
|
||||
|
||||
let mut data_file = zip.by_name("TTMPD.mpd").unwrap();
|
||||
let version = &*extractor.manifest().version;
|
||||
|
||||
std::fs::create_dir_all("files").unwrap();
|
||||
|
||||
|
@ -69,31 +68,3 @@ pub struct SavedFile {
|
|||
pub group: Option<String>,
|
||||
pub option: Option<String>,
|
||||
}
|
||||
|
||||
struct Multiplexer<W1, W2> {
|
||||
one: W1,
|
||||
two: W2,
|
||||
}
|
||||
|
||||
impl<W1, W2> Multiplexer<W1, W2> {
|
||||
fn new(one: W1, two: W2) -> Self {
|
||||
Self {
|
||||
one,
|
||||
two,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W1: Write, W2: Write> Write for Multiplexer<W1, W2> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let one = self.one.write(buf);
|
||||
let two = self.two.write(buf);
|
||||
one.and(two)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
let one = self.one.flush();
|
||||
let two = self.two.flush();
|
||||
one.and(two)
|
||||
}
|
||||
}
|
||||
|
|
41
src/lib.rs
41
src/lib.rs
|
@ -1,18 +1,51 @@
|
|||
use std::io::{Read, Seek};
|
||||
|
||||
use serde::de::Error as _;
|
||||
use serde::Deserialize;
|
||||
use serde_json::de::IoRead;
|
||||
use serde_json::StreamDeserializer;
|
||||
pub use zip::{read::ZipFile, ZipArchive};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::model::ModPack;
|
||||
use crate::model::{ModPack, SimpleMod};
|
||||
use crate::model::manifest_kind::ManifestKind;
|
||||
|
||||
pub mod model;
|
||||
pub mod error;
|
||||
pub(crate) mod tracking_reader;
|
||||
pub mod ttmp_extractor;
|
||||
|
||||
pub fn from_reader<R: Read + Seek>(reader: R) -> Result<(ModPack, ZipArchive<R>)> {
|
||||
pub fn from_reader<R: Read + Seek>(reader: R) -> Result<(ManifestKind, ZipArchive<R>)> {
|
||||
let mut zip = ZipArchive::new(reader).map_err(Error::Zip)?;
|
||||
let manifest = zip.by_name("TTMPL.mpl").map_err(Error::Zip)?;
|
||||
let manifest = serde_json::from_reader(manifest).map_err(Error::InvalidManifest)?;
|
||||
Ok((manifest, zip))
|
||||
let mut manifest = StreamDeserializer::new(IoRead::new(manifest))
|
||||
.collect::<Result<Vec<DesManifest>, _>>()
|
||||
.map_err(Error::InvalidManifest)?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum DesManifest {
|
||||
V1(SimpleMod),
|
||||
V2(ModPack),
|
||||
}
|
||||
|
||||
let all_v1 = manifest.iter().all(|inner| matches!(inner, DesManifest::V1(_)));
|
||||
let transformed = if all_v1 {
|
||||
let mods: Vec<SimpleMod> = manifest.into_iter()
|
||||
.map(|inner| match inner {
|
||||
DesManifest::V1(inner) => inner,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
ManifestKind::V1(mods)
|
||||
} else if manifest.len() == 1 && matches!(&manifest[0], DesManifest::V2(_)) {
|
||||
ManifestKind::V2(match manifest.remove(0) {
|
||||
DesManifest::V2(pack) => pack,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
} else {
|
||||
return Err(Error::InvalidManifest(serde_json::Error::custom("unrecognised manifest")));
|
||||
};
|
||||
|
||||
Ok((transformed, zip))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
use crate::model::{ModPack, SimpleMod};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ManifestKind {
|
||||
V1(Vec<SimpleMod>),
|
||||
V2(ModPack),
|
||||
}
|
||||
|
||||
impl ManifestKind {
|
||||
pub fn simple_mods_list(&self) -> &[SimpleMod] {
|
||||
match self {
|
||||
Self::V1(mods) => mods,
|
||||
Self::V2(pack) => match &pack.simple_mods_list {
|
||||
Some(list) => list,
|
||||
None => &[],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ pub use self::{
|
|||
simple_mod::SimpleMod,
|
||||
};
|
||||
|
||||
pub mod manifest_kind;
|
||||
pub mod mod_group;
|
||||
pub mod mod_option;
|
||||
pub mod mod_pack;
|
||||
|
|
|
@ -7,8 +7,10 @@ pub struct SimpleMod {
|
|||
pub category: String,
|
||||
pub full_path: String,
|
||||
pub dat_file: String,
|
||||
#[serde(default)]
|
||||
pub is_default: bool,
|
||||
pub mod_offset: usize,
|
||||
pub mod_size: usize,
|
||||
#[serde(default)]
|
||||
pub mod_pack_entry: Option<serde_json::Value>,
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ use sqpack::binrw::{BinRead, BinWriterExt, VecArgs};
|
|||
use sqpack::binrw::meta::ReadEndian;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::{Error, ModPack};
|
||||
use crate::Error;
|
||||
use crate::error::Result;
|
||||
use crate::model::manifest_kind::ManifestKind;
|
||||
use crate::model::SimpleMod;
|
||||
use crate::tracking_reader::TrackingReader;
|
||||
|
||||
|
@ -20,7 +21,7 @@ impl<T> WriteSeek for T
|
|||
where T: Write + Seek {}
|
||||
|
||||
pub struct TtmpExtractor<R> {
|
||||
manifest: ModPack,
|
||||
manifest: ManifestKind,
|
||||
zip: RefCell<ZipArchive<R>>,
|
||||
}
|
||||
|
||||
|
@ -34,11 +35,11 @@ impl<R: Read + Seek> TtmpExtractor<R> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn manifest(&self) -> &ModPack {
|
||||
pub fn manifest(&self) -> &ManifestKind {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
pub fn into_manifest(self) -> ModPack {
|
||||
pub fn into_manifest(self) -> ManifestKind {
|
||||
self.manifest
|
||||
}
|
||||
|
||||
|
@ -92,34 +93,34 @@ impl<R: Read + Seek> TtmpExtractor<R> {
|
|||
let mut all_files = Vec::new();
|
||||
|
||||
let mut seen_groups: HashMap<&str, usize> = HashMap::new();
|
||||
if let Some(pages) = &self.manifest.mod_pack_pages {
|
||||
for page in pages {
|
||||
for group in &page.mod_groups {
|
||||
let seen = seen_groups.entry(&group.group_name).or_default();
|
||||
*seen += 1;
|
||||
if let ManifestKind::V2(pack) = &self.manifest {
|
||||
if let Some(pages) = &pack.mod_pack_pages {
|
||||
for page in pages {
|
||||
for group in &page.mod_groups {
|
||||
let seen = seen_groups.entry(&group.group_name).or_default();
|
||||
*seen += 1;
|
||||
|
||||
for option in &group.option_list {
|
||||
for file in &option.mods_jsons {
|
||||
all_files.push(ModFile {
|
||||
group: Some(&*option.group_name),
|
||||
group_occurence: *seen,
|
||||
option: Some(&*option.name),
|
||||
file,
|
||||
});
|
||||
for option in &group.option_list {
|
||||
for file in &option.mods_jsons {
|
||||
all_files.push(ModFile {
|
||||
group: Some(&*option.group_name),
|
||||
group_occurence: *seen,
|
||||
option: Some(&*option.name),
|
||||
file,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(list) = &self.manifest.simple_mods_list {
|
||||
all_files.extend(list.iter().map(|file| ModFile {
|
||||
group: None,
|
||||
group_occurence: 0,
|
||||
option: None,
|
||||
file,
|
||||
}));
|
||||
}
|
||||
all_files.extend(self.manifest.simple_mods_list().iter().map(|file| ModFile {
|
||||
group: None,
|
||||
group_occurence: 0,
|
||||
option: None,
|
||||
file,
|
||||
}));
|
||||
|
||||
all_files.sort_unstable_by_key(|file| file.file.mod_offset);
|
||||
all_files
|
||||
|
@ -293,9 +294,11 @@ impl<R: Read> TtmpExtractor<R> {
|
|||
|
||||
fn extract_texture_file<T: Read, W: Write>(info: &SqPackFileInfoHeader, mut reader: T, mut writer: W, buf: &mut [u8]) -> Result<()> {
|
||||
let std_info: SqPackFileInfo = Self::read_struct(&mut reader, buf)?;
|
||||
println!("{:#?}", std_info);
|
||||
let blocks: Vec<LodBlock> = (0..std_info.number_of_blocks)
|
||||
.map(|_| Self::read_struct(&mut reader, buf))
|
||||
.collect::<Result<_>>()?;
|
||||
println!("{:#?}", blocks);
|
||||
|
||||
let sub_block_count = blocks
|
||||
.iter()
|
||||
|
@ -341,6 +344,7 @@ impl<R: Read> TtmpExtractor<R> {
|
|||
|
||||
fn read_block_into<T: Read, W: Write>(mut reader: T, mut writer: W, buf: &mut [u8], size: usize) -> Result<u64> {
|
||||
let header: DatBlockHeader = Self::read_struct(&mut reader, buf)?;
|
||||
println!("{:#?}", header);
|
||||
|
||||
let (read, actual) = if header.compressed_size == 32_000 {
|
||||
// uncompressed
|
||||
|
|
Loading…
Reference in New Issue