Add create support for DSC file

This commit is contained in:
2025-07-04 13:55:56 +08:00
parent 5136927a97
commit 4141f361e3
4 changed files with 289 additions and 4 deletions

View File

@@ -29,7 +29,7 @@ utf16string = "0.2"
[features]
default = ["bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-system-img", "circus", "escude", "escude-arc", "kirikiri", "kirikiri-img", "yaneurao", "yaneurao-itufuru"]
bgi = []
bgi-arc = ["bgi", "utils-bit-stream"]
bgi-arc = ["bgi", "rand", "utils-bit-stream"]
bgi-img = ["bgi", "image", "utils-bit-stream"]
cat-system = []
cat-system-arc = ["cat-system", "blowfish", "utils-crc32"]

View File

@@ -24,3 +24,21 @@ impl<T: Copy> VecExt<T> for Vec<T> {
}
}
}
pub trait SliceExt<T> {
fn rfind(&self, pattern: &[T]) -> Option<usize>;
}
impl<T: PartialEq> SliceExt<T> for [T] {
fn rfind(&self, pattern: &[T]) -> Option<usize> {
if pattern.is_empty() || self.len() < pattern.len() {
return None;
}
for i in (0..=self.len() - pattern.len()).rev() {
if &self[i..i + pattern.len()] == pattern {
return Some(i);
}
}
None
}
}

View File

