2024-02-22 06:13:35 +00:00
|
|
|
use std::{
|
|
|
|
env,
|
|
|
|
fs::File,
|
|
|
|
io::{BufReader, BufWriter, Write},
|
|
|
|
mem::size_of,
|
|
|
|
path::Path,
|
|
|
|
};
|
|
|
|
|
|
|
|
use hex::deserialize as deserialize_hex;
|
|
|
|
use serde::Deserialize;
|
2024-10-04 02:57:02 +00:00
|
|
|
use zerocopy::{Immutable, IntoBytes, KnownLayout};
|
2024-02-22 06:13:35 +00:00
|
|
|
|
|
|
|
// Keep in sync with build.rs
|
2024-10-04 02:57:02 +00:00
|
|
|
#[derive(Clone, Debug, IntoBytes, Immutable, KnownLayout)]
|
2024-02-22 06:13:35 +00:00
|
|
|
#[repr(C, align(4))]
|
|
|
|
struct Header {
|
|
|
|
entry_count: u32,
|
|
|
|
entry_size: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep in sync with redump.rs
|
2024-10-04 02:57:02 +00:00
|
|
|
#[derive(Clone, Debug, IntoBytes, Immutable, KnownLayout)]
|
2024-02-22 06:13:35 +00:00
|
|
|
#[repr(C, align(4))]
|
|
|
|
struct GameEntry {
|
|
|
|
crc32: u32,
|
|
|
|
string_table_offset: u32,
|
|
|
|
sectors: u32,
|
|
|
|
md5: [u8; 16],
|
|
|
|
sha1: [u8; 20],
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let output = std::process::Command::new("git")
|
|
|
|
.args(["rev-parse", "HEAD"])
|
|
|
|
.output()
|
|
|
|
.expect("Failed to execute git");
|
|
|
|
let rev = String::from_utf8(output.stdout).expect("Failed to parse git output");
|
|
|
|
println!("cargo:rustc-env=GIT_COMMIT_SHA={rev}");
|
|
|
|
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
|
|
|
|
|
|
|
|
let out_dir = env::var("OUT_DIR").unwrap();
|
|
|
|
let dest_path = Path::new(&out_dir).join("parsed-dats.bin");
|
2024-06-19 03:04:59 +00:00
|
|
|
let out_file = File::create(dest_path).expect("Failed to open out file");
|
|
|
|
let mut out = zstd::Encoder::new(BufWriter::new(out_file), zstd::zstd_safe::max_c_level())
|
|
|
|
.expect("Failed to create zstd encoder");
|
2024-02-22 06:13:35 +00:00
|
|
|
|
|
|
|
// Parse dat files
|
|
|
|
let mut entries = Vec::<(GameEntry, String)>::new();
|
2024-06-19 03:04:59 +00:00
|
|
|
for path in [
|
|
|
|
"assets/gc-non-redump.dat",
|
|
|
|
"assets/gc-npdp.dat",
|
|
|
|
"assets/gc-redump.dat",
|
|
|
|
"assets/wii-redump.dat",
|
|
|
|
] {
|
2024-02-23 02:58:01 +00:00
|
|
|
println!("cargo:rustc-rerun-if-changed={}", path);
|
2024-02-22 06:13:35 +00:00
|
|
|
let file = BufReader::new(File::open(path).expect("Failed to open dat file"));
|
|
|
|
let dat: DatFile = quick_xml::de::from_reader(file).expect("Failed to parse dat file");
|
2024-06-19 03:04:59 +00:00
|
|
|
entries.extend(dat.games.into_iter().filter_map(|game| {
|
|
|
|
if game.roms.len() != 1 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let rom = &game.roms[0];
|
|
|
|
Some((
|
2024-02-22 06:13:35 +00:00
|
|
|
GameEntry {
|
|
|
|
string_table_offset: 0,
|
2024-06-19 03:04:59 +00:00
|
|
|
crc32: u32::from_be_bytes(rom.crc32),
|
|
|
|
md5: rom.md5,
|
|
|
|
sha1: rom.sha1,
|
|
|
|
sectors: rom.size.div_ceil(0x8000) as u32,
|
2024-02-22 06:13:35 +00:00
|
|
|
},
|
|
|
|
game.name,
|
2024-06-19 03:04:59 +00:00
|
|
|
))
|
2024-02-22 06:13:35 +00:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort by CRC32
|
|
|
|
entries.sort_by_key(|(entry, _)| entry.crc32);
|
|
|
|
|
2024-06-19 03:04:59 +00:00
|
|
|
// Calculate total size and store in zstd header
|
|
|
|
let entries_size = entries.len() * size_of::<GameEntry>();
|
|
|
|
let string_table_size = entries.iter().map(|(_, name)| name.len() + 4).sum::<usize>();
|
|
|
|
let total_size = size_of::<Header>() + entries_size + string_table_size;
|
|
|
|
out.set_pledged_src_size(Some(total_size as u64)).unwrap();
|
|
|
|
out.include_contentsize(true).unwrap();
|
|
|
|
|
2024-02-22 06:13:35 +00:00
|
|
|
// Write game entries
|
|
|
|
let header =
|
|
|
|
Header { entry_count: entries.len() as u32, entry_size: size_of::<GameEntry>() as u32 };
|
2024-06-19 03:04:59 +00:00
|
|
|
out.write_all(header.as_bytes()).unwrap();
|
2024-02-22 06:13:35 +00:00
|
|
|
let mut string_table_offset = 0u32;
|
|
|
|
for (entry, name) in &mut entries {
|
|
|
|
entry.string_table_offset = string_table_offset;
|
2024-06-19 03:04:59 +00:00
|
|
|
out.write_all(entry.as_bytes()).unwrap();
|
2024-02-22 06:13:35 +00:00
|
|
|
string_table_offset += name.as_bytes().len() as u32 + 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write string table
|
|
|
|
for (_, name) in &entries {
|
2024-06-19 03:04:59 +00:00
|
|
|
out.write_all(&(name.len() as u32).to_le_bytes()).unwrap();
|
|
|
|
out.write_all(name.as_bytes()).unwrap();
|
2024-02-22 06:13:35 +00:00
|
|
|
}
|
2024-06-19 03:04:59 +00:00
|
|
|
|
|
|
|
// Finalize
|
|
|
|
out.finish()
|
|
|
|
.expect("Failed to finish zstd encoder")
|
|
|
|
.flush()
|
|
|
|
.expect("Failed to flush output file");
|
2024-02-22 06:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
struct DatFile {
|
|
|
|
#[serde(rename = "game")]
|
|
|
|
games: Vec<DatGame>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
struct DatGame {
|
|
|
|
#[serde(rename = "@name")]
|
|
|
|
name: String,
|
2024-06-19 03:04:59 +00:00
|
|
|
// #[serde(rename = "category", default)]
|
|
|
|
// categories: Vec<String>,
|
|
|
|
#[serde(rename = "rom")]
|
|
|
|
roms: Vec<DatGameRom>,
|
2024-02-22 06:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
struct DatGameRom {
|
2024-06-19 03:04:59 +00:00
|
|
|
// #[serde(rename = "@name")]
|
|
|
|
// name: String,
|
2024-02-22 06:13:35 +00:00
|
|
|
#[serde(rename = "@size")]
|
|
|
|
size: u64,
|
|
|
|
#[serde(rename = "@crc", deserialize_with = "deserialize_hex")]
|
|
|
|
crc32: [u8; 4],
|
|
|
|
#[serde(rename = "@md5", deserialize_with = "deserialize_hex")]
|
|
|
|
md5: [u8; 16],
|
|
|
|
#[serde(rename = "@sha1", deserialize_with = "deserialize_hex")]
|
|
|
|
sha1: [u8; 20],
|
|
|
|
}
|