From 87195d86735f818fcb9a3a9fa2eae91e4fa42c55 Mon Sep 17 00:00:00 2001 From: scientificworld Date: Sun, 18 Jan 2026 10:10:35 +0800 Subject: [PATCH] feat: support compressed and embedded EVB package There might be a conflict with Experimental/Microsoft/ArcEXE.cs. Maybe add a option later to disable that? --- ArcFormats/Enigma/ArcEVB.cs | 57 +++++++++-- ArcFormats/aPLibStream.cs | 188 ++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 ArcFormats/aPLibStream.cs diff --git a/ArcFormats/Enigma/ArcEVB.cs b/ArcFormats/Enigma/ArcEVB.cs index e117e293..f833329f 100644 --- a/ArcFormats/Enigma/ArcEVB.cs +++ b/ArcFormats/Enigma/ArcEVB.cs @@ -29,6 +29,7 @@ using System.IO; using System.Linq; using System.Text; using GameRes.Utility; +using GameRes.Compression; namespace GameRes.Formats.Enigma { public enum NodeTypes { @@ -45,14 +46,33 @@ namespace GameRes.Formats.Enigma { public override bool IsHierarchic { get { return true; } } public override bool CanWrite { get { return false; } } + public EvbPackOpener() { + Signatures = new uint[] { 0x425645, 0x905a4d, 0 }; + } + public override ArcFile TryOpen(ArcView file) { - uint index_size = file.View.ReadUInt32(0x40) + 68; - uint index_offset = 0x4F; + uint base_offset = 0; + if (file.View.AsciiEqual(0, "MZ")) { + var exe = new ExeFile(file); + var sig = new byte[] { 0x45, 0x56, 0x42, 0x00 }; + if (exe.ContainsSection(".enigma1")) { + var ofs = exe.FindString(exe.Sections[".enigma1"], sig); + if (ofs != -1) + base_offset = (uint)ofs; + } + if (base_offset == 0) + return null; + } + else if (!file.View.AsciiEqual(0, "EVB")) + return null; + + uint index_size = file.View.ReadUInt32(base_offset + 0x40) + base_offset + 68; + uint index_offset = base_offset + 0x4F; uint file_offset = index_size; var dir = new List(); var name_buffer = new StringBuilder(); - var counts = new List { file.View.ReadUInt32(0x4C) }; + var counts = new List { file.View.ReadUInt32(base_offset + 0x4C) }; var names = new List { "" }; while (index_offset < index_size - 4) { @@ -73,12 +93,12 @@ namespace GameRes.Formats.Enigma { index_offset++; counts[counts.Count - 1]--; if (type == NodeTypes.File) { - var entry = Create(Path.Combine(names.Concat(new[] { name }).ToArray())); + var entry = Create(Path.Combine(names.Concat(new[] { name }).ToArray())); uint unpacked_size = file.View.ReadUInt32(index_offset + 2); uint size = file.View.ReadUInt32(index_offset + 49); - if (unpacked_size != size) - return null; // packed entry not implemented + entry.IsPacked = unpacked_size != size; entry.Offset = file_offset; + entry.UnpackedSize = unpacked_size; entry.Size = size; file_offset += size; if (!entry.CheckPlacement(file.MaxOffset)) @@ -106,5 +126,30 @@ namespace GameRes.Formats.Enigma { return new ArcFile(file, this, dir); } + + public override Stream OpenEntry(ArcFile arc, Entry entry) { + var pent = entry as PackedEntry; + if (pent.IsPacked) { + uint header_size = arc.File.View.ReadUInt32(pent.Offset); + uint offset = header_size; + Stream input = null; + + for (uint i = 8; i < header_size; i += 12) { + uint chunk_size = arc.File.View.ReadUInt32(pent.Offset + i); + var chunk = new aPLibStream( + arc.File.CreateStream(pent.Offset + offset, chunk_size) + ); + if (input != null) + input = new ConcatStream(input, chunk); + else + input = chunk; + offset += chunk_size; + } + + return input; + } + else + return arc.File.CreateStream(pent.Offset, pent.Size); + } } } diff --git a/ArcFormats/aPLibStream.cs b/ArcFormats/aPLibStream.cs new file mode 100644 index 00000000..a454750f --- /dev/null +++ b/ArcFormats/aPLibStream.cs @@ -0,0 +1,188 @@ +//! \file aPLibStream.cs +//! \date 2026-01-09 + +/* + * aPLib compression library - the smaller the better :) + * + * C depacker + * + * Copyright (c) 1998-2014 Joergen Ibsen + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + */ + +// C# port by scientificworld + +using System; +using System.Collections.Generic; +using System.IO; + +namespace GameRes.Compression { + public sealed class aPLibCoroutine : Decompressor { + Stream m_input; + uint m_tag; + uint m_bitcount; + + public override void Initialize (Stream input) { + m_input = input; + } + + protected override IEnumerator Unpack () { + uint offs, len, R0, LWM; + + var hist = new List(); + + m_bitcount = 0; + R0 = uint.MaxValue; // (uint) -1 + LWM = 0; + + m_buffer[m_pos] = ReadByte(); + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + + while (true) { + if (GetBit() != 0) { + if (GetBit() != 0) { + if (GetBit() != 0) { + offs = 0; + + for (int i = 0; i < 4; i++) { + offs = (offs << 1) + GetBit(); + } + + if (offs != 0) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + } + else { + m_buffer[m_pos] = 0x00; + } + hist.Add(m_buffer[m_pos++]); + + LWM = 0; + if (--m_length == 0) + yield return m_pos; + } + else { + offs = (uint)ReadByte(); + + len = 2 + (offs & 0x0001); + + offs >>= 1; + + if (offs != 0) { + for (; len != 0; len--) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + } + } + else { + yield break; + } + + R0 = offs; + LWM = 1; + } + } + else { + offs = GetGamma(); + + if ((LWM == 0) && (offs == 2)) { + offs = R0; + + len = GetGamma(); + + for (; len != 0; len--) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + } + } + else { + if (LWM == 0) { + offs -= 3; + } + else { + offs -= 2; + } + + offs <<= 8; + offs += (uint)ReadByte(); + + len = GetGamma(); + + if (offs >= 32000) { + len++; + } + if (offs >= 1280) { + len++; + } + if (offs < 128) { + len += 2; + } + + for (; len != 0; len--) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + } + + R0 = offs; + } + + LWM = 1; + } + } + else { + m_buffer[m_pos] = ReadByte(); + hist.Add(m_buffer[m_pos++]); + LWM = 0; + if (--m_length == 0) + yield return m_pos; + } + } + } + + uint GetBit () { + uint bit; + + if (m_bitcount-- == 0) { + m_tag = (uint)ReadByte(); + m_bitcount = 7; + } + bit = (m_tag >> 7) & 0x01; + m_tag <<= 1; + + return bit; + } + + uint GetGamma () { + uint result = 1; + + do { + result = (result << 1) + GetBit(); + } while (GetBit() != 0); + + return result; + } + + byte ReadByte () { + int b = m_input.ReadByte(); + if (b == -1) + throw new EndOfStreamException(); + return (byte)b; + } + } + + public class aPLibStream : PackedStream { + public aPLibStream (Stream input, CompressionMode mode = CompressionMode.Decompress, bool leave_open = false) : base (input, leave_open) { + if (mode != CompressionMode.Decompress) + throw new NotImplementedException ("aPLibStream compression not implemented"); + } + } +}