@@ -4,7 +4,9 @@ use crate::scripts::base::*;
use crate::types::*;
use crate::utils::bit_stream::*;
use anyhow::Result;
use std::io::Write;
use rand::Rng;
use std::collections::BinaryHeap;
use std::io::{Seek, Write};
#[derive(Debug)]
struct HuffmanCode {
@@ -208,6 +210,240 @@ impl<'a> DscDecoder<'a> {
}
}
#[derive(Debug, Clone, Copy)]
enum LzssOp {
Literal(u8),
Match { len: u16, offset: u16 },
}
#[derive(Debug)]
struct FreqNode {
freq: u32,
symbol: Option<u16>,
left: Option<Box<FreqNode>>,
right: Option<Box<FreqNode>>,
}
impl PartialEq for FreqNode {
fn eq(&self, other: &Self) -> bool {
self.freq == other.freq
}
}
impl Eq for FreqNode {}
impl PartialOrd for FreqNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FreqNode {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.freq.cmp(&self.freq)
}
}
fn calculate_huffman_depths(freqs: &[u32]) -> Vec<u8> {
let mut heap = BinaryHeap::new();
for (symbol, &freq) in freqs.iter().enumerate() {
if freq > 0 {
heap.push(FreqNode {
freq,
symbol: Some(symbol as u16),
left: None,
right: None,
});
}
}
if heap.len() <= 1 {
let mut depths = vec![0; 512];
if let Some(node) = heap.pop() {
depths[node.symbol.unwrap() as usize] = 1;
}
return depths;
}
while heap.len() > 1 {
let node1 = heap.pop().unwrap();
let node2 = heap.pop().unwrap();
let new_node = FreqNode {
freq: node1.freq + node2.freq,
symbol: None,
left: Some(Box::new(node1)),
right: Some(Box::new(node2)),
};
heap.push(new_node);
}
let mut depths = vec![0; 512];
if let Some(root) = heap.pop() {
fn traverse(node: &FreqNode, depth: u8, depths: &mut [u8]) {
if let Some(symbol) = node.symbol {
if depth == 0 {
depths[symbol as usize] = 1;
} else {
depths[symbol as usize] = depth;
}
} else {
if let Some(ref left) = node.left {
traverse(left, depth + 1, depths);
}
if let Some(ref right) = node.right {
traverse(right, depth + 1, depths);
}
}
}
traverse(&root, 0, &mut depths);
}
depths
}
fn generate_canonical_codes(depths: &[u8]) -> Vec<Option<(u16, u8)>> {
let mut codes_with_depths = vec![];
for (symbol, &depth) in depths.iter().enumerate() {
if depth > 0 {
codes_with_depths.push((symbol as u16, depth));
}
}
codes_with_depths.sort_by(|a, b| {
let depth_cmp = a.1.cmp(&b.1);
if depth_cmp == std::cmp::Ordering::Equal {
a.0.cmp(&b.0)
} else {
depth_cmp
}
});
let mut huffman_codes = vec![None; 512];
let mut current_code = 0u16;
let mut last_depth = 0u8;
for &(symbol, depth) in &codes_with_depths {
if last_depth != 0 {
current_code <<= depth - last_depth;
}
huffman_codes[symbol as usize] = Some((current_code, depth));
current_code += 1;
last_depth = depth;
}
huffman_codes
}
pub struct DscEncoder<'a, T: Write + Seek> {
stream: MsbBitWriter<'a, T>,
magic: u32,
key: u32,
dec_count: u32,
}
impl<'a, T: Write + Seek> DscEncoder<'a, T> {
pub fn new(writer: &'a mut T) -> Self {
let stream = MsbBitWriter::new(writer);
DscEncoder {
stream,
magic: 0x5344 << 16, // "DS"
key: rand::rng().random(),
dec_count: 0,
}
}
pub fn pack(mut self, data: &[u8]) -> Result<()> {
// LZSS compression
let mut ops = vec![];
let mut pos = 0;
while pos < data.len() {
let mut best_len = 0;
let mut best_offset = 0;
let max_len = (data.len() - pos).min(257);
if max_len >= 2 {
let search_start = if pos > 4097 { pos - 4097 } else { 0 };
let lookbehind = &data[search_start..pos];
for len in (2..=max_len).rev() {
if let Some(found_idx) = lookbehind.rfind(&data[pos..pos + len]) {
let offset = lookbehind.len() - found_idx;
if offset >= 2 {
best_len = len;
best_offset = offset;
break;
}
}
}
}
if best_len >= 2 {
ops.push(LzssOp::Match {
len: best_len as u16,
offset: best_offset as u16,
});
pos += best_len;
} else {
ops.push(LzssOp::Literal(data[pos]));
pos += 1;
}
}
let symbols: Vec<u16> = ops
.iter()
.map(|op| match op {
LzssOp::Literal(byte) => *byte as u16,
LzssOp::Match { len, .. } => 256 + (len - 2),
})
.collect();
self.dec_count = symbols.len() as u32;
let mut freqs = vec![0u32; 512];
for &s in &symbols {
freqs[s as usize] += 1;
}
let depths = calculate_huffman_depths(&freqs);
let huffman_codes = generate_canonical_codes(&depths);
self.stream.writer.write_all(b"DSC FORMAT 1.00\0")?;
self.stream.writer.seek(std::io::SeekFrom::Start(0x10))?;
self.stream.writer.write_u32(self.key)?;
self.stream.writer.write_u32(data.len() as u32)?;
self.stream.writer.write_u32(self.dec_count)?;
self.stream.writer.seek(std::io::SeekFrom::Start(0x20))?;
for depth in depths.iter() {
let key = self.update_key();
self.stream.writer.write_u8(depth.overflowing_add(key).0)?;
}
for op in &ops {
match op {
LzssOp::Literal(byte) => {
let symbol = *byte as u16;
let (code, len) = huffman_codes[symbol as usize].unwrap();
self.stream.put_bits(code as u32, len)?;
}
LzssOp::Match { len, offset } => {
let symbol = 256 + (len - 2);
let (code, huff_len) = huffman_codes[symbol as usize].unwrap();
self.stream.put_bits(code as u32, huff_len)?;
self.stream.put_bits((*offset - 2) as u32, 12)?;
}
}
}
self.stream.flush()?;
Ok(())
}
fn update_key(&mut self) -> u8 {
let v0 = 20021 * (self.key & 0xffff);
let mut v1 = self.magic | (self.key >> 16);
v1 = v1
.overflowing_mul(20021)
.0
.overflowing_add(self.key.overflowing_mul(346).0)
.0;
v1 = (v1 + (v0 >> 16)) & 0xffff;
self.key = (v1 << 16) + (v0 & 0xffff) + 1;
v1 as u8
}
}
#[derive(Debug)]
pub struct DscBuilder {}
@@ -251,6 +487,23 @@ impl ScriptBuilder for DscBuilder {
}
None
}
fn can_create_file(&self) -> bool {
true
}
fn create_file<'a>(
&'a self,
filename: &'a str,
mut writer: Box<dyn WriteSeek + 'a>,
_encoding: Encoding,
_file_encoding: Encoding,
) -> Result<()> {
let encoder = DscEncoder::new(&mut writer);
let data = crate::utils::files::read_file(filename)?;
encoder.pack(&data)?;
Ok(())
}
}
#[derive(Debug)]
@@ -291,4 +544,17 @@ impl Script for Dsc {
f.write_all(&self.data)?;
Ok(())
}
fn custom_import<'a>(
&'a self,
custom_filename: &'a str,
mut file: Box<dyn WriteSeek + 'a>,
_encoding: Encoding,
_output_encoding: Encoding,
) -> Result<()> {
let encoder = DscEncoder::new(&mut file);
let data = crate::utils::files::read_file(custom_filename)?;
encoder.pack(&data)?;
Ok(())
}
}

View File

@@ -42,7 +42,7 @@ impl<T: Read> MsbBitStream<T> {
}
pub struct MsbBitWriter<'a, T: Write> {
writer: &'a mut T,
pub writer: &'a mut T,
buffer: u32,
buffer_size: u32,
}
@@ -58,7 +58,8 @@ impl<'a, T: Write> MsbBitWriter<'a, T> {
pub fn flush(&mut self) -> Result<()> {
if self.buffer_size > 0 {
self.writer.write_u8((self.buffer & 0xFF) as u8)?;
self.writer
.write_u8(((self.buffer << (8 - self.buffer_size)) & 0xFF) as u8)?;
self.buffer = 0;
self.buffer_size = 0;
}