use std::fs;
use std::path::Path;
pub fn decode_text_archives(
binary_dir: &Path,
output_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let charmap = chatot::get_default_charmap();
fs::create_dir_all(output_dir)?;
for entry in fs::read_dir(binary_dir)? {
let path = entry?.path();
if !path.is_file() {
continue;
}
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("output");
let json_path = output_dir.join(format!("{stem}.json"));
let mut file = fs::File::open(&path)?;
let archive = chatot::decode_archive(charmap, &mut file, false)?;
let json = serde_json::json!({
"key": archive.key,
"messages": archive.messages.iter().enumerate().map(|(i, msg)| {
serde_json::json!({
"id": format!("msg_{stem}_{i:05}"),
"en_US": msg,
})
}).collect::<Vec<_>>(),
});
fs::write(&json_path, serde_json::to_string_pretty(&json)?)?;
}
Ok(())
}
pub fn encode_text_archives(
source_dir: &Path,
output_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let charmap = chatot::get_default_charmap();
fs::create_dir_all(output_dir)?;
for entry in fs::read_dir(source_dir)? {
let path = entry?.path();
if !path.is_file() {
continue;
}
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if !ext.eq_ignore_ascii_case("json") {
continue;
}
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("output");
let binary_path = output_dir.join(stem);
let src = chatot::TextSource {
txt: Some(vec![path]),
text_dir: None,
};
let dst = chatot::BinarySource {
archive: Some(vec![binary_path]),
archive_dir: None,
};
let settings = chatot::Settings {
json: true,
lang: "en_US".to_string(),
newer_only: false,
msgenc_format: false,
};
chatot::encode::encode_texts(charmap, &src, &dst, &settings)?;
}
Ok(())
}
pub fn read_text_archive_bin(
path: impl AsRef<Path>,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let charmap = chatot::get_default_charmap();
let mut file = std::fs::File::open(path)?;
let archive = chatot::decode_archive(charmap, &mut file, false)?;
Ok(archive.messages)
}
pub fn read_gmm_file_messages(
path: impl AsRef<Path>,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let messages = crate::c_parser::SymbolTable::extract_gmm_messages(&content)?;
Ok(messages)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_read_gmm_file_messages() {
let dir = tempdir().unwrap();
let gmm_path = dir.path().join("test.gmm");
let xml = r#"<?xml version="1.0"?>
<body language="English">
<row id="msg_00000" index="0">
<language name="English">Hello world</language>
</row>
<row id="msg_00001" index="1">
<language name="English">Second message</language>
</row>
</body>"#;
std::fs::write(&gmm_path, xml).unwrap();
let messages = read_gmm_file_messages(&gmm_path).unwrap();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0], "Hello world");
assert_eq!(messages[1], "Second message");
}
#[test]
fn test_read_gmm_file_messages_against_fixture() {
let fixture = Path::new(
"../rotom/tests/fixtures/decomp/pokeheartgold/files/msgdata/msg/msg_0115_D36R0101.gmm",
);
if !fixture.exists() {
eprintln!("Skipping fixture test: {} not found", fixture.display());
return;
}
let messages = read_gmm_file_messages(fixture).unwrap();
assert!(!messages.is_empty(), "expected at least one message");
assert!(messages.len() > 36, "expected at least 37 messages");
assert!(!messages[36].is_empty(), "message 36 should not be empty");
}
#[test]
fn test_decode_text_archives_directory() {
let dir = tempdir().unwrap();
let out = tempdir().unwrap();
let archive_path = dir.path().join("7_000");
let charmap = chatot::get_default_charmap();
let text = "// Key: 0xABCD\nHello\nWorld\n";
let temp = tempfile::NamedTempFile::new().unwrap();
std::fs::write(temp.path(), text).unwrap();
let src = chatot::TextSource {
txt: Some(vec![temp.path().to_path_buf()]),
text_dir: None,
};
let dst = chatot::BinarySource {
archive: Some(vec![archive_path.clone()]),
archive_dir: None,
};
let settings = chatot::Settings {
json: false,
lang: "en_US".to_string(),
newer_only: false,
msgenc_format: false,
};
chatot::encode::encode_texts(&charmap, &src, &dst, &settings).unwrap();
decode_text_archives(dir.path(), out.path()).unwrap();
let json_path = out.path().join("7_000.json");
assert!(json_path.exists(), "decoded JSON should exist");
let content = std::fs::read_to_string(&json_path).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed["key"], 0xABCD);
assert_eq!(parsed["messages"][0]["en_US"], "Hello");
assert_eq!(parsed["messages"][1]["en_US"], "World");
}
#[test]
fn test_encode_text_archives_directory() {
let dir = tempdir().unwrap();
let out = tempdir().unwrap();
let json_content = serde_json::json!({
"key": 0x1234,
"messages": [
{"id": "msg_000", "en_US": "First"},
{"id": "msg_001", "en_US": "Second"},
]
});
let json_path = dir.path().join("7_001.json");
std::fs::write(
&json_path,
serde_json::to_string_pretty(&json_content).unwrap(),
)
.unwrap();
encode_text_archives(dir.path(), out.path()).unwrap();
let binary_path = out.path().join("7_001");
assert!(binary_path.exists(), "encoded binary should exist");
let charmap = chatot::get_default_charmap();
let mut file = std::fs::File::open(&binary_path).unwrap();
let archive = chatot::decode_archive(&charmap, &mut file, false).unwrap();
assert_eq!(archive.key, 0x1234);
assert_eq!(archive.messages, vec!["First", "Second"]);
}
#[test]
fn test_text_archives_round_trip() {
let dir = tempdir().unwrap();
let json_out = tempdir().unwrap();
let bin_out = tempdir().unwrap();
let archive_path = dir.path().join("7_002");
let charmap = chatot::get_default_charmap();
let text = "// Key: 0xCAFE\nMessage one\nMessage two\nMessage three\n";
let temp = tempfile::NamedTempFile::new().unwrap();
std::fs::write(temp.path(), text).unwrap();
let src = chatot::TextSource {
txt: Some(vec![temp.path().to_path_buf()]),
text_dir: None,
};
let dst = chatot::BinarySource {
archive: Some(vec![archive_path]),
archive_dir: None,
};
let settings = chatot::Settings {
json: false,
lang: "en_US".to_string(),
newer_only: false,
msgenc_format: false,
};
chatot::encode::encode_texts(&charmap, &src, &dst, &settings).unwrap();
decode_text_archives(dir.path(), json_out.path()).unwrap();
encode_text_archives(json_out.path(), bin_out.path()).unwrap();
let mut file = std::fs::File::open(bin_out.path().join("7_002")).unwrap();
let archive = chatot::decode_archive(&charmap, &mut file, false).unwrap();
assert_eq!(archive.key, 0xCAFE);
assert_eq!(
archive.messages,
vec!["Message one", "Message two", "Message three"]
);
}
}