Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/league-toolkit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ bitflags = "2.5.0"
byteorder = "1.5.0"
flate2 = "1.0.30"
glam = { version = "0.27.0", features = ["glam-assert"] }
lazy_static = "1.4.0"
lazy_static = "1.5.0"
log = "0.4.21"
memchr = "2.7.2"
num_enum = "0.7.2"
Expand Down
2 changes: 1 addition & 1 deletion crates/league-toolkit/src/core/mesh/static/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use league_primitives::Color;
mod face;
mod read;

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

#[derive(Clone, Debug)]
pub struct StaticMesh {
Expand Down
145 changes: 145 additions & 0 deletions crates/league-toolkit/src/league_file/kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use super::pattern::LEAGUE_FILE_MAGIC_BYTES;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
/// The kind of league file (animation, mapgeo, bin, etc)
pub enum LeagueFileKind {
Animation,
Jpeg,
LightGrid,
LuaObj,
MapGeometry,
Png,
Preload,
PropertyBin,
PropertyBinOverride,
RiotStringTable,
SimpleSkin,
Skeleton,
StaticMeshAscii,
StaticMeshBinary,
Svg,
Texture,
TextureDds,
Unknown,
WorldGeometry,
WwiseBank,
WwisePackage,
}

impl LeagueFileKind {
#[inline]
#[must_use]
/// The extension for this file type (anm, mapgeo, bin, etc)
/// ```
/// # use league_toolkit::league_file::LeagueFileKind;
/// assert_eq!(LeagueFileKind::Animation.extension(), Some("anm"));
/// assert_eq!(LeagueFileKind::StaticMeshAscii.extension(), Some("sco"));
/// assert_eq!(LeagueFileKind::Unknown.extension(), None);
///
pub fn extension(&self) -> Option<&'static str> {
Some(match self {
Self::Unknown => return None,
Self::Animation => "anm",
Self::Jpeg => "jpg",
Self::LightGrid => "lightgrid",
Self::LuaObj => "luaobj",
Self::MapGeometry => "mapgeo",
Self::Png => "png",
Self::Preload => "preload",
Self::PropertyBin => "bin",
Self::PropertyBinOverride => "bin",
Self::RiotStringTable => "stringtable",
Self::SimpleSkin => "skn",
Self::Skeleton => "skl",
Self::StaticMeshAscii => "sco",
Self::StaticMeshBinary => "scb",
Self::Texture => "tex",
Self::TextureDds => "dds",
Self::WorldGeometry => "wgeo",
Self::WwiseBank => "bnk",
Self::WwisePackage => "wpk",
Self::Svg => "svg",
})
}

#[must_use]
/// Infer the file type from the extension. Works with or without a preceding `'.'`.
/// ```
/// # use league_toolkit::league_file::LeagueFileKind;
/// #
/// assert_eq!(LeagueFileKind::from_extension("png"), LeagueFileKind::Png);
/// assert_eq!(LeagueFileKind::from_extension(".png"), LeagueFileKind::Png);
/// ```
pub fn from_extension(extension: impl AsRef<str>) -> LeagueFileKind {
let extension = extension.as_ref();
if extension.is_empty() {
return LeagueFileKind::Unknown;
}

let extension = match extension.starts_with('.') {
true => &extension[1..],
false => extension,
};

match extension {
"anm" => Self::Animation,
"bin" => Self::PropertyBin,
"bnk" => Self::WwiseBank,
"dds" => Self::TextureDds,
"jpg" => Self::Jpeg,
"luaobj" => Self::LuaObj,
"mapgeo" => Self::MapGeometry,
"png" => Self::Png,
"preload" => Self::Preload,
"scb" => Self::StaticMeshBinary,
"sco" => Self::StaticMeshAscii,
"skl" => Self::Skeleton,
"skn" => Self::SimpleSkin,
"stringtable" => Self::RiotStringTable,
"svg" => Self::Svg,
"tex" => Self::Texture,
"wgeo" => Self::WorldGeometry,
"wpk" => Self::WwisePackage,
_ => Self::Unknown,
}
}

/// Identify the type of league file from the magic at the start of the file. You must provide at
/// least [`MAX_MAGIC_SIZE`] bytes of data to be able to detect all file types.
///
/// # Examples
/// ```
/// # use league_toolkit::league_file::*;
/// #
/// let data = b"r3d2skltblahblahblahblah";
/// let kind = LeagueFileKind::identify_from_bytes(data);
/// assert_eq!(kind, LeagueFileKind::Skeleton);
/// ```
///
///
/// ## Identifying from a reader
/// ```
/// # use std::fs::File;
/// # use std::io::{self, Cursor, Read};
/// # use league_toolkit::league_file::*;
/// #
/// let mut reader = Cursor::new([0x33, 0x22, 0x11, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]);
/// let mut buffer = [0; MAX_MAGIC_SIZE];
/// reader.read(&mut buffer)?;
///
/// let kind = LeagueFileKind::identify_from_bytes(&buffer);
/// assert_eq!(kind, LeagueFileKind::SimpleSkin);
/// # Ok::<(), io::Error>(())
/// ```
pub fn identify_from_bytes(data: &[u8]) -> LeagueFileKind {
for magic_byte in LEAGUE_FILE_MAGIC_BYTES.iter() {
if magic_byte.matches(data) {
return magic_byte.kind;
}
}

LeagueFileKind::Unknown
}
}
8 changes: 8 additions & 0 deletions crates/league-toolkit/src/league_file/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Utility module for identifying/working with League of Legends file types.
//!
//! See [`LeagueFileKind`] for more information.
mod kind;
mod pattern;

