Skip to content

Commit 88c606e

Browse files
committed
feat: new code
1 parent aae7d42 commit 88c606e

File tree

9 files changed

+454
-123
lines changed

9 files changed

+454
-123
lines changed

crates/io-ext/src/writer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ pub trait WriterExt: Write {
2121
Ok(())
2222
}
2323

24+
/// Writes a string with a length prefix (writes sizeof(str.len()) + str.len() bytes)
25+
fn write_len_prefixed_string_better<T: ByteOrder>(
26+
&mut self,
27+
str: impl AsRef<str>,
28+
) -> io::Result<()> {
29+
let str = str.as_ref();
30+
self.write_u16::<T>(str.len() as _)?;
31+
self.write_all(str.as_bytes())?;
32+
Ok(())
33+
}
34+
2435
/// Writes a string with a null terminator (writes sizeof(str) + 1 bytes)
2536
fn write_terminated_string<S: AsRef<str>>(&mut self, str: S) -> io::Result<()> {
2637
self.write_all(str.as_ref().as_bytes())?;

crates/league-modpkg/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ byteorder = "1.5.0"
99
io-ext = { path = "../io-ext" }
1010
serde = { version = "1.0", features = ["derive"] }
1111
rmp-serde = "1.3.0"
12+
twox-hash = { version = "2.0.1", features = ["xxhash64"] }

crates/league-modpkg/src/chunk.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::error::ModpkgError;
1+
use crate::{error::ModpkgError, ModpkgCompression};
22
use byteorder::{ReadBytesExt as _, LE};
33
use std::io::{BufReader, Read};
44

@@ -7,6 +7,7 @@ pub struct ModpkgChunk {
77
path_hash: u64,
88

99
data_offset: usize,
10+
compression: ModpkgCompression,
1011
compressed_size: usize,
1112
uncompressed_size: usize,
1213

@@ -15,14 +16,15 @@ pub struct ModpkgChunk {
1516

1617
path_index: u32,
1718
wad_paths_index: u32,
18-
layers_index: u32,
19+
layer_index: u32,
1920
}
2021

2122
impl ModpkgChunk {
2223
pub fn read(reader: &mut BufReader<impl Read>) -> Result<Self, ModpkgError> {
2324
let path_hash = reader.read_u64::<LE>()?;
2425

2526
let data_offset = reader.read_u64::<LE>()?;
27+
let compression = ModpkgCompression::try_from(reader.read_u8()?)?;
2628
let compressed_size = reader.read_u64::<LE>()?;
2729
let uncompressed_size = reader.read_u64::<LE>()?;
2830

@@ -31,19 +33,19 @@ impl ModpkgChunk {
3133

3234
let path_index = reader.read_u32::<LE>()?;
3335
let wad_paths_index = reader.read_u32::<LE>()?;
34-
let layers_index = reader.read_u32::<LE>()?;
35-
let _ = reader.read_u32::<LE>()?; // reserved
36+
let layer_index = reader.read_u32::<LE>()?;
3637

3738
Ok(Self {
3839
path_hash,
3940
data_offset: data_offset as usize,
41+
compression,
4042
compressed_size: compressed_size as usize,
4143
uncompressed_size: uncompressed_size as usize,
4244
compressed_checksum,
4345
uncompressed_checksum,
4446
path_index,
4547
wad_paths_index,
46-
layers_index,
48+
layer_index,
4749
})
4850
}
4951

@@ -54,6 +56,9 @@ impl ModpkgChunk {
5456
pub fn data_offset(&self) -> usize {
5557
self.data_offset
5658
}
59+
pub fn compression(&self) -> ModpkgCompression {
60+
self.compression
61+
}
5762
pub fn compressed_size(&self) -> usize {
5863
self.compressed_size
5964
}
@@ -74,7 +79,7 @@ impl ModpkgChunk {
7479
pub fn wad_paths_index(&self) -> u32 {
7580
self.wad_paths_index
7681
}
77-
pub fn layers_index(&self) -> u32 {
78-
self.layers_index
82+
pub fn layer_index(&self) -> u32 {
83+
self.layer_index
7984
}
8085
}

crates/league-modpkg/src/error.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use thiserror::Error;
22

3+
use crate::ModpkgCompression;
4+
35
#[derive(Error, Debug)]
46
pub enum ModpkgError {
57
#[error("IO error: {0}")]
@@ -11,10 +13,20 @@ pub enum ModpkgError {
1113
InvalidHeaderSize { header_size: u32, actual_size: u64 },
1214
#[error("Chunks are not in ascending order: previous: {previous}, current: {current}")]
1315
UnsortedChunks { previous: u64, current: u64 },
16+
#[error("Missing metadata chunk")]
17+
MissingMetadata,
1418
#[error("Missing base layer")]
1519
MissingBaseLayer,
1620
#[error("Invalid modpkg compression type: {0}")]
1721
InvalidCompressionType(u8),
22+
#[error(
23+
"Unexpected compression type: chunk: {chunk:x}, expected: {expected}, actual: {actual}"
24+
)]
25+
UnexpectedCompressionType {
26+
chunk: u64,
27+
expected: ModpkgCompression,
28+
actual: ModpkgCompression,
29+
},
1830
#[error("Invalid modpkg license type: {0}")]
1931
InvalidLicenseType(u8),
2032
#[error("Invalid modpkg magic: {0}")]
@@ -23,6 +35,8 @@ pub enum ModpkgError {
2335
InvalidVersion(u32),
2436
#[error("Duplicate chunk: {0}")]
2537
DuplicateChunk(u64),
38+
#[error("Chunk not found: {0:x}")]
39+
MissingChunk(u64),
2640

2741
#[error("Msgpack decode error: {0}")]
2842
MsgpackDecode(#[from] rmp_serde::decode::Error),

crates/league-modpkg/src/lib.rs

Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
use chunk::ModpkgChunk;
2-
use serde::{Deserialize, Serialize};
3-
use std::collections::HashMap;
4-
2+
use error::ModpkgError;
3+
use metadata::ModpkgMetadata;
4+
use std::{collections::HashMap, fmt::Display, io};
55
mod chunk;
66
mod error;
7+
mod license;
8+
mod metadata;
79
mod read;
10+
mod utils;
11+
12+
pub const METADATA_CHUNK_NAME: &str = "__metadata__";
13+
pub const LAYERS_CHUNK_NAME: &str = "__layers__";
14+
pub const WADS_CHUNK_NAME: &str = "__wads__";
15+
pub const CHUNK_PATHS_CHUNK_NAME: &str = "__chunk_paths__";
16+
17+
pub const METADATA_CHUNK_HASH: u64 = 0xc3b02c1cbcdff91f;
18+
pub const LAYERS_CHUNK_HASH: u64 = 0xe8f354f18f398ee1;
19+
pub const WADS_CHUNK_HASH: u64 = 0x67c34d7d3d2900df;
20+
pub const CHUNK_PATHS_CHUNK_HASH: u64 = 0xbe4dc608d6e153c0;
821

922
#[derive(Debug, PartialEq)]
10-
pub struct Modpkg {
23+
pub struct Modpkg<TSource: io::Read + io::Seek> {
1124
metadata: ModpkgMetadata,
1225
chunk_paths: Vec<String>,
1326
wad_paths: Vec<String>,
1427
chunks: HashMap<u64, ModpkgChunk>,
28+
29+
source: TSource,
1530
}
1631

17-
impl Modpkg {
32+
impl<TSource: io::Read + io::Seek> Modpkg<TSource> {
1833
pub fn metadata(&self) -> &ModpkgMetadata {
1934
&self.metadata
2035
}
@@ -23,68 +38,33 @@ impl Modpkg {
2338
}
2439
}
2540

26-
#[derive(Debug, PartialEq, Serialize, Deserialize)]
27-
pub struct ModpkgMetadata {
28-
name: String,
29-
display_name: String,
30-
description: Option<String>,
31-
version: String,
32-
distributor: Option<String>,
33-
authors: Vec<ModpkgAuthor>,
34-
license: ModpkgLicense,
35-
}
36-
37-
impl ModpkgMetadata {
38-
pub fn name(&self) -> &str {
39-
&self.name
40-
}
41-
pub fn display_name(&self) -> &str {
42-
&self.display_name
43-
}
44-
pub fn description(&self) -> Option<&str> {
45-
self.description.as_deref()
46-
}
47-
pub fn version(&self) -> &str {
48-
&self.version
49-
}
50-
pub fn distributor(&self) -> Option<&str> {
51-
self.distributor.as_deref()
52-
}
53-
pub fn authors(&self) -> &[ModpkgAuthor] {
54-
&self.authors
55-
}
56-
pub fn license(&self) -> &ModpkgLicense {
57-
&self.license
58-
}
59-
}
60-
61-
#[derive(Debug, PartialEq, Serialize, Deserialize)]
62-
pub enum ModpkgLicense {
63-
None,
64-
Spdx { spdx_id: String },
65-
Custom { name: String, url: String },
66-
}
67-
68-
#[derive(Debug, PartialEq, Serialize, Deserialize)]
69-
pub struct ModpkgAuthor {
70-
name: String,
71-
role: Option<String>,
72-
}
73-
74-
#[derive(Debug, PartialEq, Eq)]
41+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
7542
pub enum ModpkgCompression {
7643
None = 0,
7744
Zstd = 1,
7845
}
7946

47+
impl Display for ModpkgCompression {
48+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49+
write!(
50+
f,
51+
"{:?}",
52+
match self {
53+
ModpkgCompression::None => "none",
54+
ModpkgCompression::Zstd => "zstd",
55+
}
56+
)
57+
}
58+
}
59+
8060
impl TryFrom<u8> for ModpkgCompression {
81-
type Error = &'static str;
61+
type Error = ModpkgError;
8262

8363
fn try_from(value: u8) -> Result<Self, Self::Error> {
8464
Ok(match value {
8565
0 => ModpkgCompression::None,
8666
1 => ModpkgCompression::Zstd,
87-
_ => return Err("Invalid modpkg compression value"),
67+
_ => return Err(ModpkgError::InvalidCompressionType(value)),
8868
})
8969
}
9070
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::io::{BufReader, Read, Write};
2+
3+
use byteorder::{ReadBytesExt as _, WriteBytesExt as _, LE};
4+
use io_ext::{ReaderExt as _, WriterExt as _};
5+
6+
use crate::error::ModpkgError;
7+
8+
#[derive(Debug, PartialEq, Default)]
9+
pub enum ModpkgLicense {
10+
#[default]
11+
None,
12+
Spdx {
13+
spdx_id: String,
14+
},
15+
Custom {
16+
name: String,
17+
url: String,
18+
},
19+
}
20+
21+
impl ModpkgLicense {
22+
pub fn read(reader: &mut BufReader<impl Read>) -> Result<Self, ModpkgError> {
23+
let license_type = reader.read_u8()?;
24+
match license_type {
25+
0 => Ok(Self::None),
26+
1 => Ok(Self::Spdx {
27+
spdx_id: reader.read_len_prefixed_string::<LE>()?,
28+
}),
29+
2 => Ok(Self::Custom {
30+
name: reader.read_len_prefixed_string::<LE>()?,
31+
url: reader.read_len_prefixed_string::<LE>()?,
32+
}),
33+
_ => Err(ModpkgError::InvalidLicenseType(license_type)),
34+
}
35+
}
36+
37+
pub fn write(&self, writer: &mut impl Write) -> Result<(), ModpkgError> {
38+
writer.write_u8(match self {
39+
Self::None => 0,
40+
Self::Spdx { .. } => 1,
41+
Self::Custom { .. } => 2,
42+
})?;
43+
44+
match self {
45+
Self::Spdx { spdx_id } => {
46+
writer.write_len_prefixed_string_better::<LE>(spdx_id)?;
47+
48+
Ok(())
49+
}
50+
Self::Custom { name, url } => {
51+
writer.write_len_prefixed_string_better::<LE>(name)?;
52+
writer.write_len_prefixed_string_better::<LE>(url)?;
53+
54+
Ok(())
55+
}
56+
Self::None => Ok(()),
57+
}
58+
}
59+
60+
pub fn size(&self) -> usize {
61+
match self {
62+
Self::None => 1,
63+
Self::Spdx { spdx_id } => 1 + 4 + spdx_id.len(),
64+
Self::Custom { name, url } => 1 + 4 + 4 + name.len() + url.len(),
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)