mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-08 05:48:46 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b33dceb61 | |||
| 67ea0ff8e6 | |||
| 552160ec86 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
||||
/patched
|
||||
*.log
|
||||
.vscode/
|
||||
/vendor
|
||||
|
||||
611
Cargo.lock
generated
611
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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 }
|
||||
|
||||
|
||||
@@ -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)?
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user