Skip to content

Commit 4bb7277

Browse files
authored
Merge pull request #27 from LeagueToolkit/alan/feat/league-file
feat: league file stuff
2 parents aa8faf3 + 67e46de commit 4bb7277

File tree

6 files changed

+256
-2
lines changed

6 files changed

+256
-2
lines changed

crates/league-toolkit/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ bitflags = "2.5.0"
2020
byteorder = "1.5.0"
2121
flate2 = "1.0.30"
2222
glam = { version = "0.27.0", features = ["glam-assert"] }
23-
lazy_static = "1.4.0"
23+
lazy_static = "1.5.0"
2424
log = "0.4.21"
2525
memchr = "2.7.2"
2626
num_enum = "0.7.2"

crates/league-toolkit/src/core/mesh/static/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use league_primitives::Color;
66
mod face;
77
mod read;
88

9-
const MAGIC: &[u8] = "r3d2Mesh".as_bytes();
9+
pub const MAGIC: &[u8] = b"r3d2Mesh";
1010

1111
#[derive(Clone, Debug)]
1212
pub struct StaticMesh {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use super::pattern::LEAGUE_FILE_MAGIC_BYTES;
2+
3+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
4+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5+
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
6+
/// The kind of league file (animation, mapgeo, bin, etc)
7+
pub enum LeagueFileKind {
8+
Animation,
9+
Jpeg,
10+
LightGrid,
11+
LuaObj,
12+
MapGeometry,
13+
Png,
14+
Preload,
15+
PropertyBin,
16+
PropertyBinOverride,
17+
RiotStringTable,
18+
SimpleSkin,
19+
Skeleton,
20+
StaticMeshAscii,
21+
StaticMeshBinary,
22+
Svg,
23+
Texture,
24+
TextureDds,
25+
Unknown,
26+
WorldGeometry,
27+
WwiseBank,
28+
WwisePackage,
29+
}
30+
31+
impl LeagueFileKind {
32+
#[inline]
33+
#[must_use]
34+
/// The extension for this file type (anm, mapgeo, bin, etc)
35+
/// ```
36+
/// # use league_toolkit::league_file::LeagueFileKind;
37+
/// assert_eq!(LeagueFileKind::Animation.extension(), Some("anm"));
38+
/// assert_eq!(LeagueFileKind::StaticMeshAscii.extension(), Some("sco"));
39+
/// assert_eq!(LeagueFileKind::Unknown.extension(), None);
40+
///
41+
pub fn extension(&self) -> Option<&'static str> {
42+
Some(match self {
43+
Self::Unknown => return None,
44+
Self::Animation => "anm",
45+
Self::Jpeg => "jpg",
46+
Self::LightGrid => "lightgrid",
47+
Self::LuaObj => "luaobj",
48+
Self::MapGeometry => "mapgeo",
49+
Self::Png => "png",
50+
Self::Preload => "preload",
51+
Self::PropertyBin => "bin",
52+
Self::PropertyBinOverride => "bin",
53+
Self::RiotStringTable => "stringtable",
54+
Self::SimpleSkin => "skn",
55+
Self::Skeleton => "skl",
56+
Self::StaticMeshAscii => "sco",
57+
Self::StaticMeshBinary => "scb",
58+
Self::Texture => "tex",
59+
Self::TextureDds => "dds",
60+
Self::WorldGeometry => "wgeo",
61+
Self::WwiseBank => "bnk",
62+
Self::WwisePackage => "wpk",
63+
Self::Svg => "svg",
64+
})
65+
}
66+
67+
#[must_use]
68+
/// Infer the file type from the extension. Works with or without a preceding `'.'`.
69+
/// ```
70+
/// # use league_toolkit::league_file::LeagueFileKind;
71+
/// #
72+
/// assert_eq!(LeagueFileKind::from_extension("png"), LeagueFileKind::Png);
73+
/// assert_eq!(LeagueFileKind::from_extension(".png"), LeagueFileKind::Png);
74+
/// ```
75+
pub fn from_extension(extension: impl AsRef<str>) -> LeagueFileKind {
76+
let extension = extension.as_ref();
77+
if extension.is_empty() {
78+
return LeagueFileKind::Unknown;
79+
}
80+
81+
let extension = match extension.starts_with('.') {
82+
true => &extension[1..],
83+
false => extension,
84+
};
85+
86+
match extension {
87+
"anm" => Self::Animation,
88+
"bin" => Self::PropertyBin,
89+
"bnk" => Self::WwiseBank,
90+
"dds" => Self::TextureDds,
91+
"jpg" => Self::Jpeg,
92+
"luaobj" => Self::LuaObj,
93+
"mapgeo" => Self::MapGeometry,
94+
"png" => Self::Png,
95+
"preload" => Self::Preload,
96+
"scb" => Self::StaticMeshBinary,
97+
"sco" => Self::StaticMeshAscii,
98+
"skl" => Self::Skeleton,
99+
"skn" => Self::SimpleSkin,
100+
"stringtable" => Self::RiotStringTable,
101+
"svg" => Self::Svg,
102+
"tex" => Self::Texture,
103+
"wgeo" => Self::WorldGeometry,
104+
"wpk" => Self::WwisePackage,
105+
_ => Self::Unknown,
106+
}
107+
}
108+
109+
/// Identify the type of league file from the magic at the start of the file. You must provide at
110+
/// least [`MAX_MAGIC_SIZE`] bytes of data to be able to detect all file types.
111+
///
112+
/// # Examples
113+
/// ```
114+
/// # use league_toolkit::league_file::*;
115+
/// #
116+
/// let data = b"r3d2skltblahblahblahblah";
117+
/// let kind = LeagueFileKind::identify_from_bytes(data);
118+
/// assert_eq!(kind, LeagueFileKind::Skeleton);
119+
/// ```
120+
///
121+
///
122+
/// ## Identifying from a reader
123+
/// ```
124+
/// # use std::fs::File;
125+
/// # use std::io::{self, Cursor, Read};
126+
/// # use league_toolkit::league_file::*;
127+
/// #
128+
/// let mut reader = Cursor::new([0x33, 0x22, 0x11, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]);
129+
/// let mut buffer = [0; MAX_MAGIC_SIZE];
130+
/// reader.read(&mut buffer)?;
131+
///
132+
/// let kind = LeagueFileKind::identify_from_bytes(&buffer);
133+
/// assert_eq!(kind, LeagueFileKind::SimpleSkin);
134+
/// # Ok::<(), io::Error>(())
135+
/// ```
136+
pub fn identify_from_bytes(data: &[u8]) -> LeagueFileKind {
137+
for magic_byte in LEAGUE_FILE_MAGIC_BYTES.iter() {
138+
if magic_byte.matches(data) {
139+
return magic_byte.kind;
140+
}
141+
}
142+
143+
LeagueFileKind::Unknown
144+
}
145+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//! Utility module for identifying/working with League of Legends file types.
2+
//!
3+
//! See [`LeagueFileKind`] for more information.
4+
mod kind;
5+
mod pattern;
6+
7+
pub use kind::*;
8+
pub use pattern::MAX_MAGIC_SIZE;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use lazy_static::lazy_static;
2+
3+
use super::LeagueFileKind;
4+
5+
pub static LEAGUE_FILE_MAGIC_BYTES: &[LeagueFilePattern] = &[
6+
LeagueFilePattern::from_bytes(crate::core::mesh::MAGIC, LeagueFileKind::StaticMeshBinary),
7+
LeagueFilePattern::from_bytes(b"r3d2sklt", LeagueFileKind::Skeleton),
8+
LeagueFilePattern::from_bytes(b"r3d2ammd", LeagueFileKind::Animation),
9+
LeagueFilePattern::from_bytes(b"r3d2canm", LeagueFileKind::Animation),
10+
LeagueFilePattern::from_fn(
11+
|data| u32::from_le_bytes(data[4..8].try_into().unwrap()) == 1,
12+
8,
13+
LeagueFileKind::WwisePackage,
14+
),
15+
LeagueFilePattern::from_fn(|data| &data[1..4] == b"PNG", 4, LeagueFileKind::Png),
16+
LeagueFilePattern::from_bytes(b"DDS ", LeagueFileKind::TextureDds),
17+
LeagueFilePattern::from_bytes(&[0x33, 0x22, 0x11, 0x00], LeagueFileKind::SimpleSkin),
18+
LeagueFilePattern::from_bytes(b"PROP", LeagueFileKind::PropertyBin),
19+
LeagueFilePattern::from_bytes(b"BKHD", LeagueFileKind::WwiseBank),
20+
LeagueFilePattern::from_bytes(b"WGEO", LeagueFileKind::WorldGeometry),
21+
LeagueFilePattern::from_bytes(b"OEGM", LeagueFileKind::MapGeometry),
22+
LeagueFilePattern::from_bytes(b"[Obj", LeagueFileKind::StaticMeshAscii),
23+
LeagueFilePattern::from_fn(|data| &data[1..5] == b"LuaQ", 5, LeagueFileKind::LuaObj),
24+
LeagueFilePattern::from_bytes(b"PreLoad", LeagueFileKind::Preload),
25+
LeagueFilePattern::from_fn(
26+
|data| u32::from_le_bytes(data[..4].try_into().unwrap()) == 3,
27+
4,
28+
LeagueFileKind::LightGrid,
29+
),
30+
LeagueFilePattern::from_bytes(b"RST", LeagueFileKind::RiotStringTable),
31+
LeagueFilePattern::from_bytes(b"PTCH", LeagueFileKind::PropertyBinOverride),
32+
LeagueFilePattern::from_fn(
33+
|data| ((u32::from_le_bytes(data[..4].try_into().unwrap()) & 0x00FFFFFF) == 0x00FFD8FF),
34+
3,
35+
LeagueFileKind::Jpeg,
36+
),
37+
LeagueFilePattern::from_fn(
38+
|data| u32::from_le_bytes(data[4..8].try_into().unwrap()) == 0x22FD4FC3,
39+
8,
40+
LeagueFileKind::Skeleton,
41+
),
42+
LeagueFilePattern::from_bytes(b"TEX\0", LeagueFileKind::Texture),
43+
LeagueFilePattern::from_bytes(b"<svg", LeagueFileKind::Svg),
44+
];
45+
46+
/// The length of the largest possible file type magic, in bytes.
47+
pub const MAX_MAGIC_SIZE: usize = 8;
48+
49+
pub enum LeagueFilePatternKind {
50+
Bytes(&'static [u8]),
51+
Fn(fn(&[u8]) -> bool),
52+
}
53+
54+
pub struct LeagueFilePattern {
55+
pub pattern: LeagueFilePatternKind,
56+
pub min_length: usize,
57+
pub kind: LeagueFileKind,
58+
}
59+
60+
impl LeagueFilePattern {
61+
const fn from_bytes(bytes: &'static [u8], kind: LeagueFileKind) -> Self {
62+
Self {
63+
pattern: LeagueFilePatternKind::Bytes(bytes),
64+
min_length: bytes.len(),
65+
kind,
66+
}
67+
}
68+
69+
const fn from_fn(f: fn(&[u8]) -> bool, min_length: usize, kind: LeagueFileKind) -> Self {
70+
Self {
71+
pattern: LeagueFilePatternKind::Fn(f),
72+
min_length,
73+
kind,
74+
}
75+
}
76+
77+
pub fn matches(&self, data: &[u8]) -> bool {
78+
data.len() >= self.min_length
79+
&& match self.pattern {
80+
LeagueFilePatternKind::Bytes(bytes) => &data[..bytes.len()] == bytes,
81+
LeagueFilePatternKind::Fn(f) => f(data),
82+
}
83+
}
84+
}
85+
86+
mod tests {
87+
use super::*;
88+
89+
#[test]
90+
fn validate_max_magic_size() {
91+
assert_eq!(
92+
MAX_MAGIC_SIZE,
93+
LEAGUE_FILE_MAGIC_BYTES
94+
.iter()
95+
.map(|p| p.min_length)
96+
.max()
97+
.unwrap()
98+
);
99+
}
100+
}

crates/league-toolkit/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod core;
2+
pub mod league_file;
23
pub mod util;

0 commit comments

Comments
 (0)