mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-08 22:08:47 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b33dceb61 | |||
| 67ea0ff8e6 | |||
| 552160ec86 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@
|
|||||||
/patched
|
/patched
|
||||||
*.log
|
*.log
|
||||||
.vscode/
|
.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]
|
[package]
|
||||||
name = "msg_tool"
|
name = "msg_tool"
|
||||||
version = "0.2.14"
|
version = "0.2.15"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
repository = "https://github.com/lifegpc/msg-tool"
|
repository = "https://github.com/lifegpc/msg-tool"
|
||||||
description = "A command-line tool for exporting, importing, packing, and unpacking script files."
|
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 }
|
libflac-sys = { version = "0.3", optional = true }
|
||||||
libtlg-rs = { version = "0.2", optional = true, features = ["encode"] }
|
libtlg-rs = { version = "0.2", optional = true, features = ["encode"] }
|
||||||
lz4 = { version = "1.28", optional = true }
|
lz4 = { version = "1.28", optional = true }
|
||||||
markup5ever = { version = "0.35", optional = true }
|
markup5ever = { version = "0.36", optional = true }
|
||||||
markup5ever_rcdom = { version = "0.35", optional = true }
|
markup5ever_rcdom = { version = "0.36", optional = true }
|
||||||
memchr = { version = "2.7", optional = true }
|
memchr = { version = "2.7", optional = true }
|
||||||
mozjpeg = { version = "0.10", optional = true }
|
mozjpeg = { version = "0.10", optional = true }
|
||||||
msg_tool_macro = { version = "0.2.12" }
|
msg_tool_macro = { version = "0.2.12" }
|
||||||
@@ -52,7 +52,7 @@ unicode-segmentation = "1.12"
|
|||||||
url = { version = "2.5", optional = true }
|
url = { version = "2.5", optional = true }
|
||||||
utf16string = "0.2"
|
utf16string = "0.2"
|
||||||
webp = { version = "0.3", default-features = false, optional = true }
|
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}
|
xp3 = { version = "0.3", optional = true}
|
||||||
zstd = { version = "0.13", optional = true }
|
zstd = { version = "0.13", optional = true }
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ impl CrxdImage {
|
|||||||
} else {
|
} else {
|
||||||
let mut nf = std::path::PathBuf::from(filename);
|
let mut nf = std::path::PathBuf::from(filename);
|
||||||
nf.set_file_name(name);
|
nf.set_file_name(name);
|
||||||
|
nf = crate::utils::files::get_ignorecase_path(&nf)?;
|
||||||
let f = std::fs::File::open(nf)?;
|
let f = std::fs::File::open(nf)?;
|
||||||
CrxImage::new(std::io::BufReader::new(f), config)?
|
CrxImage::new(std::io::BufReader::new(f), config)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ impl DpakLoader {
|
|||||||
let dpak = match self.map.get(dpak) {
|
let dpak = match self.map.get(dpak) {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => {
|
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)?;
|
let ndpak = Dpak::new(&path)?;
|
||||||
self.map.insert(dpak.to_string(), ndpak);
|
self.map.insert(dpak.to_string(), ndpak);
|
||||||
self.map.get(dpak).unwrap()
|
self.map.get(dpak).unwrap()
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ impl Pgd3 {
|
|||||||
let path = {
|
let path = {
|
||||||
let mut pb = std::path::PathBuf::from(filename);
|
let mut pb = std::path::PathBuf::from(filename);
|
||||||
pb.set_file_name(&header.base_name);
|
pb.set_file_name(&header.base_name);
|
||||||
pb
|
crate::utils::files::get_ignorecase_path(&pb)?
|
||||||
};
|
};
|
||||||
crate::utils::files::read_file(&path).map_err(|e| {
|
crate::utils::files::read_file(&path).map_err(|e| {
|
||||||
anyhow::anyhow!("Failed to read base image file '{}': {}", path.display(), e)
|
anyhow::anyhow!("Failed to read base image file '{}': {}", path.display(), e)
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ impl SoftpalScript {
|
|||||||
} else {
|
} else {
|
||||||
let mut path = std::path::PathBuf::from(filename);
|
let mut path = std::path::PathBuf::from(filename);
|
||||||
path.set_file_name(name);
|
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))
|
std::fs::read(path).map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", name, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
//! Utilities for File Operations
|
//! Utilities for File Operations
|
||||||
use crate::scripts::{ALL_EXTS, ARCHIVE_EXTS};
|
use crate::scripts::{ALL_EXTS, ARCHIVE_EXTS};
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
use std::path::Component;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// Returns the relative path from `root` to `target`.
|
/// Returns the relative path from `root` to `target`.
|
||||||
@@ -336,3 +340,129 @@ pub fn sanitize_path(path: &str) -> String {
|
|||||||
|
|
||||||
result
|
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