siglus_assets/
thumb_table.rs1use crate::lzss;
21use crate::util::read_i32_le;
22use anyhow::{bail, Result};
23use std::collections::BTreeMap;
24use std::fs;
25use std::path::Path;
26
27#[derive(Debug, Clone)]
28pub struct ThumbTable {
29 header_size: i32,
30 version: i32,
31 map: BTreeMap<String, String>,
32}
33
34impl ThumbTable {
35 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
37 let bytes = fs::read(path)?;
38 Self::from_bytes(&bytes)
39 }
40
41 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
43 let mut off = 0usize;
44 let header_size = read_i32_le(bytes, &mut off)?;
45 let version = read_i32_le(bytes, &mut off)?;
46 let data_cnt = read_i32_le(bytes, &mut off)?;
47 if data_cnt < 0 {
48 bail!("thumb_table: invalid data_cnt={data_cnt}");
49 }
50 if off > bytes.len() {
51 bail!("thumb_table: unexpected EOF in header");
52 }
53
54 let unpack = lzss::lzss_unpack(&bytes[off..])?;
56 let mut uoff = 0usize;
57
58 let mut map: BTreeMap<String, String> = BTreeMap::new();
59 for _ in 0..(data_cnt as usize) {
60 let pct = read_tchar_null(&unpack, &mut uoff)?;
61 let thumb = read_tchar_null(&unpack, &mut uoff)?;
62 let pct = to_lowercase_like_engine(&pct);
63 let thumb = to_lowercase_like_engine(&thumb);
64 map.insert(pct, thumb);
65 }
66
67 Ok(Self {
68 header_size,
69 version,
70 map,
71 })
72 }
73
74 pub fn header_size(&self) -> i32 {
75 self.header_size
76 }
77
78 pub fn version(&self) -> i32 {
79 self.version
80 }
81
82 pub fn map(&self) -> &BTreeMap<String, String> {
83 &self.map
84 }
85
86 pub fn get(&self, pct: &str) -> Option<&String> {
88 let key = to_lowercase_like_engine(pct);
89 self.map.get(&key)
90 }
91
92 pub fn get_by_file_stem(&self, name: &str) -> Option<&String> {
96 let stem = name.rsplit_once('.').map(|(s, _)| s).unwrap_or(name);
97 self.get(stem)
98 }
99}
100
101fn read_tchar_null(buf: &[u8], off: &mut usize) -> Result<String> {
102 let mut u16s: Vec<u16> = Vec::new();
103 loop {
104 if *off + 2 > buf.len() {
105 bail!("thumb_table: unterminated TCHAR string");
106 }
107 let w = u16::from_le_bytes([buf[*off], buf[*off + 1]]);
108 *off += 2;
109 if w == 0 {
110 break;
111 }
112 u16s.push(w);
113 }
114 Ok(String::from_utf16_lossy(&u16s))
115}
116
117fn to_lowercase_like_engine(s: &str) -> String {
121 s.chars().flat_map(|c| c.to_lowercase()).collect()
122}