Add compress support for softpal pgd image file

This commit is contained in:
2025-09-17 21:32:21 +08:00
parent 6e64a85950
commit 8afc11b943
5 changed files with 231 additions and 8 deletions

View File

@@ -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)
}

View File

@@ -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(())
}
}