Compare commits

...

4 Commits

Author SHA1 Message Date
Anna 0bf9129986
chore: bump version to 6.0.0 2023-03-21 18:33:50 -04:00
Anna f39eab6c51
refactor: allow using only the model without any logic 2023-03-21 18:33:42 -04:00
Anna 0a00b48c4d
fix: ignore chunks with 0 size 2023-03-21 18:24:25 -04:00
Anna f31dfd704d
fix: handle deduped ttmps 2023-03-21 18:24:07 -04:00
11 changed files with 93 additions and 48 deletions

View File

@ -1,23 +1,42 @@
[package]
name = "ttmp"
version = "5.2.0"
version = "6.0.0"
edition = "2021"
autoexamples = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
model = [
"serde_json",
]
logic = [
"model",
"blake3",
"crossbeam-channel",
"crossbeam-utils",
"flate2",
"num_cpus",
"serde",
"serde_json",
"sqpack",
"tempfile",
"thiserror",
"zip",
]
[dependencies]
blake3 = { version = "1", features = ["traits-preview"] }
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
flate2 = { version = "1", default-features = false, features = ["zlib-ng"] }
num_cpus = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqpack = { git = "https://git.anna.lgbt/ascclemens/sqpack-rs", features = ["read", "write"] }
tempfile = "3"
thiserror = "1"
zip = { version = "0.6", default-features = false, features = ["deflate-zlib"] }
blake3 = { version = "1", features = ["traits-preview"], optional = true }
crossbeam-channel = { version = "0.5", optional = true }
crossbeam-utils = { version = "0.8", optional = true }
flate2 = { version = "1", default-features = false, features = ["zlib-ng"], optional = true }
num_cpus = { version = "1", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true }
sqpack = { git = "https://git.anna.lgbt/ascclemens/sqpack-rs", features = ["read", "write"], optional = true }
tempfile = { version = "3", optional = true }
thiserror = { version = "1", optional = true }
zip = { version = "0.6", default-features = false, features = ["deflate-zlib"], optional = true }
[dev-dependencies]
anyhow = "1"

View File

@ -1,22 +1,41 @@
#[cfg(feature = "logic")]
use std::io::{Read, Seek};
use serde::de::Error as _;
use serde::Deserialize;
use serde_json::StreamDeserializer;
use serde_json::de::IoRead;
#[cfg(feature = "logic")]
use serde::{
de::Error as _,
Deserialize,
};
#[cfg(feature = "logic")]
use serde_json::{
de::IoRead,
StreamDeserializer,
};
#[cfg(feature = "logic")]
pub use zip::{read::ZipFile, ZipArchive};
#[cfg(feature = "logic")]
use crate::{
model::{ModPack, SimpleMod},
model::manifest_kind::ManifestKind,
};
#[cfg(feature = "logic")]
use crate::error::{Error, Result};
use crate::model::{ModPack, SimpleMod};
use crate::model::manifest_kind::ManifestKind;
#[cfg(feature = "model")]
pub mod model;
#[cfg(feature = "logic")]
pub mod error;
#[cfg(feature = "logic")]
pub(crate) mod tracking_reader;
#[cfg(feature = "logic")]
pub mod ttmp_extractor;
#[cfg(feature = "logic")]
pub mod mpd_encoder;
#[cfg(feature = "logic")]
pub(crate) mod util;
#[cfg(feature = "logic")]
pub fn from_value(value: serde_json::Value) -> Result<ManifestKind> {
let manifest = if value.is_array() {
ManifestKind::V1(serde_json::from_value(value).map_err(Error::InvalidManifest)?)
@ -27,6 +46,7 @@ pub fn from_value(value: serde_json::Value) -> Result<ManifestKind> {
Ok(manifest)
}
#[cfg(feature = "logic")]
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)?;

View File

@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::model::{ModPack, SimpleMod};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "snake_case")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ManifestKind {
V1(Vec<SimpleMod>),
V2(ModPack),

View File

@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::model::{ModOption, SelectionType};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
pub struct ModGroup {
pub group_name: String,
pub selection_type: SelectionType,

View File

@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::model::{SelectionType, SimpleMod};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
pub struct ModOption {
pub name: String,
pub description: Option<String>,

View File

@ -1,12 +1,11 @@
use serde::{Deserialize, Serialize};
use crate::model::{ModPackPage, SimpleMod};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
pub struct ModPack {
pub minimum_framework_version: Option<String>,
#[serde(rename = "TTMPVersion")]
#[cfg_attr(feature = "serde", serde(rename = "TTMPVersion"))]
pub ttmp_version: String,
pub name: String,
pub author: String,

View File

@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::model::ModGroup;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
pub struct ModPackPage {
pub page_index: i32,
pub mod_groups: Vec<ModGroup>,

View File

@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum SelectionType {
Single,
Multi,

View File

@ -1,19 +1,18 @@
use serde::{Deserialize, Serialize};
use crate::model::ModPack;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
pub struct SimpleMod {
pub name: String,
pub category: String,
pub full_path: String,
pub dat_file: String,
#[serde(default)]
#[cfg_attr(feature = "serde", serde(default))]
pub is_default: bool,
pub mod_offset: usize,
pub mod_size: usize,
#[serde(default)]
#[cfg_attr(feature = "serde", serde(default))]
pub mod_pack_entry: Option<serde_json::Value>,
}

View File

@ -268,6 +268,10 @@ impl MpdEncoder {
.map(|info| info.uncompressed_size as u32)
.sum();
if compressed_size == 0 || decompressed_size == 0 {
continue;
}
lod_blocks.push(LodBlock {
compressed_offset: compressed_offset as u32,
compressed_size,
@ -281,6 +285,7 @@ impl MpdEncoder {
sub_blocks.extend(infos);
}
// FIXME: fails on minions defined
assert_eq!(sub_blocks_len, sub_blocks.len());
let after_data = self.writer.stream_position().map_err(Error::Io)?;

View File

@ -52,8 +52,16 @@ impl<R: Read + Seek> TtmpExtractor<R> {
let mut data_file = TrackingReader::new(zip.by_name("TTMPD.mpd")
.map_err(Error::MissingDataFile)?);
let mut last_offset = None;
let mut buf = [0; 4096];
for mod_file in all_files {
// handle deduped ttmps
if Some(mod_file.file.mod_offset) == last_offset {
continue;
}
last_offset = Some(mod_file.file.mod_offset);
let file = mod_file.file;
data_file.read = 0;