1- use crate :: utils:: { is_chunk_path , WadHashtable } ;
1+ use crate :: utils:: { is_hex_chunk_path , truncate_middle , WadHashtable } ;
22use color_eyre:: eyre:: { self , Ok } ;
33use eyre:: Context ;
44use fancy_regex:: Regex ;
@@ -9,13 +9,15 @@ use league_toolkit::{
99use std:: {
1010 collections:: HashMap ,
1111 ffi:: OsStr ,
12- fs:: { self , DirBuilder , File } ,
12+ fs:: { self , File } ,
1313 io:: { self , Read , Seek } ,
1414 path:: { Path , PathBuf } ,
1515} ;
1616use tracing_indicatif:: span_ext:: IndicatifSpanExt ;
1717use tracing_indicatif:: style:: ProgressStyle ;
1818
19+ const MAX_LOG_PATH_LEN : usize = 120 ;
20+
1921pub struct Extractor < ' chunks > {
2022 decoder : & ' chunks mut WadDecoder < ' chunks , & ' chunks File > ,
2123 hashtable : & ' chunks WadHashtable ,
@@ -55,13 +57,6 @@ impl<'chunks> Extractor<'chunks> {
5557 span. pb_set_message ( "Extracting chunks" ) ;
5658 span. pb_set_finish_message ( "Extraction complete" ) ;
5759
58- prepare_extraction_directories_absolute (
59- chunks. iter ( ) ,
60- self . hashtable ,
61- & extract_directory,
62- self . filter_pattern . as_ref ( ) ,
63- ) ?;
64-
6560 extract_wad_chunks (
6661 self . decoder ,
6762 chunks,
@@ -84,44 +79,6 @@ impl<'chunks> Extractor<'chunks> {
8479 }
8580}
8681
87- pub fn prepare_extraction_directories_absolute < ' chunks > (
88- chunks : impl Iterator < Item = ( & ' chunks u64 , & ' chunks WadChunk ) > ,
89- wad_hashtable : & WadHashtable ,
90- extraction_directory : impl AsRef < Path > ,
91- filter_pattern : Option < & Regex > ,
92- ) -> eyre:: Result < ( ) > {
93- // collect all chunk directories
94- let chunk_directories = chunks. filter_map ( |( _, chunk) | {
95- let chunk_path_str = wad_hashtable. resolve_path ( chunk. path_hash ( ) ) ;
96- if let Some ( regex) = filter_pattern {
97- if !regex. is_match ( chunk_path_str. as_ref ( ) ) . unwrap_or ( false ) {
98- return None ;
99- }
100- }
101- Path :: new ( chunk_path_str. as_ref ( ) )
102- . parent ( )
103- . map ( |path| path. to_path_buf ( ) )
104- } ) ;
105-
106- create_extraction_directories ( chunk_directories, extraction_directory) ?;
107-
108- Ok ( ( ) )
109- }
110-
111- fn create_extraction_directories (
112- chunk_directories : impl Iterator < Item = impl AsRef < Path > > ,
113- extraction_directory : impl AsRef < Path > ,
114- ) -> eyre:: Result < ( ) > {
115- // this wont error if the directory already exists since recursive mode is enabled
116- for chunk_directory in chunk_directories {
117- DirBuilder :: new ( )
118- . recursive ( true )
119- . create ( extraction_directory. as_ref ( ) . join ( chunk_directory) ) ?;
120- }
121-
122- Ok ( ( ) )
123- }
124-
12582pub fn extract_wad_chunks < TSource : Read + Seek > (
12683 decoder : & mut WadDecoder < TSource > ,
12784 chunks : & HashMap < u64 , WadChunk > ,
@@ -137,7 +94,8 @@ pub fn extract_wad_chunks<TSource: Read + Seek>(
13794 let chunk_path = Path :: new ( chunk_path_str. as_ref ( ) ) ;
13895
13996 // advance progress for every chunk (including ones we skip)
140- report_progress ( i as f64 / chunks. len ( ) as f64 , chunk_path. to_str ( ) ) ?;
97+ let truncated = truncate_middle ( chunk_path_str. as_ref ( ) , MAX_LOG_PATH_LEN ) ;
98+ report_progress ( i as f64 / chunks. len ( ) as f64 , Some ( truncated. as_str ( ) ) ) ?;
14199
142100 if let Some ( regex) = filter_pattern {
143101 if !regex. is_match ( chunk_path_str. as_ref ( ) ) . unwrap_or ( false ) {
@@ -146,15 +104,15 @@ pub fn extract_wad_chunks<TSource: Read + Seek>(
146104 }
147105 }
148106
149- extract_wad_chunk_absolute ( decoder, chunk, chunk_path, & extract_directory, filter_type) ?;
107+ extract_wad_chunk ( decoder, chunk, chunk_path, & extract_directory, filter_type) ?;
150108
151109 i += 1 ;
152110 }
153111
154112 Ok ( ( ) )
155113}
156114
157- pub fn extract_wad_chunk_absolute < ' wad , TSource : Read + Seek > (
115+ pub fn extract_wad_chunk < ' wad , TSource : Read + Seek > (
158116 decoder : & mut WadDecoder < ' wad , TSource > ,
159117 chunk : & WadChunk ,
160118 chunk_path : impl AsRef < Path > ,
@@ -176,8 +134,12 @@ pub fn extract_wad_chunk_absolute<'wad, TSource: Read + Seek>(
176134 return Ok ( ( ) ) ;
177135 }
178136
179- let chunk_path = resolve_final_chunk_path ( chunk_path, & chunk_data) ;
180- let Err ( error) = fs:: write ( extract_directory. as_ref ( ) . join ( & chunk_path) , & chunk_data) else {
137+ let chunk_path = resolve_final_chunk_path ( & extract_directory, chunk_path, & chunk_data) ;
138+ let full_path = extract_directory. as_ref ( ) . join ( & chunk_path) ;
139+ if let Some ( parent) = full_path. parent ( ) {
140+ fs:: create_dir_all ( parent) ?;
141+ }
142+ let Err ( error) = fs:: write ( & full_path, & chunk_data) else {
181143 return Ok ( ( ) ) ;
182144 } ;
183145
@@ -187,33 +149,46 @@ pub fn extract_wad_chunk_absolute<'wad, TSource: Read + Seek>(
187149 } else {
188150 Err ( error) . wrap_err ( format ! (
189151 "failed to write chunk (chunk_path: {})" ,
190- chunk_path . display( )
152+ truncate_middle ( & full_path . display( ) . to_string ( ) , MAX_LOG_PATH_LEN )
191153 ) )
192154 }
193155}
194156
195- fn resolve_final_chunk_path ( chunk_path : impl AsRef < Path > , chunk_data : & [ u8 ] ) -> PathBuf {
196- let mut chunk_path = chunk_path. as_ref ( ) . to_path_buf ( ) ;
197- if chunk_path. extension ( ) . is_none ( ) && is_chunk_path ( & chunk_path) {
198- // check for known extensions
199- match LeagueFileKind :: identify_from_bytes ( chunk_data) {
200- LeagueFileKind :: Unknown => {
201- tracing:: warn!(
202- "chunk has no known extension, prepending '.' (chunk_path: {})" ,
203- chunk_path. display( )
204- ) ;
205-
206- chunk_path = chunk_path. with_file_name ( OsStr :: new (
207- & ( "." . to_string ( ) + chunk_path. file_name ( ) . unwrap ( ) . to_string_lossy ( ) . as_ref ( ) ) ,
208- ) ) ;
209- }
210- file_kind => {
211- chunk_path. set_extension ( file_kind. extension ( ) . unwrap ( ) ) ;
212- }
213- }
157+ fn resolve_final_chunk_path (
158+ extract_directory : impl AsRef < Path > ,
159+ chunk_path : impl AsRef < Path > ,
160+ chunk_data : & [ u8 ] ,
161+ ) -> PathBuf {
162+ let mut final_path = chunk_path. as_ref ( ) . to_path_buf ( ) ;
163+
164+ // Hashed paths must remain exactly 16 hex characters with no extension
165+ if is_hex_chunk_path ( & final_path) {
166+ return final_path;
167+ }
168+
169+ // - If the original path has no extension, affix .ltk (and real extension if known)
170+ // - OR if the destination path collides with an existing directory, affix .ltk
171+ let has_extension = final_path. extension ( ) . is_some ( ) ;
172+ let collides_with_dir = extract_directory. as_ref ( ) . join ( & final_path) . is_dir ( ) ;
173+ if !has_extension || collides_with_dir {
174+ let original_stem = chunk_path
175+ . as_ref ( )
176+ . file_stem ( )
177+ . unwrap_or_default ( )
178+ . to_string_lossy ( ) ;
179+ let new_name = build_ltk_name ( & original_stem, chunk_data) ;
180+ final_path. set_file_name ( OsStr :: new ( & new_name) ) ;
214181 }
215182
216- chunk_path
183+ final_path
184+ }
185+
186+ fn build_ltk_name ( file_stem : & str , chunk_data : & [ u8 ] ) -> String {
187+ let kind = LeagueFileKind :: identify_from_bytes ( chunk_data) ;
188+ match kind. extension ( ) {
189+ Some ( ext) => format ! ( "{}.ltk.{}" , file_stem, ext) ,
190+ None => format ! ( "{}.ltk" , file_stem) ,
191+ }
217192}
218193
219194fn write_long_filename_chunk (
@@ -223,29 +198,15 @@ fn write_long_filename_chunk(
223198 chunk_data : & [ u8 ] ,
224199) -> eyre:: Result < ( ) > {
225200 let hashed_path = format ! ( "{:016x}" , chunk. path_hash( ) ) ;
201+ let disp = chunk_path. as_ref ( ) . display ( ) . to_string ( ) ;
202+ let truncated = truncate_middle ( & disp, MAX_LOG_PATH_LEN ) ;
226203 tracing:: warn!(
227- "invalid chunk filename, writing as hashed path (chunk_path: {}, hashed_path: {})" ,
228- chunk_path . as_ref ( ) . display ( ) ,
204+ "Long filename detected (chunk_path: {}, hashed_path: {})" ,
205+ truncated ,
229206 & hashed_path
230207 ) ;
231208
232- let file_kind = LeagueFileKind :: identify_from_bytes ( chunk_data) ;
233- let extension = file_kind. extension ( ) ;
234-
235- match file_kind {
236- LeagueFileKind :: Unknown => {
237- fs:: write ( extract_directory. as_ref ( ) . join ( hashed_path) , chunk_data) ?;
238- }
239- _ => {
240- fs:: write (
241- extract_directory
242- . as_ref ( )
243- . join ( format ! ( "{:016x}" , chunk. path_hash( ) ) )
244- . with_extension ( extension. unwrap ( ) ) ,
245- chunk_data,
246- ) ?;
247- }
248- }
209+ fs:: write ( extract_directory. as_ref ( ) . join ( hashed_path) , chunk_data) ?;
249210
250211 Ok ( ( ) )
251212}
0 commit comments