Skip to content

Commit eda3d91

Browse files
committed
code drop
0 parents  commit eda3d91

File tree

15 files changed

+2184
-0
lines changed

15 files changed

+2184
-0
lines changed

Cargo.lock

Lines changed: 715 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[workspace]
2+
resolver = "2"
3+
members = [
4+
"k_archives",
5+
"unarchive",
6+
]
7+
8+
[profile.release]
9+
lto = true

Readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Konami update archive tool
2+
Simple konami update parser and unpacker written in rust.
3+
4+
Supports mar (as well as encrypted files from gitadora updates), qar, bar, d2, cab (as well as the inner arcfile), and lst (info files for gitadora updates) and info (similar file for jubeat).
5+
6+
No support currently for repacking into these archive formats.

k_archives/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "k_archives"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
binread = "2.2.0"
8+
byteorder = "1.4.3"
9+
cab = "0.6.0"
10+
crc-any = "2.4.4"
11+
thiserror = "1.0.31"
12+
rand = "0.8.5"
13+
14+
[dev-dependencies]
15+
indicatif = { version = "0.16.2", features = ["rayon"] }
16+
rayon = "1.5.2"

k_archives/src/bar.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use std::collections::HashMap;
2+
use std::fs::File;
3+
use std::io::{BufRead, BufReader, Cursor, Seek, SeekFrom};
4+
use std::path::PathBuf;
5+
6+
use byteorder::{LittleEndian, ReadBytesExt};
7+
8+
use crate::common::*;
9+
10+
fn read_file_name<T>(rdr: &mut T) -> Result<String, KArchiveError>
11+
where
12+
T: BufRead + Seek,
13+
{
14+
let mut buf = Vec::<u8>::new();
15+
let size = rdr.read_until(0, &mut buf)?;
16+
rdr.seek(SeekFrom::Current(256 - size as i64))?;
17+
Ok(String::from_utf8(
18+
buf.strip_suffix(&[0])
19+
.ok_or(KArchiveError::Other(
20+
"Failed to strip suffix (malformed or incomplete archive)",
21+
))?
22+
.to_vec(),
23+
)?
24+
.trim_start_matches(['.', '\\'])
25+
.replace('\\', "/")
26+
.to_string())
27+
}
28+
29+
pub(crate) fn parse(path: PathBuf) -> Result<KArchive, KArchiveError> {
30+
let buffer = benchmark(&path)?;
31+
let mut file = match &buffer {
32+
Some(buf) => BufReader::new(InternalFile::Buffer(Cursor::new(buf))),
33+
None => BufReader::new(InternalFile::RealFile(File::open(&path)?)),
34+
};
35+
let mut files: HashMap<PathBuf, KFileInfo> = HashMap::new();
36+
// Skip the first 10 bytes
37+
file.seek_relative(10)?;
38+
let file_count = file.read_u16::<LittleEndian>()?;
39+
let parse_result = (0..file_count).try_for_each(|_| {
40+
let name = read_file_name(&mut file)?;
41+
// bar files are weird. in M39A bars, the filename takes 252 bytes rather than 256
42+
// So let's check if we just read one of those
43+
if file.read_i32::<LittleEndian>()? == -1 {
44+
file.seek_relative(-8)?;
45+
} else {
46+
file.seek_relative(-4)?;
47+
}
48+
let magic1 = file.read_i32::<LittleEndian>()?;
49+
let magic2 = file.read_i32::<LittleEndian>()?;
50+
if magic1 != 3 || magic2 != -1 {
51+
return Err(KArchiveError::ParseError(format!(
52+
"magic numbers are wrong: {} {}",
53+
magic1, magic2
54+
)));
55+
}
56+
let size = file.read_u32::<LittleEndian>()? as u64;
57+
file.seek_relative(4)?;
58+
let offset = file.stream_position()?;
59+
file.seek_relative(size as i64)?;
60+
61+
files.insert(
62+
name.into(),
63+
KFileInfo {
64+
size,
65+
offset,
66+
cipher: None,
67+
},
68+
);
69+
Ok(())
70+
});
71+
match parse_result {
72+
Ok(_) => {}
73+
Err(e) => {
74+
eprintln!("k_archives: Error in archive parsing: {}", e);
75+
eprintln!("k_archives: Continuing with {} files parsed", files.len());
76+
}
77+
}
78+
Ok(KArchive::new(path, files, buffer))
79+
}
80+
81+
#[cfg(test)]
82+
mod tests {
83+
use super::*;
84+
use std::io::Cursor;
85+
#[test]
86+
fn test_filename() {
87+
let cursor = Cursor::new(vec![
88+
92, 74, 69, 65, 50, 48, 50, 52, 48, 52, 49, 53, 48, 48, 99, 111, 110, 116, 101, 110,
89+
116, 115, 92, 53, 92, 102, 92, 56, 92, 54, 52, 52, 102, 48, 52, 99, 57, 102, 52, 48,
90+
49, 50, 100, 100, 55, 50, 53, 102, 57, 50, 49, 52, 51, 54, 55, 54, 98, 97, 99, 99, 55,
91+
51, 52, 50, 52, 54, 0, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
92+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
93+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
94+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
95+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
96+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
97+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
98+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
99+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
100+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
101+
254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254,
102+
254, 254, 254, 254, 254, 254,
103+
]);
104+
let mut filename = BufReader::new(cursor);
105+
assert_eq!(
106+
read_file_name(&mut filename).unwrap(),
107+
"JEA2024041500contents/5/f/8/644f04c9f4012dd725f92143676bacc734246"
108+
)
109+
}
110+
}

