beri cool
This commit is contained in:
commit
ef0a2ace43
4 changed files with 1397 additions and 0 deletions
1221
Cargo.lock
generated
Normal file
1221
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "metra"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.51", features = ["derive"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_with = "3.15.1"
|
||||
toml = "0.9.8"
|
||||
unreal_asset = "0.1.16"
|
||||
13
README.md
Normal file
13
README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Mercury Translator
|
||||
Inject WTT .toml translations into WACCA
|
||||
|
||||
## Usage
|
||||
```
|
||||
Usage: metra --messagefolder <MESSAGEFOLDER> --translations <TRANSLATIONS>
|
||||
|
||||
Options:
|
||||
-m, --messagefolder <MESSAGEFOLDER> Path to Message directory
|
||||
-t, --translations <TRANSLATIONS> Path to directory containing translation files
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
152
src/main.rs
Normal file
152
src/main.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{self, File},
|
||||
io::{Cursor, Write},
|
||||
process::{ExitCode, exit},
|
||||
};
|
||||
|
||||
use clap::{Parser, command};
|
||||
use unreal_asset::{
|
||||
Asset, cast,
|
||||
engine_version::EngineVersion,
|
||||
exports::{Export, data_table_export::DataTableExport},
|
||||
properties::{Property, PropertyDataTrait},
|
||||
};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_with::{NoneAsEmptyString, serde_as};
|
||||
// use std::collections::HashMap;
|
||||
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to Message directory
|
||||
#[arg(short, long)]
|
||||
messagefolder: String,
|
||||
|
||||
/// Path to directory containing translation files
|
||||
#[arg(short, long)]
|
||||
translations: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct TranslationFile {
|
||||
#[serde(flatten)]
|
||||
pub translations: HashMap<String, TranslationEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(rename_all(deserialize = "PascalCase"))]
|
||||
pub struct TranslationEntry {
|
||||
pub japanese_message: Option<TranslationValue>,
|
||||
#[serde(alias = "EnglishMessageUSA")]
|
||||
pub english_message_usa: Option<TranslationValue>,
|
||||
#[serde(alias = "EnglishMessageSG")]
|
||||
pub english_message_sg: Option<TranslationValue>,
|
||||
#[serde(alias = "TraditionalChineseMessageTW")]
|
||||
pub traditional_chinese_message_tw: Option<TranslationValue>,
|
||||
#[serde(alias = "TraditionalChineseMessageHK")]
|
||||
pub traditional_chinese_message_hk: Option<TranslationValue>,
|
||||
pub simplified_chinese_message: Option<TranslationValue>,
|
||||
pub korean_message: Option<TranslationValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum TranslationValue {
|
||||
String(String),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
fn convert_opt(opt: Option<TranslationValue>) -> Option<String> {
|
||||
let opt = opt.clone();
|
||||
opt.and_then(|v| match v {
|
||||
TranslationValue::String(s) => Some(s),
|
||||
TranslationValue::Bool(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn main() -> Result<ExitCode, String> {
|
||||
let args = Args::parse();
|
||||
let mut translationmap = HashMap::new();
|
||||
let Ok(files) = fs::read_dir(&args.translations) else { println!("{}: Translation folder not found.", &args.translations); exit(2) };
|
||||
|
||||
for file in files {
|
||||
let name = file.unwrap().file_name().into_string().unwrap();
|
||||
if name.ends_with(".toml") {
|
||||
let dat_str = fs::read_to_string(format!("{}/{}",&args.translations,name));
|
||||
let config: TranslationFile = toml::from_str(&dat_str.unwrap()).expect("Bad Translation file");
|
||||
translationmap.insert(name.replace(".toml", ""), config);
|
||||
}
|
||||
}
|
||||
for t in translationmap {
|
||||
let filename = t.0;
|
||||
let Ok(file) = File::open(
|
||||
format!("{}/{}.uasset",args.messagefolder,filename),
|
||||
) else { println!("[{}] .uasset not found.",filename); continue };
|
||||
let Ok(bulk_file) = File::open(
|
||||
format!("{}/{}.uexp",args.messagefolder,filename),
|
||||
) else { println!("[{}] .uexp not found.",filename); continue };
|
||||
let mut asset = Asset::new(file, Some(bulk_file), EngineVersion::VER_UE4_19).unwrap();
|
||||
|
||||
let data_table_export: &mut DataTableExport =
|
||||
cast!(Export, DataTableExport, &mut asset.asset_data.exports[0])
|
||||
.expect("First export is not a DataTableExport");
|
||||
|
||||
let translations = t.1;
|
||||
for entry in &mut data_table_export.table.data {
|
||||
let entry_name = entry.name.get_content();
|
||||
if translations.translations.get(&entry_name).is_none() {
|
||||
continue;
|
||||
}
|
||||
let entryvalues = translations.translations.get(&entry_name).unwrap();
|
||||
for property in &mut entry.value {
|
||||
let property_name = property.get_name().get_content();
|
||||
let tl = match property_name.as_str() {
|
||||
"JapaneseMessage" => convert_opt(entryvalues.japanese_message.clone()),
|
||||
"EnglishMessageUSA" => convert_opt(entryvalues.english_message_usa.clone()),
|
||||
"EnglishMessageSG" => convert_opt(entryvalues.english_message_sg.clone()),
|
||||
"TraditionalChineseMessageTW" => {
|
||||
convert_opt(entryvalues.traditional_chinese_message_tw.clone())
|
||||
}
|
||||
"TraditionalChineseMessageHK" => {
|
||||
convert_opt(entryvalues.traditional_chinese_message_hk.clone())
|
||||
}
|
||||
"SimplifiedChineseMessage" => {
|
||||
convert_opt(entryvalues.simplified_chinese_message.clone())
|
||||
}
|
||||
"KoreanMessage" => convert_opt(entryvalues.korean_message.clone()),
|
||||
_ => None,
|
||||
};
|
||||
if tl.is_some() {
|
||||
let translation = tl.unwrap().replace("\\n", "\\n\\r");
|
||||
println!("[{}] Wrote {} for {}",filename,property_name,entry_name);
|
||||
if let Some(bool_prop) = cast!(Property, StrProperty, property) {
|
||||
bool_prop.value = Some(translation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut modified = Cursor::new(Vec::new());
|
||||
let mut uexp_modified = Cursor::new(Vec::new());
|
||||
asset
|
||||
.write_data(&mut modified, Some(&mut uexp_modified))
|
||||
.unwrap();
|
||||
drop(asset);
|
||||
let modified = modified.into_inner();
|
||||
let uexp_modified = uexp_modified.into_inner();
|
||||
|
||||
let Ok(mut file) = File::create(
|
||||
format!("{}/{}.uasset",args.messagefolder,filename),
|
||||
) else { println!("[{}] failed to write to .uasset.",filename); continue };
|
||||
let Ok(mut bulk_file) = File::create(
|
||||
format!("{}/{}.uexp",args.messagefolder,filename),
|
||||
) else { println!("[{}] failed to write to .uexp.",filename); continue };
|
||||
|
||||
file.write(&modified).unwrap();
|
||||
bulk_file.write(&uexp_modified).unwrap();
|
||||
}
|
||||
|
||||
Ok(ExitCode::from(0))
|
||||
}
|
||||
Loading…
Reference in a new issue