mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 13:28:47 +08:00
Add compress support for softpal pgd image file
This commit is contained in:
@@ -297,11 +297,16 @@ impl<T: Read + Seek> PgdReader<T> {
|
||||
pub struct PgdWriter {
|
||||
data: ImageData,
|
||||
method: u32,
|
||||
fake_compress: bool,
|
||||
}
|
||||
|
||||
impl PgdWriter {
|
||||
pub fn new(data: ImageData) -> Self {
|
||||
Self { data, method: 3 }
|
||||
pub fn new(data: ImageData, fake_compress: bool) -> Self {
|
||||
Self {
|
||||
data,
|
||||
method: 3,
|
||||
fake_compress,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_method(mut self, method: u32) -> Self {
|
||||
@@ -315,7 +320,11 @@ impl PgdWriter {
|
||||
_ => panic!("Unsupported GE mode: {}", self.method),
|
||||
};
|
||||
let unpacked_len = data.len() as u32;
|
||||
let compressed = ge_fake_compress(&data)?;
|
||||
let compressed = if self.fake_compress {
|
||||
ge_fake_compress(&data)?
|
||||
} else {
|
||||
ge_compress(&data)?
|
||||
};
|
||||
let packed_len = compressed.len() as u32;
|
||||
writer.write_u32(unpacked_len)?;
|
||||
writer.write_u32(packed_len)?;
|
||||
@@ -412,3 +421,199 @@ fn ge_fake_compress(data: &[u8]) -> Result<Vec<u8>> {
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
// 新增:基于散列的快速 LZSS 压缩,兼容 unpack_ge_pre
|
||||
fn ge_compress(data: &[u8]) -> Result<Vec<u8>> {
|
||||
const MIN_MATCH: usize = 4;
|
||||
const MAX_LEN: usize = 0x7FF + 4; // 2047 + 4 = 2051
|
||||
const MAX_DIST: usize = 0xFFF; // 12-bit distance
|
||||
const HASH_BITS: usize = 16;
|
||||
const HASH_SIZE: usize = 1 << HASH_BITS;
|
||||
|
||||
#[inline(always)]
|
||||
fn hash3(bytes: &[u8]) -> usize {
|
||||
// 3字节哈希,乘黄金常数,取高 HASH_BITS 位
|
||||
let v = ((bytes[0] as u32) << 16) ^ ((bytes[1] as u32) << 8) ^ (bytes[2] as u32);
|
||||
(v.wrapping_mul(0x9E3779B1) >> (32 - HASH_BITS)) as usize
|
||||
}
|
||||
|
||||
let n = data.len();
|
||||
if n == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut out = Vec::with_capacity(n / 2 + 16);
|
||||
|
||||
// 控制块状态
|
||||
let mut ctrl_pos = out.len();
|
||||
out.push(0u8); // 占位
|
||||
let mut ctrl: u8 = 0;
|
||||
let mut ctrl_cnt: u8 = 0;
|
||||
|
||||
// 哈希表:保存最近出现位置
|
||||
let mut head = vec![usize::MAX; HASH_SIZE];
|
||||
|
||||
// 延迟字面量缓冲
|
||||
let mut lit_start = 0usize;
|
||||
let mut lit_len = 0usize;
|
||||
|
||||
// 辅助:开始新的控制块
|
||||
#[inline(always)]
|
||||
fn start_block(buf: &mut Vec<u8>, ctrl_pos: &mut usize, ctrl: &mut u8, ctrl_cnt: &mut u8) {
|
||||
*ctrl = 0;
|
||||
*ctrl_cnt = 0;
|
||||
*ctrl_pos = buf.len();
|
||||
buf.push(0u8);
|
||||
}
|
||||
|
||||
// 辅助:写入控制字节
|
||||
#[inline(always)]
|
||||
fn flush_ctrl(buf: &mut Vec<u8>, ctrl_pos: usize, ctrl: u8) {
|
||||
if let Some(slot) = buf.get_mut(ctrl_pos) {
|
||||
*slot = ctrl;
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助:输出字面量(可拆分为多个 <=255 的条目)
|
||||
let flush_literals = |out: &mut Vec<u8>,
|
||||
ctrl: &mut u8,
|
||||
ctrl_cnt: &mut u8,
|
||||
ctrl_pos: &mut usize,
|
||||
lit_start: &mut usize,
|
||||
lit_len: &mut usize| {
|
||||
while *lit_len > 0 {
|
||||
if *ctrl_cnt == 8 {
|
||||
flush_ctrl(out, *ctrl_pos, *ctrl);
|
||||
start_block(out, ctrl_pos, ctrl, ctrl_cnt);
|
||||
}
|
||||
let chunk = std::cmp::min(255, *lit_len);
|
||||
out.push(chunk as u8);
|
||||
out.extend_from_slice(&data[*lit_start..*lit_start + chunk]);
|
||||
*lit_start += chunk;
|
||||
*lit_len -= chunk;
|
||||
*ctrl_cnt += 1; // 字面量控制位为0,无需设置位
|
||||
}
|
||||
};
|
||||
|
||||
let mut pos = 0usize;
|
||||
|
||||
while pos < n {
|
||||
// 尝试匹配
|
||||
let mut best_len = 0usize;
|
||||
let mut best_dist = 0usize;
|
||||
|
||||
if pos + MIN_MATCH <= n {
|
||||
let h = hash3(&data[pos..pos + 3]);
|
||||
let cand = head[h];
|
||||
if cand != usize::MAX && cand < pos {
|
||||
let dist = pos - cand;
|
||||
if dist > 0 && dist <= MAX_DIST {
|
||||
// 计算匹配长度
|
||||
let max_len = std::cmp::min(MAX_LEN, n - pos);
|
||||
// 快速比较
|
||||
let mut l = 0usize;
|
||||
while l < max_len && data[cand + l] == data[pos + l] {
|
||||
l += 1;
|
||||
}
|
||||
if l >= MIN_MATCH {
|
||||
best_len = l;
|
||||
best_dist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if best_len >= MIN_MATCH {
|
||||
// 先刷新字面量
|
||||
flush_literals(
|
||||
&mut out,
|
||||
&mut ctrl,
|
||||
&mut ctrl_cnt,
|
||||
&mut ctrl_pos,
|
||||
&mut lit_start,
|
||||
&mut lit_len,
|
||||
);
|
||||
|
||||
// 控制块满则换块
|
||||
if ctrl_cnt == 8 {
|
||||
flush_ctrl(&mut out, ctrl_pos, ctrl);
|
||||
start_block(&mut out, &mut ctrl_pos, &mut ctrl, &mut ctrl_cnt);
|
||||
}
|
||||
|
||||
let l = best_len.min(MAX_LEN);
|
||||
let dist = best_dist;
|
||||
|
||||
// 写入回溯条目:u16 (LE),必要时再跟长度低8位
|
||||
let len_minus4 = l - 4;
|
||||
let mut word: u16 = ((dist as u16) << 4) as u16;
|
||||
if len_minus4 <= 7 {
|
||||
// 短长度:bit3=1,低3位为长度
|
||||
word |= (len_minus4 as u16) | 0x8;
|
||||
out.push((word & 0xFF) as u8);
|
||||
out.push((word >> 8) as u8);
|
||||
} else {
|
||||
// 扩展长度:bit3=0,低3位为长度高3位,随后写低8位
|
||||
word |= ((len_minus4 >> 8) as u16) & 0x7;
|
||||
out.push((word & 0xFF) as u8);
|
||||
out.push((word >> 8) as u8);
|
||||
out.push((len_minus4 & 0xFF) as u8);
|
||||
}
|
||||
|
||||
// 设置控制位为1
|
||||
let bit = 1u8.wrapping_shl(ctrl_cnt as u32);
|
||||
ctrl |= bit;
|
||||
ctrl_cnt += 1;
|
||||
|
||||
// 更新哈希表(覆盖匹配范围,避免过度开销也可简化为只更新起始位)
|
||||
let end = pos + l;
|
||||
let upd_end = end
|
||||
.saturating_sub(MIN_MATCH - 1)
|
||||
.min(n.saturating_sub(MIN_MATCH));
|
||||
let mut p = pos;
|
||||
while p <= upd_end {
|
||||
let h = hash3(&data[p..p + 3]);
|
||||
head[h] = p;
|
||||
p += 1;
|
||||
}
|
||||
|
||||
pos += l;
|
||||
lit_start = pos;
|
||||
lit_len = 0;
|
||||
} else {
|
||||
// 作为字面量
|
||||
if pos + 2 < n {
|
||||
let h = hash3(&data[pos..pos + 3]);
|
||||
head[h] = pos;
|
||||
}
|
||||
pos += 1;
|
||||
lit_len += 1;
|
||||
|
||||
// 字面量满255则立刻输出一条
|
||||
if lit_len == 255 {
|
||||
flush_literals(
|
||||
&mut out,
|
||||
&mut ctrl,
|
||||
&mut ctrl_cnt,
|
||||
&mut ctrl_pos,
|
||||
&mut lit_start,
|
||||
&mut lit_len,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束时刷新剩余字面量与控制字节
|
||||
if lit_len > 0 {
|
||||
flush_literals(
|
||||
&mut out,
|
||||
&mut ctrl,
|
||||
&mut ctrl_cnt,
|
||||
&mut ctrl_pos,
|
||||
&mut lit_start,
|
||||
&mut lit_len,
|
||||
);
|
||||
}
|
||||
flush_ctrl(&mut out, ctrl_pos, ctrl);
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl ScriptBuilder for PgdGeBuilder {
|
||||
&'a self,
|
||||
data: ImageData,
|
||||
mut writer: Box<dyn WriteSeek + 'a>,
|
||||
_options: &ExtraConfig,
|
||||
options: &ExtraConfig,
|
||||
) -> Result<()> {
|
||||
let header = PgdGeHeader {
|
||||
offset_x: 0,
|
||||
@@ -72,7 +72,9 @@ impl ScriptBuilder for PgdGeBuilder {
|
||||
};
|
||||
writer.write_all(b"GE \0")?;
|
||||
header.pack(&mut writer, false, Encoding::Utf8)?;
|
||||
PgdWriter::new(data).with_method(3).pack_ge(&mut writer)?;
|
||||
PgdWriter::new(data, options.pgd_fake_compress)
|
||||
.with_method(3)
|
||||
.pack_ge(&mut writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -81,10 +83,11 @@ impl ScriptBuilder for PgdGeBuilder {
|
||||
pub struct PgdGe {
|
||||
header: PgdGeHeader,
|
||||
data: ImageData,
|
||||
fake_compress: bool,
|
||||
}
|
||||
|
||||
impl PgdGe {
|
||||
pub fn new<T: Read + Seek>(mut input: T, _config: &ExtraConfig) -> Result<Self> {
|
||||
pub fn new<T: Read + Seek>(mut input: T, config: &ExtraConfig) -> Result<Self> {
|
||||
let mut magic = [0u8; 4];
|
||||
input.read_exact(&mut magic)?;
|
||||
if &magic != b"GE \0" {
|
||||
@@ -93,7 +96,11 @@ impl PgdGe {
|
||||
let header = PgdGeHeader::unpack(&mut input, false, Encoding::Utf8)?;
|
||||
let reader = PgdReader::with_ge_header(input, &header)?;
|
||||
let data = reader.unpack_ge()?;
|
||||
Ok(Self { header, data })
|
||||
Ok(Self {
|
||||
header,
|
||||
data,
|
||||
fake_compress: config.pgd_fake_compress,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +144,9 @@ impl Script for PgdGe {
|
||||
header.mode = 3;
|
||||
file.write_all(b"GE \0")?;
|
||||
header.pack(&mut file, false, Encoding::Utf8)?;
|
||||
PgdWriter::new(data).with_method(3).pack_ge(&mut file)?;
|
||||
PgdWriter::new(data, self.fake_compress)
|
||||
.with_method(3)
|
||||
.pack_ge(&mut file)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user