@@ -46,7 +46,10 @@ use crate::{
4646 diff:: { calc_diff_ranges, print_diff, process_code} ,
4747 dol:: process_dol,
4848 elf:: { process_elf, write_elf} ,
49- file:: { buf_writer, touch, verify_hash, FileIterator , FileReadInfo } ,
49+ file:: {
50+ buf_copy_with_hash, buf_writer, check_hash_str, touch, verify_hash, FileIterator ,
51+ FileReadInfo ,
52+ } ,
5053 lcf:: { asm_path_for_unit, generate_ldscript, obj_path_for_unit} ,
5154 map:: apply_map_file,
5255 path:: { check_path_buf, native_path} ,
@@ -229,6 +232,10 @@ pub struct ProjectConfig {
229232 /// Optional base path for all object files.
230233 #[ serde( with = "unix_path_serde_option" , default , skip_serializing_if = "is_default" ) ]
231234 pub object_base : Option < Utf8UnixPathBuf > ,
235+ /// Whether to extract objects from a disc image into object base. If false, the files
236+ /// will be used from the disc image directly without extraction.
237+ #[ serde( default = "bool_true" , skip_serializing_if = "is_true" ) ]
238+ pub extract_objects : bool ,
232239}
233240
234241impl Default for ProjectConfig {
@@ -248,6 +255,7 @@ impl Default for ProjectConfig {
248255 fill_gaps : true ,
249256 export_all : true ,
250257 object_base : None ,
258+ extract_objects : true ,
251259 }
252260 }
253261}
@@ -1103,7 +1111,13 @@ fn split(args: SplitArgs) -> Result<()> {
11031111 let mut config_file = open_file ( & args. config , true ) ?;
11041112 serde_yaml:: from_reader ( config_file. as_mut ( ) ) ?
11051113 } ;
1106- let object_base = find_object_base ( & config) ?;
1114+
1115+ let mut object_base = find_object_base ( & config) ?;
1116+ if config. extract_objects && matches ! ( object_base, ObjectBase :: Vfs ( ..) ) {
1117+ // Extract files from the VFS into the object base directory
1118+ let target_dir = extract_objects ( & config, & object_base) ?;
1119+ object_base = ObjectBase :: Extracted ( target_dir) ;
1120+ }
11071121
11081122 for module_config in config. modules . iter_mut ( ) {
11091123 let mut file = object_base. open ( & module_config. object ) ?;
@@ -2001,6 +2015,7 @@ fn apply_add_relocations(obj: &mut ObjInfo, relocations: &[AddRelocationConfig])
20012015pub enum ObjectBase {
20022016 None ,
20032017 Directory ( Utf8NativePathBuf ) ,
2018+ Extracted ( Utf8NativePathBuf ) ,
20042019 Vfs ( Utf8NativePathBuf , Box < dyn Vfs + Send + Sync > ) ,
20052020}
20062021
@@ -2009,6 +2024,7 @@ impl ObjectBase {
20092024 match self {
20102025 ObjectBase :: None => path. with_encoding ( ) ,
20112026 ObjectBase :: Directory ( base) => base. join ( path. with_encoding ( ) ) ,
2027+ ObjectBase :: Extracted ( base) => extracted_path ( base, path) ,
20122028 ObjectBase :: Vfs ( base, _) => Utf8NativePathBuf :: from ( format ! ( "{}:{}" , base, path) ) ,
20132029 }
20142030 }
@@ -2017,12 +2033,22 @@ impl ObjectBase {
20172033 match self {
20182034 ObjectBase :: None => open_file ( & path. with_encoding ( ) , true ) ,
20192035 ObjectBase :: Directory ( base) => open_file ( & base. join ( path. with_encoding ( ) ) , true ) ,
2036+ ObjectBase :: Extracted ( base) => open_file ( & extracted_path ( base, path) , true ) ,
20202037 ObjectBase :: Vfs ( vfs_path, vfs) => {
20212038 open_file_with_fs ( vfs. clone ( ) , & path. with_encoding ( ) , true )
20222039 . with_context ( || format ! ( "Using disc image {}" , vfs_path) )
20232040 }
20242041 }
20252042 }
2043+
2044+ pub fn base_path ( & self ) -> & Utf8NativePath {
2045+ match self {
2046+ ObjectBase :: None => Utf8NativePath :: new ( "" ) ,
2047+ ObjectBase :: Directory ( base) => base,
2048+ ObjectBase :: Extracted ( base) => base,
2049+ ObjectBase :: Vfs ( base, _) => base,
2050+ }
2051+ }
20262052}
20272053
20282054pub fn find_object_base ( config : & ProjectConfig ) -> Result < ObjectBase > {
@@ -2037,7 +2063,6 @@ pub fn find_object_base(config: &ProjectConfig) -> Result<ObjectBase> {
20372063 let format = nodtool:: nod:: Disc :: detect ( file. as_mut ( ) ) ?;
20382064 if let Some ( format) = format {
20392065 file. rewind ( ) ?;
2040- log:: info!( "Using disc image {}" , path) ;
20412066 let fs = open_fs ( file, ArchiveKind :: Disc ( format) ) ?;
20422067 return Ok ( ObjectBase :: Vfs ( path, fs) ) ;
20432068 }
@@ -2047,3 +2072,83 @@ pub fn find_object_base(config: &ProjectConfig) -> Result<ObjectBase> {
20472072 }
20482073 Ok ( ObjectBase :: None )
20492074}
2075+
2076+ /// Extracts object files from the disc image into the object base directory.
2077+ fn extract_objects ( config : & ProjectConfig , object_base : & ObjectBase ) -> Result < Utf8NativePathBuf > {
2078+ let target_dir: Utf8NativePathBuf = match config. object_base . as_ref ( ) {
2079+ Some ( path) => path. with_encoding ( ) ,
2080+ None => bail ! ( "No object base specified" ) ,
2081+ } ;
2082+ let mut object_paths = Vec :: < ( & Utf8UnixPath , Option < & str > , Utf8NativePathBuf ) > :: new ( ) ;
2083+ {
2084+ let target_path = extracted_path ( & target_dir, & config. base . object ) ;
2085+ if !fs:: exists ( & target_path)
2086+ . with_context ( || format ! ( "Failed to check path '{}'" , target_path) ) ?
2087+ {
2088+ object_paths. push ( ( & config. base . object , config. base . hash . as_deref ( ) , target_path) ) ;
2089+ }
2090+ }
2091+ if let Some ( selfile) = & config. selfile {
2092+ let target_path = extracted_path ( & target_dir, selfile) ;
2093+ if !fs:: exists ( & target_path)
2094+ . with_context ( || format ! ( "Failed to check path '{}'" , target_path) ) ?
2095+ {
2096+ object_paths. push ( ( selfile, config. selfile_hash . as_deref ( ) , target_path) ) ;
2097+ }
2098+ }
2099+ for module_config in & config. modules {
2100+ let target_path = extracted_path ( & target_dir, & module_config. object ) ;
2101+ if !fs:: exists ( & target_path)
2102+ . with_context ( || format ! ( "Failed to check path '{}'" , target_path) ) ?
2103+ {
2104+ object_paths. push ( ( & module_config. object , module_config. hash . as_deref ( ) , target_path) ) ;
2105+ }
2106+ }
2107+ if object_paths. is_empty ( ) {
2108+ return Ok ( target_dir) ;
2109+ }
2110+ log:: info!(
2111+ "Extracting {} file{} from {}" ,
2112+ object_paths. len( ) ,
2113+ if object_paths. len( ) == 1 { "" } else { "s" } ,
2114+ object_base. base_path( )
2115+ ) ;
2116+ let start = Instant :: now ( ) ;
2117+ for ( source_path, hash, target_path) in object_paths {
2118+ let mut file = object_base. open ( source_path) ?;
2119+ if let Some ( parent) = target_path. parent ( ) {
2120+ fs:: create_dir_all ( parent)
2121+ . with_context ( || format ! ( "Failed to create directory '{}'" , parent) ) ?;
2122+ }
2123+ let mut out = fs:: File :: create ( & target_path)
2124+ . with_context ( || format ! ( "Failed to create file '{}'" , target_path) ) ?;
2125+ let hash_bytes = buf_copy_with_hash ( & mut file, & mut out)
2126+ . with_context ( || format ! ( "Failed to extract file '{}'" , target_path) ) ?;
2127+ if let Some ( hash) = hash {
2128+ check_hash_str ( hash_bytes, hash) . with_context ( || {
2129+ format ! ( "Source file failed verification: '{}'" , object_base. join( source_path) )
2130+ } ) ?;
2131+ }
2132+ }
2133+ let duration = start. elapsed ( ) ;
2134+ log:: info!( "Extraction completed in {}.{:03}s" , duration. as_secs( ) , duration. subsec_millis( ) ) ;
2135+ Ok ( target_dir)
2136+ }
2137+
2138+ /// Converts VFS paths like `path/to/container.arc:file` to `path/to/container/file`.
2139+ fn extracted_path ( target_dir : & Utf8NativePath , path : & Utf8UnixPath ) -> Utf8NativePathBuf {
2140+ let mut target_path = target_dir. to_owned ( ) ;
2141+ let mut split = path. as_str ( ) . split ( ':' ) . peekable ( ) ;
2142+ while let Some ( path) = split. next ( ) {
2143+ let path = Utf8UnixPath :: new ( path) ;
2144+ if split. peek ( ) . is_some ( ) {
2145+ if let Some ( parent) = path. parent ( ) {
2146+ target_path. push ( parent. with_encoding ( ) ) ;
2147+ }
2148+ target_path. push ( path. file_stem ( ) . unwrap ( ) ) ;
2149+ } else {
2150+ target_path. push ( path. with_encoding ( ) ) ;
2151+ }
2152+ }
2153+ target_path
2154+ }
0 commit comments