3 Commits

Author SHA1 Message Date
3b33dceb61 release 0.2.15 & Update deps 2025-12-13 10:09:33 +08:00
67ea0ff8e6 Allow to load files without case sensitive 2025-12-12 23:01:09 +08:00
552160ec86 Allow to load file with ignore case 2025-12-12 10:50:21 +08:00
8 changed files with 381 additions and 376 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
/patched
*.log
.vscode/
/vendor

611
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "msg_tool"
version = "0.2.14"
version = "0.2.15"
edition = "2024"
repository = "https://github.com/lifegpc/msg-tool"
description = "A command-line tool for exporting, importing, packing, and unpacking script files."
@@ -30,8 +30,8 @@ lazy_static = "1.5.0"
libflac-sys = { version = "0.3", optional = true }
libtlg-rs = { version = "0.2", optional = true, features = ["encode"] }
lz4 = { version = "1.28", optional = true }
markup5ever = { version = "0.35", optional = true }
markup5ever_rcdom = { version = "0.35", optional = true }
markup5ever = { version = "0.36", optional = true }
markup5ever_rcdom = { version = "0.36", optional = true }
memchr = { version = "2.7", optional = true }
mozjpeg = { version = "0.10", optional = true }
msg_tool_macro = { version = "0.2.12" }
@@ -52,7 +52,7 @@ unicode-segmentation = "1.12"
url = { version = "2.5", optional = true }
utf16string = "0.2"
webp = { version = "0.3", default-features = false, optional = true }
xml5ever = { version = "0.35", optional = true }
xml5ever = { version = "0.36", optional = true }
xp3 = { version = "0.3", optional = true}
zstd = { version = "0.13", optional = true }

View File

@@ -99,6 +99,7 @@ impl CrxdImage {
} else {
let mut nf = std::path::PathBuf::from(filename);
nf.set_file_name(name);
nf = crate::utils::files::get_ignorecase_path(&nf)?;
let f = std::fs::File::open(nf)?;
CrxImage::new(std::io::BufReader::new(f), config)?
}

View File

@@ -189,7 +189,8 @@ impl DpakLoader {
let dpak = match self.map.get(dpak) {
Some(d) => d,
None => {
let path = dir.join(dpak);
let mut path = dir.join(dpak);
path = crate::utils::files::get_ignorecase_path(&path)?;
let ndpak = Dpak::new(&path)?;
self.map.insert(dpak.to_string(), ndpak);
self.map.get(dpak).unwrap()

View File

@@ -90,7 +90,7 @@ impl Pgd3 {
let path = {
let mut pb = std::path::PathBuf::from(filename);
pb.set_file_name(&header.base_name);
pb
crate::utils::files::get_ignorecase_path(&pb)?
};
crate::utils::files::read_file(&path).map_err(|e| {
anyhow::anyhow!("Failed to read base image file '{}': {}", path.display(), e)

View File

@@ -99,6 +99,7 @@ impl SoftpalScript {
} else {
let mut path = std::path::PathBuf::from(filename);
path.set_file_name(name);
path = crate::utils::files::get_ignorecase_path(&path)?;
std::fs::read(path).map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", name, e))
}
}

View File

@@ -1,8 +1,12 @@
//! Utilities for File Operations
use crate::scripts::{ALL_EXTS, ARCHIVE_EXTS};
#[cfg(not(windows))]
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::{Read, Write};
#[cfg(not(windows))]
use std::path::Component;
use std::path::{Path, PathBuf};
/// Returns the relative path from `root` to `target`.
@@ -336,3 +340,129 @@ pub fn sanitize_path(path: &str) -> String {
result
}
pub fn get_ignorecase_path<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
#[cfg(windows)]
return Ok(path.as_ref().to_path_buf());
#[cfg(not(windows))]
{
let path = path.as_ref();
// If the path exists as is, return it
if path.exists() {
return Ok(path.to_path_buf());
}
{
// Helper: try to resolve the remaining tail components starting from base,
// performing case-insensitive matches for each step.
fn resolve_from_base(base: PathBuf, tail: &[OsString]) -> io::Result<Option<PathBuf>> {
let mut cur = base;
for comp in tail {
let direct = cur.join(comp);
if direct.exists() {
cur = direct;
continue;
}
if !cur.is_dir() {
return Ok(None);
}
let mut found = None;
for entry in fs::read_dir(&cur)? {
let entry = entry?;
let name = entry.file_name();
if name
.to_string_lossy()
.eq_ignore_ascii_case(&comp.to_string_lossy())
{
found = Some(cur.join(name));
break;
}
}
match found {
Some(p) => cur = p,
None => return Ok(None),
}
}
Ok(Some(cur))
}
let orig = path;
// If it exists as-is, return immediately
if orig.exists() {
return Ok(orig.to_path_buf());
}
// Collect components as OsString (preserve Prefix/RootDir as components)
let comps: Vec<OsString> = orig
.components()
.map(|c| match c {
Component::Prefix(p) => p.as_os_str().to_os_string(),
Component::RootDir => OsString::from(std::path::MAIN_SEPARATOR.to_string()),
other => other.as_os_str().to_os_string(),
})
.collect();
// Try replacing components from the bottom (leaf) upward.
let len = comps.len();
for idx in (0..len).rev() {
// Build parent path from comps[0..idx]
let mut parent = PathBuf::new();
for j in 0..idx {
parent.push(&comps[j]);
}
if parent.as_os_str().is_empty() {
parent = PathBuf::from(".");
}
// If parent doesn't exist or is not a directory, skip this level
if !parent.exists() || !parent.is_dir() {
continue;
}
// Look for a case-insensitive match for the component at idx inside parent
let target = &comps[idx];
let mut matched_name: Option<OsString> = None;
for entry in fs::read_dir(&parent)? {
let entry = entry?;
let name = entry.file_name();
if name
.to_string_lossy()
.eq_ignore_ascii_case(&target.to_string_lossy())
{
matched_name = Some(name);
break;
}
}
if let Some(name) = matched_name {
// Reconstruct candidate path: parent + matched_name + remaining original tail
let candidate_base = parent.join(name);
let tail: Vec<OsString> = comps.iter().skip(idx + 1).cloned().collect();
if tail.is_empty() {
if candidate_base.exists() {
return Ok(candidate_base);
} else {
// Even if leaf matched, final file may not exist (e.g., different deeper casing),
// attempt to resolve remaining components (none here) so treat as not found.
continue;
}
}
if let Some(resolved) = resolve_from_base(candidate_base, &tail)? {
return Ok(resolved);
}
}
}
Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"Path {} not found (case-insensitive search failed)",
path.display()
),
))
}
}
}