Fix wrong serde serialize for psb file

This commit is contained in:
2025-07-01 14:01:40 +08:00
parent cbfbf622cf
commit 6d76d8454d
5 changed files with 680 additions and 189 deletions

View File

@@ -1,27 +1,13 @@
use crate::ext::io::*;
use crate::ext::psb::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::encoding::{decode_to_string, encode_string};
use anyhow::Result;
use emote_psb::types::PsbValue;
use emote_psb::types::collection::PsbObject;
use emote_psb::{PsbReader, PsbWriter, VirtualPsb};
use json::JsonValue;
use serde::{Deserialize, Serialize};
use emote_psb::{PsbReader, PsbWriter};
use std::collections::HashSet;
use std::io::{Read, Seek, Write};
use std::io::{Read, Seek};
use std::path::Path;
trait JsonExt {
fn is_valid_str(&self) -> bool;
}
impl JsonExt for JsonValue {
fn is_valid_str(&self) -> bool {
self.is_string() || self.is_null()
}
}
#[derive(Debug)]
pub struct ScnScriptBuilder {}
@@ -111,7 +97,7 @@ impl ScriptBuilder for ScnScriptBuilder {
#[derive(Debug)]
pub struct ScnScript {
psb: VirtualPsb,
psb: VirtualPsbFixed,
language_index: usize,
export_comumode: bool,
filename: String,
@@ -125,7 +111,7 @@ impl ScnScript {
.load()
.map_err(|e| anyhow::anyhow!("Failed to load PSB from {}: {:?}", filename, e))?;
Ok(Self {
psb,
psb: psb.to_psb_fixed(),
language_index: config.kirikiri_language_index.unwrap_or(0),
export_comumode: config.kirikiri_export_comumode,
filename: filename.to_string(),
@@ -133,40 +119,6 @@ impl ScnScript {
}
}
#[derive(Debug, Serialize)]
pub struct PsbDataRef<'a> {
pub version: u16,
pub encryption: u16,
pub root: &'a emote_psb::types::collection::PsbObject,
}
impl<'a> PsbDataRef<'a> {
pub fn new(psb: &'a VirtualPsb) -> Self {
let header = psb.header();
Self {
version: header.version,
encryption: header.encryption,
root: psb.root(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct PsbData {
pub version: u16,
pub encryption: u16,
pub root: emote_psb::types::collection::PsbObject,
}
impl PsbData {
pub fn header(&self) -> emote_psb::header::PsbHeader {
emote_psb::header::PsbHeader {
version: self.version,
encryption: self.encryption,
}
}
}
impl Script for ScnScript {
fn default_output_script_type(&self) -> OutputScriptType {
OutputScriptType::Json
@@ -184,10 +136,10 @@ impl Script for ScnScript {
let mut messages = Vec::new();
let root = self.psb.root();
let scenes = root
.get_value("scenes".into())
.get_value("scenes")
.ok_or(anyhow::anyhow!("scenes not found"))?;
let scenes = match scenes {
PsbValue::List(list) => list,
PsbValueFixed::List(list) => list,
_ => return Err(anyhow::anyhow!("scenes is not a list")),
};
let mut comu = if self.export_comumode {
@@ -197,26 +149,25 @@ impl Script for ScnScript {
};
for (i, oscene) in scenes.iter().enumerate() {
let scene = match oscene {
PsbValue::Object(obj) => obj,
PsbValueFixed::Object(obj) => obj,
_ => return Err(anyhow::anyhow!("scene at index {} is not an object", i)),
};
if let Some(PsbValue::List(texts)) = scene.get_value("texts".into()) {
if let Some(PsbValueFixed::List(texts)) = scene.get_value("texts") {
for (j, text) in texts.iter().enumerate() {
if let PsbValue::List(text) = text {
if let PsbValueFixed::List(text) = text {
let values = text.values();
if values.len() <= 1 {
continue; // Skip if there are not enough values
}
let name = &values[0];
let name = match name {
PsbValue::String(s) => Some(s),
PsbValue::Null => None,
PsbValue::None => None,
PsbValueFixed::String(s) => Some(s),
PsbValueFixed::Null => None,
_ => return Err(anyhow::anyhow!("name is not a string or null")),
};
let mut display_name;
let mut message;
if matches!(values[1], PsbValue::List(_)) {
if matches!(values[1], PsbValueFixed::List(_)) {
display_name = None;
message = &values[1];
} else {
@@ -224,9 +175,8 @@ impl Script for ScnScript {
continue; // Skip if there is no message
}
display_name = match &values[1] {
PsbValue::String(s) => Some(s),
PsbValue::Null => None,
PsbValue::None => None,
PsbValueFixed::String(s) => Some(s),
PsbValueFixed::Null => None,
_ => {
return Err(anyhow::anyhow!(
"display name is not a string or null at {i},{j}"
@@ -235,19 +185,18 @@ impl Script for ScnScript {
};
message = &values[2];
}
if matches!(message, PsbValue::List(_)) {
if matches!(message, PsbValueFixed::List(_)) {
let tmp = message;
if let PsbValue::List(list) = tmp {
if let PsbValueFixed::List(list) = tmp {
if list.len() > self.language_index {
if let PsbValue::List(data) =
if let PsbValueFixed::List(data) =
&list.values()[self.language_index]
{
if data.len() >= 2 {
let data = data.values();
display_name = match &data[0] {
PsbValue::String(s) => Some(s),
PsbValue::Null => None,
PsbValue::None => None,
PsbValueFixed::String(s) => Some(s),
PsbValueFixed::Null => None,
_ => {
return Err(anyhow::anyhow!(
"display name is not a string or null at {i},{j}"
@@ -260,7 +209,7 @@ impl Script for ScnScript {
}
}
}
if let PsbValue::String(message) = message {
if let PsbValueFixed::String(message) = message {
match name {
Some(name) => {
let name = match display_name {
@@ -285,19 +234,17 @@ impl Script for ScnScript {
}
}
}
if let Some(PsbValue::List(selects)) = scene.get_value("selects".into()) {
if let Some(PsbValueFixed::List(selects)) = scene.get_value("selects") {
for select in selects.iter() {
if let PsbValue::Object(select) = select {
if let PsbValueFixed::Object(select) = select {
let mut text = None;
if let Some(PsbValue::List(language)) = select.get_value("language".into())
{
if let Some(PsbValueFixed::List(language)) = select.get_value("language") {
if language.len() > self.language_index {
let v = &language.values()[self.language_index];
if let PsbValue::Object(v) = v {
text = match v.get_value("text".into()) {
Some(PsbValue::String(s)) => Some(s),
Some(PsbValue::Null) => None,
Some(PsbValue::None) => None,
if let PsbValueFixed::Object(v) = v {
text = match v.get_value("text") {
Some(PsbValueFixed::String(s)) => Some(s),
Some(PsbValueFixed::Null) => None,
None => None,
_ => {
return Err(anyhow::anyhow!(
@@ -309,10 +256,9 @@ impl Script for ScnScript {
}
}
if text.is_none() {
text = match select.get_value("text".into()) {
Some(PsbValue::String(s)) => Some(s),
Some(PsbValue::Null) => None,
Some(PsbValue::None) => None,
text = match select.get_value("text") {
Some(PsbValueFixed::String(s)) => Some(s),
Some(PsbValueFixed::Null) => None,
None => None,
_ => {
return Err(anyhow::anyhow!(
@@ -372,11 +318,10 @@ impl Script for ScnScript {
) -> Result<()> {
let mut mes = messages.iter();
let mut cur_mes = mes.next();
// We use json library to process the PSB data, because emote-psb does not support update data.
let t = serde_json::to_string(&self.psb.root())?;
let mut root = json::parse(&t)?;
let mut psb = self.psb.clone();
let root = psb.root_mut();
let scenes = &mut root["scenes"];
if !scenes.is_array() {
if !scenes.is_list() {
return Err(anyhow::anyhow!("scenes is not an array"));
}
for (i, scene) in scenes.members_mut().enumerate() {
@@ -384,19 +329,19 @@ impl Script for ScnScript {
return Err(anyhow::anyhow!("scene at {} is not an object", i));
}
for text in scene["texts"].members_mut() {
if text.is_array() {
if text.is_list() {
if text.len() <= 1 {
continue; // Skip if there are not enough values
}
if cur_mes.is_none() {
cur_mes = mes.next();
}
if !text[0].is_valid_str() {
if !text[0].is_string_or_null() {
return Err(anyhow::anyhow!("name is not a string or null"));
}
let has_name = text[0].is_string();
let mut has_display_name;
if text[1].is_array() {
if text[1].is_list() {
if text[1].is_string() {
let m = match cur_mes.take() {
Some(m) => m,
@@ -412,7 +357,7 @@ impl Script for ScnScript {
name = name.replace(key, value);
}
}
text[0] = json::JsonValue::String(name);
text[0].set_string(name);
} else {
return Err(anyhow::anyhow!("Name is missing for message."));
}
@@ -423,13 +368,13 @@ impl Script for ScnScript {
message = message.replace(key, value);
}
}
text[1] = json::JsonValue::String(message.replace("\n", "\\n"));
} else if text[1].is_array() {
text[1].set_string(message.replace("\n", "\\n"));
} else if text[1].is_list() {
if text[1].len() > self.language_index
&& text[1][self.language_index].is_array()
&& text[1][self.language_index].is_list()
&& text[1][self.language_index].len() >= 2
{
if !text[1][self.language_index][0].is_valid_str() {
if !text[1][self.language_index][0].is_string_or_null() {
return Err(anyhow::anyhow!(
"display name is not a string or null"
));
@@ -451,10 +396,9 @@ impl Script for ScnScript {
}
}
if has_display_name {
text[1][self.language_index][0] =
json::JsonValue::String(name);
text[1][self.language_index][0].set_string(name);
} else {
text[0] = json::JsonValue::String(name);
text[0].set_string(name);
}
} else {
return Err(anyhow::anyhow!(
@@ -468,8 +412,8 @@ impl Script for ScnScript {
message = message.replace(key, value);
}
}
text[1][self.language_index][1] =
json::JsonValue::String(message.replace("\n", "\\n"));
text[1][self.language_index][1]
.set_string(message.replace("\n", "\\n"));
}
}
}
@@ -477,7 +421,7 @@ impl Script for ScnScript {
if text.len() <= 2 {
continue; // Skip if there is no message
}
if !text[1].is_valid_str() {
if !text[1].is_string_or_null() {
return Err(anyhow::anyhow!("display name is not a string or null"));
}
has_display_name = text[1].is_string();
@@ -497,9 +441,9 @@ impl Script for ScnScript {
}
}
if has_display_name {
text[1] = json::JsonValue::String(name);
text[1].set_string(name);
} else {
text[0] = json::JsonValue::String(name);
text[0].set_string(name);
}
} else {
return Err(anyhow::anyhow!("Name is missing for message."));
@@ -511,13 +455,13 @@ impl Script for ScnScript {
message = message.replace(key, value);
}
}
text[2] = json::JsonValue::String(message.replace("\n", "\\n"));
} else if text[2].is_array() {
text[2].set_string(message.replace("\n", "\\n"));
} else if text[2].is_list() {
if text[2].len() > self.language_index
&& text[2][self.language_index].is_array()
&& text[2][self.language_index].is_list()
&& text[2][self.language_index].len() >= 2
{
if !text[2][self.language_index][0].is_valid_str() {
if !text[2][self.language_index][0].is_string_or_null() {
return Err(anyhow::anyhow!(
"display name is not a string or null"
));
@@ -539,10 +483,9 @@ impl Script for ScnScript {
}
}
if has_display_name {
text[2][self.language_index][0] =
json::JsonValue::String(name);
text[2][self.language_index][0].set_string(name);
} else {
text[0] = json::JsonValue::String(name);
text[0].set_string(name);
}
} else {
return Err(anyhow::anyhow!(
@@ -556,8 +499,8 @@ impl Script for ScnScript {
message = message.replace(key, value);
}
}
text[2][self.language_index][1] =
json::JsonValue::String(message.replace("\n", "\\n"));
text[2][self.language_index][1]
.set_string(message.replace("\n", "\\n"));
}
}
}
@@ -569,61 +512,11 @@ impl Script for ScnScript {
if cur_mes.is_some() || mes.next().is_some() {
return Err(anyhow::anyhow!("Some messages were not processed."));
}
let s = json::stringify(root);
let obj = serde_json::from_str::<PsbObject>(&s)?;
let oheader = self.psb.header();
let header = emote_psb::header::PsbHeader {
version: oheader.version,
encryption: oheader.encryption,
};
let psb = VirtualPsb::new(header, Vec::new(), Vec::new(), obj);
let psb = psb.to_psb();
let writer = PsbWriter::new(psb, file);
writer
.finish()
.map_err(|e| anyhow::anyhow!("Failed to write PSB: {:?}", e))?;
Ok(())
}
fn custom_output_extension(&self) -> &'static str {
"json"
}
fn custom_export(&self, filename: &Path, encoding: Encoding) -> Result<()> {
if !self.psb.resources().is_empty() {
eprintln!(
"Warning: The PSB contains resources, which may not be fully represented in the JSON output."
);
crate::COUNTER.inc_warning();
}
if !self.psb.extra().is_empty() {
eprintln!(
"Warning: The PSB contains extra data, which may not be fully represented in the JSON output."
);
crate::COUNTER.inc_warning();
}
let psb_data = PsbDataRef::new(&self.psb);
let str = serde_json::to_string_pretty(&psb_data)?;
let s = encode_string(encoding, &str, false)?;
let mut f = std::fs::File::create(filename)?;
f.write_all(&s)?;
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(encoding, &data)?;
let psb_data: PsbData = serde_json::from_str(&s)?;
let psb = VirtualPsb::new(psb_data.header(), Vec::new(), Vec::new(), psb_data.root);
let writer = PsbWriter::new(psb, file);
writer
.finish()
.map_err(|e| anyhow::anyhow!("Failed to write PSB: {:?}", e))?;
writer.finish().map_err(|e| {
anyhow::anyhow!("Failed to write PSB to file {}: {:?}", self.filename, e)
})?;
Ok(())
}
}
@@ -640,16 +533,15 @@ impl ExportComuMes {
}
}
pub fn export(&mut self, value: &PsbValue) {
pub fn export(&mut self, value: &PsbValueFixed) {
match value {
PsbValue::Object(obj) => {
PsbValueFixed::Object(obj) => {
for (k, v) in obj.iter() {
if k == "comumode" {
if let PsbValue::List(list) = v {
if let PsbValueFixed::List(list) = v {
for item in list.iter() {
if let PsbValue::Object(obj) = item {
if let Some(PsbValue::String(s)) = obj.get_value("text".into())
{
if let PsbValueFixed::Object(obj) = item {
if let Some(PsbValueFixed::String(s)) = obj.get_value("text") {
self.messages.insert(s.string().replace("\\n", "\n"));
}
}
@@ -660,15 +552,15 @@ impl ExportComuMes {
}
}
}
PsbValue::List(list) => {
PsbValueFixed::List(list) => {
let list = list.values();
if list.len() > 1 {
if let PsbValue::String(s) = &list[0] {
if let PsbValueFixed::String(s) = &list[0] {
if s.string() == "comumode" {
for i in 1..list.len() {
if let PsbValue::String(s) = &list[i - 1] {
if let PsbValueFixed::String(s) = &list[i - 1] {
if s.string() == "text" {
if let PsbValue::String(text) = &list[i] {
if let PsbValueFixed::String(text) = &list[i] {
self.messages
.insert(text.string().replace("\\n", "\n"));
}