pub use kind::*;
pub use pattern::MAX_MAGIC_SIZE;
100 changes: 100 additions & 0 deletions crates/league-toolkit/src/league_file/pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use lazy_static::lazy_static;

use super::LeagueFileKind;

pub static LEAGUE_FILE_MAGIC_BYTES: &[LeagueFilePattern] = &[
LeagueFilePattern::from_bytes(crate::core::mesh::MAGIC, LeagueFileKind::StaticMeshBinary),
LeagueFilePattern::from_bytes(b"r3d2sklt", LeagueFileKind::Skeleton),
LeagueFilePattern::from_bytes(b"r3d2ammd", LeagueFileKind::Animation),
LeagueFilePattern::from_bytes(b"r3d2canm", LeagueFileKind::Animation),
LeagueFilePattern::from_fn(
|data| u32::from_le_bytes(data[4..8].try_into().unwrap()) == 1,
8,
LeagueFileKind::WwisePackage,
),
LeagueFilePattern::from_fn(|data| &data[1..4] == b"PNG", 4, LeagueFileKind::Png),
LeagueFilePattern::from_bytes(b"DDS ", LeagueFileKind::TextureDds),
LeagueFilePattern::from_bytes(&[0x33, 0x22, 0x11, 0x00], LeagueFileKind::SimpleSkin),
LeagueFilePattern::from_bytes(b"PROP", LeagueFileKind::PropertyBin),
LeagueFilePattern::from_bytes(b"BKHD", LeagueFileKind::WwiseBank),
LeagueFilePattern::from_bytes(b"WGEO", LeagueFileKind::WorldGeometry),
LeagueFilePattern::from_bytes(b"OEGM", LeagueFileKind::MapGeometry),
LeagueFilePattern::from_bytes(b"[Obj", LeagueFileKind::StaticMeshAscii),
LeagueFilePattern::from_fn(|data| &data[1..5] == b"LuaQ", 5, LeagueFileKind::LuaObj),
LeagueFilePattern::from_bytes(b"PreLoad", LeagueFileKind::Preload),
LeagueFilePattern::from_fn(
|data| u32::from_le_bytes(data[..4].try_into().unwrap()) == 3,
4,
LeagueFileKind::LightGrid,
),
LeagueFilePattern::from_bytes(b"RST", LeagueFileKind::RiotStringTable),
LeagueFilePattern::from_bytes(b"PTCH", LeagueFileKind::PropertyBinOverride),
LeagueFilePattern::from_fn(
|data| ((u32::from_le_bytes(data[..4].try_into().unwrap()) & 0x00FFFFFF) == 0x00FFD8FF),
3,
LeagueFileKind::Jpeg,
),
LeagueFilePattern::from_fn(
|data| u32::from_le_bytes(data[4..8].try_into().unwrap()) == 0x22FD4FC3,
8,
LeagueFileKind::Skeleton,
),
LeagueFilePattern::from_bytes(b"TEX\0", LeagueFileKind::Texture),
LeagueFilePattern::from_bytes(b"<svg", LeagueFileKind::Svg),
];

/// The length of the largest possible file type magic, in bytes.
pub const MAX_MAGIC_SIZE: usize = 8;

pub enum LeagueFilePatternKind {
Bytes(&'static [u8]),
Fn(fn(&[u8]) -> bool),
}

pub struct LeagueFilePattern {
pub pattern: LeagueFilePatternKind,
pub min_length: usize,
pub kind: LeagueFileKind,
}

impl LeagueFilePattern {
const fn from_bytes(bytes: &'static [u8], kind: LeagueFileKind) -> Self {
Self {
pattern: LeagueFilePatternKind::Bytes(bytes),
min_length: bytes.len(),
kind,
}
}

const fn from_fn(f: fn(&[u8]) -> bool, min_length: usize, kind: LeagueFileKind) -> Self {
Self {
pattern: LeagueFilePatternKind::Fn(f),
min_length,
kind,
}
}

pub fn matches(&self, data: &[u8]) -> bool {
data.len() >= self.min_length
&& match self.pattern {
LeagueFilePatternKind::Bytes(bytes) => &data[..bytes.len()] == bytes,
LeagueFilePatternKind::Fn(f) => f(data),
}
}
}

mod tests {
use super::*;

#[test]
fn validate_max_magic_size() {
assert_eq!(
MAX_MAGIC_SIZE,
LEAGUE_FILE_MAGIC_BYTES
.iter()
.map(|p| p.min_length)
.max()
.unwrap()
);
}
}
1 change: 1 addition & 0 deletions crates/league-toolkit/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod core;
pub mod league_file;
pub mod util;
Loading