k_archives/src/cab.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::common::*;
2+
use byteorder::{LittleEndian, ReadBytesExt};
3+
use std::collections::HashMap;
4+
use std::fs::File;
5+
use std::io::{BufRead, BufReader, Cursor, Read, Seek, SeekFrom};
6+
use std::path::PathBuf;
7+
8+
fn read_file_name<T>(rdr: &mut T) -> Result<String, KArchiveError>
9+
where
10+
T: BufRead + Seek,
11+
{
12+
let mut buf = Vec::<u8>::new();
13+
rdr.read_until(0, &mut buf)?;
14+
Ok(String::from_utf8(
15+
buf.strip_suffix(&[0])
16+
.ok_or(KArchiveError::Other(
17+
"Failed to strip suffix (malformed or incomplete archive)",
18+
))?
19+
.to_vec(),
20+
)?)
21+
}
22+
23+
fn read_folder<T>(
24+
rdr: &mut T,
25+
mut full_path: PathBuf,
26+
files: &mut HashMap<PathBuf, KFileInfo>,
27+
) -> Result<(), KArchiveError>
28+
where
29+
T: BufRead + Seek,
30+
{
31+
let action = rdr.read_u8()?;
32+
full_path.push(read_file_name(rdr)?);
33+
let param = rdr.read_i32::<LittleEndian>()?;
34+
match action {
35+
0x00 => {
36+
files.insert(
37+
full_path,
38+
KFileInfo {
39+
size: param as u64,
40+
offset: rdr.stream_position()?,
41+
cipher: None,
42+
},
43+
);
44+
rdr.seek(SeekFrom::Current(param as i64))?;
45+
}
46+
0x01 => {
47+
let mut entries = param;
48+
while entries > 0 {
49+
read_folder(rdr, full_path.clone(), files)?;
50+
entries -= 1;
51+
}
52+
}
53+
_ => unreachable!("Only two types of entries"),
54+
}
55+
Ok(())
56+
}
57+
58+
pub(crate) fn parse(path: PathBuf) -> Result<KArchive, KArchiveError> {
59+
let cab_file = File::open(&path)?;
60+
let mut cabinet = cab::Cabinet::new(cab_file)?;
61+
let arcsize = cabinet
62+
.get_file_entry("arcfile")
63+
.ok_or(KArchiveError::Other("Failed to get arcfile from cab"))?
64+
.uncompressed_size()
65+
.into();
66+
// I've never seen a cab file that didn't just have an arcfile and filelist inside so assume the structure will be like that until proven wrong
67+
let mut arcfile = BufReader::new(cabinet.read_file("arcfile")?);
68+
// Due to bugs with the cab crate, i'm storing the arcfile buffer inside the KArchive struct for this specific format.
69+
// If interfacing with the cab file directly becomes viable, i'll switch away from this method...
70+
let mut buf = Vec::with_capacity(arcsize as usize);
71+
arcfile.read_to_end(&mut buf)?;
72+
let mut cursor = Cursor::new(buf);
73+
let mut files: HashMap<PathBuf, KFileInfo> = HashMap::new();
74+
while cursor.stream_position()? != arcsize {
75+
read_folder(&mut cursor, PathBuf::from(""), &mut files)?;
76+
}
77+
// Leak the buffer to get a static lifetime slice. This is fine because
78+
// it's guaranteed to live until the program is terminated anyways...
79+
let buffer = cursor.into_inner();
80+
Ok(KArchive::new(path, files, Some(buffer)))
81+
}

0 commit comments

Comments
 (0)