Skip to content

Commit 18bd608

Browse files
committed
Extract files from disc image to object_base by default
When `extract_objects` is enabled, objects will be extracted once from a disc image into `object_base`, and then used directly from `object_base` going forward. This allows users to delete the disc image from their `orig` dir once the initial build completes.
1 parent 1a9736f commit 18bd608

File tree

3 files changed

+159
-34
lines changed

3 files changed

+159
-34
lines changed

src/cmd/dol.rs

+108-3
Original file line numberDiff line numberDiff line change
@@ -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

234241
impl 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])
20012015
pub 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

20282054
pub 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+
}

src/cmd/vfs.rs

+2-26
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
use std::{
2-
fs,
3-
fs::File,
4-
io,
5-
io::{BufRead, Write},
6-
};
1+
use std::{fs, fs::File, io::Write};
72

83
use anyhow::{anyhow, bail, Context};
94
use argp::FromArgs;
105
use size::Size;
116
use typed_path::{Utf8NativePath, Utf8NativePathBuf, Utf8UnixPath};
127

138
use crate::{
14-
util::path::native_path,
9+
util::{file::buf_copy, path::native_path},
1510
vfs::{
1611
decompress_file, detect, open_path, FileFormat, OpenResult, Vfs, VfsFile, VfsFileType,
1712
VfsMetadata,
@@ -261,25 +256,6 @@ fn cp_file(
261256
Ok(())
262257
}
263258

264-
fn buf_copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
265-
where
266-
R: BufRead + ?Sized,
267-
W: Write + ?Sized,
268-
{
269-
let mut copied = 0;
270-
loop {
271-
let buf = reader.fill_buf()?;
272-
let len = buf.len();
273-
if len == 0 {
274-
break;
275-
}
276-
writer.write_all(buf)?;
277-
reader.consume(len);
278-
copied += len as u64;
279-
}
280-
Ok(copied)
281-
}
282-
283259
fn cp_recursive(
284260
fs: &mut dyn Vfs,
285261
path: &Utf8UnixPath,

src/util/file.rs

+49-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{
22
fs,
33
fs::{DirBuilder, File, OpenOptions},
44
io,
5-
io::{BufRead, BufWriter, Read, Seek, SeekFrom},
5+
io::{BufRead, BufWriter, Read, Seek, SeekFrom, Write},
66
};
77

88
use anyhow::{anyhow, Context, Result};
@@ -156,13 +156,16 @@ pub fn decompress_if_needed(buf: &[u8]) -> Result<Bytes> {
156156
}
157157

158158
pub fn verify_hash(buf: &[u8], expected_str: &str) -> Result<()> {
159+
let mut hasher = Sha1::new();
160+
hasher.update(buf);
161+
check_hash_str(hasher.finalize().into(), expected_str)
162+
}
163+
164+
pub fn check_hash_str(hash_bytes: [u8; 20], expected_str: &str) -> Result<()> {
159165
let mut expected_bytes = [0u8; 20];
160166
hex::decode_to_slice(expected_str, &mut expected_bytes)
161167
.with_context(|| format!("Invalid SHA-1 '{expected_str}'"))?;
162-
let mut hasher = Sha1::new();
163-
hasher.update(buf);
164-
let hash_bytes = hasher.finalize();
165-
if hash_bytes.as_ref() == expected_bytes {
168+
if hash_bytes == expected_bytes {
166169
Ok(())
167170
} else {
168171
Err(anyhow!(
@@ -172,3 +175,44 @@ pub fn verify_hash(buf: &[u8], expected_str: &str) -> Result<()> {
172175
))
173176
}
174177
}
178+
179+
/// Copies from a buffered reader to a writer without extra allocations.
180+
pub fn buf_copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
181+
where
182+
R: BufRead + ?Sized,
183+
W: Write + ?Sized,
184+
{
185+
let mut copied = 0;
186+
loop {
187+
let buf = reader.fill_buf()?;
188+
let len = buf.len();
189+
if len == 0 {
190+
break;
191+
}
192+
writer.write_all(buf)?;
193+
reader.consume(len);
194+
copied += len as u64;
195+
}
196+
Ok(copied)
197+
}
198+
199+
/// Copies from a buffered reader to a writer without extra allocations.
200+
/// Generates an SHA-1 hash of the data as it is copied.
201+
pub fn buf_copy_with_hash<R, W>(reader: &mut R, writer: &mut W) -> io::Result<[u8; 20]>
202+
where
203+
R: BufRead + ?Sized,
204+
W: Write + ?Sized,
205+
{
206+
let mut hasher = Sha1::new();
207+
loop {
208+
let buf = reader.fill_buf()?;
209+
let len = buf.len();
210+
if len == 0 {
211+
break;
212+
}
213+
hasher.update(buf);
214+
writer.write_all(buf)?;
215+
reader.consume(len);
216+
}
217+
Ok(hasher.finalize().into())
218+
}

0 commit comments

Comments
 (0)