mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-11 07:28:49 +08:00
Add new script Kirikiri TJS NS0 binary encoded script
This commit is contained in:
@@ -155,11 +155,11 @@ impl StructPack for OpExt {
|
||||
}
|
||||
|
||||
impl StructUnpack for OpExt {
|
||||
fn unpack<R: Read + Seek>(mut reader: R, big: bool, encoding: Encoding) -> Result<Self> {
|
||||
let op = Op::unpack(&mut reader, big, encoding)?;
|
||||
fn unpack<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<Self> {
|
||||
let op = Op::unpack(reader, big, encoding)?;
|
||||
let mut ints = Vec::with_capacity(op.init_count as usize);
|
||||
for _ in 0..op.init_count {
|
||||
let i = u32::unpack(&mut reader, big, encoding)?;
|
||||
let i = u32::unpack(reader, big, encoding)?;
|
||||
ints.push(i);
|
||||
}
|
||||
let mut strs = Vec::with_capacity(op.str_count() as usize);
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod ks;
|
||||
pub mod mdf;
|
||||
pub mod scn;
|
||||
pub mod simple_crypt;
|
||||
pub mod tjs_ns0;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
174
src/scripts/kirikiri/tjs_ns0.rs
Normal file
174
src/scripts/kirikiri/tjs_ns0.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
//! Kirikiri TJS NS0 binary encoded script
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::{decode_to_string, encode_string};
|
||||
use crate::utils::struct_pack::*;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Kirikiri TJS NS0 Script Builder
|
||||
pub struct TjsNs0Builder {}
|
||||
|
||||
impl TjsNs0Builder {
|
||||
/// Creates a new instance of `TjsNs0Builder`
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptBuilder for TjsNs0Builder {
|
||||
fn default_encoding(&self) -> Encoding {
|
||||
Encoding::Utf16LE
|
||||
}
|
||||
|
||||
fn build_script(
|
||||
&self,
|
||||
buf: Vec<u8>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
_archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(TjsNs0::new(buf, filename, encoding, config)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["tjs", "pbd"]
|
||||
}
|
||||
|
||||
fn script_type(&self) -> &'static ScriptType {
|
||||
&ScriptType::KirikiriTjsNs0
|
||||
}
|
||||
|
||||
fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
|
||||
if buf_len >= 12 && buf.starts_with(b"TJS/ns0\0TJS\0") {
|
||||
return Some(100);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum TjsValue {
|
||||
Void(()),
|
||||
Int(i64),
|
||||
Str(String),
|
||||
Array(Vec<TjsValue>),
|
||||
Dict(BTreeMap<String, TjsValue>),
|
||||
}
|
||||
|
||||
fn unpack_string<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<String> {
|
||||
let len = u32::unpack(reader, big, encoding)? as usize;
|
||||
let tlen = if encoding.is_utf16le() { len * 2 } else { len };
|
||||
let mut buf = vec![0u8; tlen];
|
||||
reader.read_exact(&mut buf)?;
|
||||
let s = decode_to_string(encoding, &buf, true)?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
impl StructUnpack for TjsValue {
|
||||
fn unpack<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<Self> {
|
||||
let typ = u16::unpack(reader, big, encoding)?;
|
||||
let typ_byte = (typ & 0xff) as u8;
|
||||
Ok(match typ_byte {
|
||||
0 => TjsValue::Void(()),
|
||||
2 => TjsValue::Str(unpack_string(reader, big, encoding)?),
|
||||
4 => TjsValue::Int(i64::unpack(reader, big, encoding)?),
|
||||
0x81 => {
|
||||
let arr_len = u32::unpack(reader, big, encoding)? as usize;
|
||||
let mut arr = Vec::with_capacity(arr_len);
|
||||
for _ in 0..arr_len {
|
||||
arr.push(reader.read_struct::<TjsValue>(big, encoding)?);
|
||||
}
|
||||
TjsValue::Array(arr)
|
||||
}
|
||||
0xC1 => {
|
||||
let kv_len = u32::unpack(reader, big, encoding)? as usize;
|
||||
let mut dict = BTreeMap::new();
|
||||
for _ in 0..kv_len {
|
||||
let key = unpack_string(reader, big, encoding)?;
|
||||
let value = reader.read_struct::<TjsValue>(big, encoding)?;
|
||||
dict.insert(key, value);
|
||||
}
|
||||
TjsValue::Dict(dict)
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unsupported TJS/ns0 value type: {} at pos {}",
|
||||
typ_byte,
|
||||
reader.stream_position()? - 2
|
||||
));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Kirikiri TJS NS0 Script
|
||||
pub struct TjsNs0 {
|
||||
data: TjsValue,
|
||||
custom_yaml: bool,
|
||||
}
|
||||
|
||||
impl TjsNs0 {
|
||||
/// Creates a new `TjsNs0` script from the given buffer and filename
|
||||
///
|
||||
/// * `buf` - The buffer containing the TJS/ns0 data
|
||||
/// * `filename` - The name of the file
|
||||
/// * `encoding` - The encoding to use for strings
|
||||
/// * `config` - Extra configuration options
|
||||
pub fn new(
|
||||
buf: Vec<u8>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Self> {
|
||||
let mut reader = MemReader::new(buf);
|
||||
let mut header = [0u8; 16];
|
||||
reader.read_exact(&mut header)?;
|
||||
if &header != b"TJS/ns0\0TJS\0\0\0\0\0" {
|
||||
return Err(anyhow::anyhow!("Invalid TJS/ns0 header: {:?}", &header));
|
||||
}
|
||||
let data = TjsValue::unpack(&mut reader, false, encoding)?;
|
||||
Ok(Self {
|
||||
data,
|
||||
custom_yaml: config.custom_yaml,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Script for TjsNs0 {
|
||||
fn default_output_script_type(&self) -> OutputScriptType {
|
||||
OutputScriptType::Custom
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions {
|
||||
FormatOptions::None
|
||||
}
|
||||
|
||||
fn is_output_supported(&self, output: OutputScriptType) -> bool {
|
||||
matches!(output, OutputScriptType::Custom)
|
||||
}
|
||||
|
||||
fn custom_output_extension<'a>(&'a self) -> &'a str {
|
||||
if self.custom_yaml { "yaml" } else { "json" }
|
||||
}
|
||||
|
||||
fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
|
||||
let s = if self.custom_yaml {
|
||||
serde_yaml_ng::to_string(&self.data)?
|
||||
} else {
|
||||
serde_json::to_string_pretty(&self.data)?
|
||||
};
|
||||
let s = encode_string(encoding, &s, false)?;
|
||||
let mut writer = crate::utils::files::write_file(filename)?;
|
||||
writer.write_all(&s)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,8 @@ lazy_static::lazy_static! {
|
||||
Box::new(softpal::scr::SoftpalScriptBuilder::new()),
|
||||
#[cfg(feature = "artemis-panmimisoft")]
|
||||
Box::new(artemis::panmimisoft::txt::TxtBuilder::new()),
|
||||
#[cfg(feature = "kirikiri")]
|
||||
Box::new(kirikiri::tjs_ns0::TjsNs0Builder::new()),
|
||||
];
|
||||
/// A list of all script extensions.
|
||||
pub static ref ALL_EXTS: Vec<String> =
|
||||
|
||||
16
src/types.rs
16
src/types.rs
@@ -15,6 +15,8 @@ pub enum Encoding {
|
||||
Cp932,
|
||||
/// GB2312 encoding
|
||||
Gb2312,
|
||||
/// UTF-16 Little Endian encoding
|
||||
Utf16LE,
|
||||
/// Code page encoding (Windows only)
|
||||
#[cfg(windows)]
|
||||
CodePage(u32),
|
||||
@@ -37,6 +39,16 @@ impl Encoding {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the encoding is UTF-16LE.
|
||||
pub fn is_utf16le(&self) -> bool {
|
||||
match self {
|
||||
Self::Utf16LE => true,
|
||||
#[cfg(windows)]
|
||||
Self::CodePage(code_page) => *code_page == 1200,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the encoding is UTF8.
|
||||
pub fn is_utf8(&self) -> bool {
|
||||
match self {
|
||||
@@ -505,6 +517,10 @@ pub enum ScriptType {
|
||||
#[value(alias("kr-mdf"))]
|
||||
/// Kirikiri MDF (zlib compressed) file
|
||||
KirikiriMdf,
|
||||
#[cfg(feature = "kirikiri")]
|
||||
#[value(alias("kr-tjs-ns0"))]
|
||||
/// Kirikiri TJS NS0 binary encoded script
|
||||
KirikiriTjsNs0,
|
||||
#[cfg(feature = "softpal")]
|
||||
/// Softpal src script
|
||||
Softpal,
|
||||
|
||||
@@ -144,6 +144,16 @@ pub fn decode_to_string(
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
Encoding::Utf16LE => Ok(encoding::codec::utf_16::UTF_16LE_ENCODING
|
||||
.decode(
|
||||
data,
|
||||
if check {
|
||||
DecoderTrap::Strict
|
||||
} else {
|
||||
DecoderTrap::Replace
|
||||
},
|
||||
)
|
||||
.map_err(|_| anyhow::anyhow!("Failed to decode UTF-16LE"))?),
|
||||
#[cfg(windows)]
|
||||
Encoding::CodePage(code_page) => Ok(super::encoding_win::decode_to_string(
|
||||
code_page, data, check,
|
||||
@@ -246,6 +256,10 @@ pub fn encode_string(
|
||||
});
|
||||
Ok(result)
|
||||
}
|
||||
Encoding::Utf16LE => {
|
||||
let re = utf16string::WString::<utf16string::LE>::from(data);
|
||||
Ok(re.as_bytes().to_vec())
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Encoding::CodePage(code_page) => {
|
||||
Ok(super::encoding_win::encode_string(code_page, data, check)?)
|
||||
|
||||
@@ -11,7 +11,7 @@ pub trait StructUnpack: Sized {
|
||||
/// * `reader` - The reader to read the binary data from.
|
||||
/// * `big` - Whether the data is in big-endian format.
|
||||
/// * `encoding` - The encoding to use for string fields.
|
||||
fn unpack<R: Read + Seek>(reader: R, big: bool, encoding: Encoding) -> Result<Self>;
|
||||
fn unpack<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<Self>;
|
||||
}
|
||||
|
||||
/// Trait for packing a struct into a binary stream.
|
||||
@@ -47,7 +47,7 @@ struct_unpack_impl_for_num!(f32);
|
||||
struct_unpack_impl_for_num!(f64);
|
||||
|
||||
impl StructUnpack for bool {
|
||||
fn unpack<R: Read + Seek>(mut reader: R, _big: bool, _encoding: Encoding) -> Result<Self> {
|
||||
fn unpack<R: Read + Seek>(reader: &mut R, _big: bool, _encoding: Encoding) -> Result<Self> {
|
||||
let mut buf = [0u8; 1];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(buf[0] != 0)
|
||||
@@ -71,7 +71,7 @@ impl<T: StructPack> StructPack for Option<T> {
|
||||
}
|
||||
|
||||
impl<T: StructUnpack> StructUnpack for Option<T> {
|
||||
fn unpack<R: Read + Seek>(reader: R, big: bool, encoding: Encoding) -> Result<Self> {
|
||||
fn unpack<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<Self> {
|
||||
let value = T::unpack(reader, big, encoding)?;
|
||||
Ok(Some(value))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user