Use custom json serde method to import/export psb

This commit is contained in:
2025-07-04 11:37:41 +08:00
parent f38dce9f3c
commit d4ad05fbf1
4 changed files with 176 additions and 1 deletions

View File

@@ -5,6 +5,8 @@ use emote_psb::types::number::*;
use emote_psb::types::reference::*;
use emote_psb::types::string::*;
use emote_psb::types::*;
#[cfg(feature = "json")]
use json::JsonValue;
use std::cmp::PartialEq;
use std::collections::HashMap;
use std::ops::{Index, IndexMut};
@@ -163,6 +165,89 @@ impl PsbValueFixed {
_ => None,
}
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> Option<JsonValue> {
match self {
PsbValueFixed::Null => Some(JsonValue::Null),
PsbValueFixed::Bool(b) => Some(JsonValue::Boolean(*b)),
PsbValueFixed::Number(n) => match n {
PsbNumber::Integer(i) => Some(JsonValue::Number((*i).into())),
PsbNumber::Float(f) => Some(JsonValue::Number((*f).into())),
PsbNumber::Double(d) => Some(JsonValue::Number((*d).into())),
},
PsbValueFixed::String(s) => Some(JsonValue::String(s.string().to_owned())),
PsbValueFixed::Resource(s) => {
Some(JsonValue::String(format!("resource#{}", s.resource_ref)))
}
PsbValueFixed::ExtraResource(s) => Some(JsonValue::String(format!(
"extra_resource#{}",
s.extra_resource_ref
))),
PsbValueFixed::IntArray(arr) => Some(JsonValue::Array(
arr.iter().map(|n| JsonValue::Number((*n).into())).collect(),
)),
PsbValueFixed::List(l) => Some(l.to_json()),
PsbValueFixed::Object(o) => Some(o.to_json()),
_ => None,
}
}
#[cfg(feature = "json")]
pub fn from_json(obj: &JsonValue) -> Self {
match obj {
JsonValue::Null => PsbValueFixed::Null,
JsonValue::Boolean(b) => PsbValueFixed::Bool(*b),
JsonValue::Number(n) => {
let data: f64 = (*n).into();
if data.fract() == 0.0 {
PsbValueFixed::Number(PsbNumber::Integer(data as i64))
} else {
PsbValueFixed::Number(PsbNumber::Float(data as f32))
}
}
JsonValue::String(s) => {
if s.starts_with("resource#") {
if let Ok(id) = s[9..].parse::<u64>() {
return PsbValueFixed::Resource(PsbResourceRef { resource_ref: id });
}
} else if s.starts_with("extra_resource#") {
if let Ok(id) = s[16..].parse::<u64>() {
return PsbValueFixed::ExtraResource(PsbExtraRef {
extra_resource_ref: id,
});
}
}
PsbValueFixed::String(PsbString::from(s.clone()))
}
JsonValue::Array(arr) => {
let values: Vec<PsbValueFixed> = arr.iter().map(PsbValueFixed::from_json).collect();
PsbValueFixed::List(PsbListFixed { values })
}
JsonValue::Object(obj) => {
let mut values = HashMap::new();
for (key, value) in obj.iter() {
values.insert(key.to_owned(), PsbValueFixed::from_json(value));
}
PsbValueFixed::Object(PsbObjectFixed { values })
}
JsonValue::Short(n) => {
let s = n.as_str();
if s.starts_with("resource#") {
if let Ok(id) = s[9..].parse::<u64>() {
return PsbValueFixed::Resource(PsbResourceRef { resource_ref: id });
}
} else if s.starts_with("extra_resource#") {
if let Ok(id) = s[16..].parse::<u64>() {
return PsbValueFixed::ExtraResource(PsbExtraRef {
extra_resource_ref: id,
});
}
}
PsbValueFixed::String(PsbString::from(s.to_owned()))
}
}
}
}
impl Index<usize> for PsbValueFixed {
@@ -351,6 +436,12 @@ impl PsbListFixed {
pub fn len(&self) -> usize {
self.values.len()
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> JsonValue {
let data: Vec<_> = self.values.iter().filter_map(|v| v.to_json()).collect();
JsonValue::Array(data)
}
}
impl Index<usize> for PsbListFixed {
@@ -484,6 +575,26 @@ impl PsbObjectFixed {
inner: self.values.iter_mut(),
}
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> JsonValue {
let mut obj = json::object::Object::new();
for (key, value) in &self.values {
if let Some(json_value) = value.to_json() {
obj.insert(key, json_value);
}
}
JsonValue::Object(obj)
}
#[cfg(feature = "json")]
pub fn from_json(obj: &JsonValue) -> Self {
let mut values = HashMap::new();
for (key, value) in obj.entries() {
values.insert(key.to_owned(), PsbValueFixed::from_json(value));
}
PsbObjectFixed { values }
}
}
impl<'a> Index<&'a str> for PsbObjectFixed {
@@ -658,6 +769,29 @@ impl VirtualPsbFixed {
let (header, resources, extra, root) = self.unwrap();
VirtualPsb::new(header, resources, extra, root.to_psb())
}
#[cfg(feature = "json")]
pub fn from_json(&mut self, obj: &JsonValue) -> Result<(), anyhow::Error> {
let version = obj["version"]
.as_u16()
.ok_or_else(|| anyhow::anyhow!("Invalid PSB version"))?;
let encryption = obj["encryption"]
.as_u16()
.ok_or_else(|| anyhow::anyhow!("Invalid PSB encryption"))?;
self.header.version = version;
self.header.encryption = encryption;
self.root = PsbObjectFixed::from_json(&obj["data"]);
Ok(())
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> JsonValue {
json::object! {
"version": self.header.version,
"encryption": self.header.encryption,
"data": self.root.to_json(),
}
}
}
pub trait VirtualPsbExt {

View File

@@ -2,6 +2,7 @@ use crate::ext::io::*;
use crate::ext::psb::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::encoding::*;
use anyhow::Result;
use emote_psb::{PsbReader, PsbWriter};
use std::collections::{HashMap, HashSet};
@@ -131,6 +132,10 @@ impl Script for ScnScript {
FormatOptions::None
}
fn is_output_supported(&self, _: OutputScriptType) -> bool {
true
}
fn extract_messages(&self) -> Result<Vec<Message>> {
let mut messages = Vec::new();
let root = self.psb.root();
@@ -566,6 +571,34 @@ impl Script for ScnScript {
})?;
Ok(())
}
fn custom_export(&self, filename: &Path, encoding: Encoding) -> Result<()> {
let s = json::stringify_pretty(self.psb.to_json(), 2);
let mut f = crate::utils::files::write_file(filename)?;
let b = encode_string(encoding, &s, false)?;
f.write_all(&b)?;
Ok(())
}
fn custom_import<'a>(
&'a self,
custom_filename: &'a str,
file: Box<dyn WriteSeek + 'a>,
_encoding: Encoding,
output_encoding: Encoding,
) -> Result<()> {
let data = crate::utils::files::read_file(custom_filename)?;
let s = decode_to_string(output_encoding, &data)?;
let json = json::parse(&s)?;
let mut psb = self.psb.clone();
psb.from_json(&json)?;
let psb = psb.to_psb();
let writer = PsbWriter::new(psb, file);
writer.finish().map_err(|e| {
anyhow::anyhow!("Failed to write PSB to file {}: {:?}", self.filename, e)
})?;
Ok(())
}
}
#[derive(Debug)]