mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-06 13:48:57 +08:00
feat: HuneX image formats
This commit is contained in:
@@ -157,6 +157,8 @@ namespace GameRes.Formats.BGI
|
||||
entry.Type = res.Type;
|
||||
else if (file.View.AsciiEqual (entry.Offset, "BSE 1."))
|
||||
entry.Type = "image";
|
||||
else if (file.View.AsciiEqual (entry.Offset, "CompressedBG"))
|
||||
entry.Type = "image";
|
||||
else if (file.View.AsciiEqual (entry.Offset+5, "w "))
|
||||
entry.Type = "audio";
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace GameRes.Formats.HuneX {
|
||||
|
||||
public HfaOpener() {
|
||||
Extensions = new string[] { "hfa" };
|
||||
ContainedFormats = new[] { "BGI", "CompressedBG", "BW", "SCR" };
|
||||
ContainedFormats = new[] { "BGI", "CompressedBG_MT", "BW", "SCR" };
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen(ArcView file) {
|
||||
|
||||
320
ArcFormats/HuneX/ArcMZP.cs
Normal file
320
ArcFormats/HuneX/ArcMZP.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
//! \file ArcMZP.cs
|
||||
//! \date 2026-02-03
|
||||
//! \brief HUNEX General Game Engine multi-frame image container.
|
||||
//
|
||||
// Copyright (C) 2026 by morkt
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.HuneX {
|
||||
internal class MzpMetaData : ImageMetaData {
|
||||
public uint TileWidth { get; set; }
|
||||
public uint TileHeight { get; set; }
|
||||
public uint TileXCount { get; set; }
|
||||
public uint TileYCount { get; set; }
|
||||
public uint Characteristics { get; set; }
|
||||
public uint Depth { get; set; }
|
||||
public uint TileCrop { get; set; }
|
||||
public BitmapPalette Palette { get; set; }
|
||||
}
|
||||
|
||||
internal class MzpArchive : ArcFile {
|
||||
public MzpMetaData MetaData { get; set; }
|
||||
|
||||
public MzpArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, MzpMetaData metadata) : base (arc, impl, dir) {
|
||||
MetaData = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MzpEntry : Entry {
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class MrgOpener : ArchiveFormat {
|
||||
public override string Tag { get { return "MZP"; } }
|
||||
public override string Description { get { return "HuneX general game engine multi-frame image"; } }
|
||||
public override uint Signature { get { return 0x6467726d; } } // "mrgd"
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public MrgOpener() {
|
||||
Extensions = new string[] { "mzp" };
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen(ArcView file) {
|
||||
if (!file.View.AsciiEqual(4, "00"))
|
||||
return null;
|
||||
|
||||
int count = file.View.ReadInt16(6);
|
||||
if (!IsSaneCount(count))
|
||||
return null;
|
||||
|
||||
var base_name = Path.GetFileNameWithoutExtension(file.Name);
|
||||
MzpMetaData metadata = null;
|
||||
|
||||
var dir = new List<Entry>(count);
|
||||
uint offset = 8;
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint section_offset = file.View.ReadUInt16(offset);
|
||||
uint file_offset = file.View.ReadUInt16(offset + 2);
|
||||
uint size_boundary = file.View.ReadUInt16(offset + 4);
|
||||
uint size = file.View.ReadUInt16(offset + 6);
|
||||
var entry = new MzpEntry {
|
||||
Offset = 8 * (count + 1) + section_offset * 0x800 + file_offset,
|
||||
Size = (size_boundary - 1) / 0x20 * 0x800 * 0x20 + size
|
||||
};
|
||||
if (!entry.CheckPlacement(file.MaxOffset))
|
||||
return null;
|
||||
if (i == 0)
|
||||
metadata = ReadInfo(file, entry);
|
||||
else {
|
||||
entry.Name = string.Format("{0}#{1:D3}", base_name, i);
|
||||
entry.Type = "image";
|
||||
entry.Index = i;
|
||||
dir.Add(entry);
|
||||
}
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
return null;
|
||||
return new MzpArchive(file, this, dir, metadata);
|
||||
}
|
||||
|
||||
MzpMetaData ReadInfo(ArcView file, Entry entry) {
|
||||
if (entry.Size < 16)
|
||||
return null;
|
||||
|
||||
MzpMetaData metadata = new MzpMetaData();
|
||||
metadata.Width = file.View.ReadUInt16(entry.Offset);
|
||||
metadata.Height = file.View.ReadUInt16(entry.Offset + 2);
|
||||
metadata.TileWidth = file.View.ReadUInt16(entry.Offset + 4);
|
||||
metadata.TileHeight = file.View.ReadUInt16(entry.Offset + 6);
|
||||
metadata.TileXCount = file.View.ReadUInt16(entry.Offset + 8);
|
||||
metadata.TileYCount = file.View.ReadUInt16(entry.Offset + 10);
|
||||
ushort type = file.View.ReadUInt16(entry.Offset + 12);
|
||||
metadata.Characteristics = type;
|
||||
byte depth = file.View.ReadByte(entry.Offset + 14);
|
||||
metadata.Depth = depth;
|
||||
metadata.TileCrop = file.View.ReadByte(entry.Offset + 15);
|
||||
|
||||
if (type == 1 && (depth & 0xf) == 0)
|
||||
metadata.BPP = 4;
|
||||
else if (type == 1 && (depth & 0xf) == 1)
|
||||
metadata.BPP = 8;
|
||||
else if (type == 8 && depth == 0x14)
|
||||
metadata.BPP = 24;
|
||||
else if ((type == 0xb && depth == 0x14) || (type == 0xc && depth == 0x11))
|
||||
metadata.BPP = 32;
|
||||
else
|
||||
throw new NotImplementedException("[MZP] Unsupported BPP type");
|
||||
|
||||
if (type == 1) {
|
||||
int palette_size = (depth & 0xf) == 1 ? 256 : 16;
|
||||
var raw_palette = new byte[0x400];
|
||||
file.View.Read(entry.Offset + 16, raw_palette, 0, (uint)palette_size * 4);
|
||||
if (depth == 0x11 || depth == 0x91) {
|
||||
for (int i = 0; i < palette_size; i += 32) {
|
||||
var block = new byte[32];
|
||||
Buffer.BlockCopy(raw_palette, (i + 8) * 4, block, 0, block.Length);
|
||||
Buffer.BlockCopy(raw_palette, (i + 16) * 4, raw_palette, (i + 8) * 4, block.Length);
|
||||
Buffer.BlockCopy(block, 0, raw_palette, (i + 16) * 4, block.Length);
|
||||
}
|
||||
}
|
||||
for (int i = palette_size; i < 256; i++)
|
||||
raw_palette[i * 4 + 3] = 0xFF;
|
||||
metadata.Palette = MzxImageReader.GetPaletteFromRaw(raw_palette);
|
||||
}
|
||||
else {
|
||||
metadata.Palette = null;
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public override IImageDecoder OpenImage(ArcFile arc, Entry entry) {
|
||||
var marc = arc as MzpArchive;
|
||||
|
||||
byte[] buffer;
|
||||
if (arc.File.View.AsciiEqual(entry.Offset, "MZX0")) {
|
||||
buffer = arc.File.View.ReadBytes(entry.Offset + 4, entry.Size - 4);
|
||||
var decoder = new MzxDecoder(buffer);
|
||||
buffer = decoder.Unpack();
|
||||
}
|
||||
else
|
||||
buffer = arc.File.View.ReadBytes(entry.Offset, entry.Size);
|
||||
|
||||
return new MzxImageReader(new BinMemoryStream(buffer), marc.MetaData);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MzxImageReader : IImageDecoder {
|
||||
IBinaryStream m_input;
|
||||
byte[] m_output;
|
||||
MzpMetaData m_info;
|
||||
uint m_height;
|
||||
uint m_width;
|
||||
|
||||
public byte[] Data { get { return m_output; } }
|
||||
public PixelFormat Format { get; private set; }
|
||||
public BitmapPalette Palette { get; private set; }
|
||||
|
||||
public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } }
|
||||
|
||||
public ImageFormat SourceFormat { get { return null; } }
|
||||
|
||||
public ImageMetaData Info {
|
||||
get {
|
||||
return new ImageMetaData {
|
||||
Height = m_height,
|
||||
Width = m_width,
|
||||
BPP = Format.BitsPerPixel
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ImageData Image {
|
||||
get {
|
||||
if (null == m_output)
|
||||
Unpack();
|
||||
return ImageData.Create(Info, Format, Palette, Data);
|
||||
}
|
||||
}
|
||||
|
||||
public MzxImageReader(IBinaryStream input, MzpMetaData info) {
|
||||
m_input = input;
|
||||
m_info = info;
|
||||
m_height = m_info.TileHeight;
|
||||
m_width = m_info.TileWidth;
|
||||
Palette = m_info.Palette;
|
||||
switch (m_info.BPP) {
|
||||
case 4: // Format = PixelFormats.Indexed4; break;
|
||||
case 8: Format = PixelFormats.Indexed8; break;
|
||||
case 24: Format = PixelFormats.Bgr24; break;
|
||||
case 32: Format = PixelFormats.Bgra32; break;
|
||||
default: throw new InvalidFormatException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Unpack() {
|
||||
if (m_info.Characteristics == 0xC) {
|
||||
UnpackHep();
|
||||
return;
|
||||
}
|
||||
uint tile_size = m_height * m_width;
|
||||
m_output = new byte[tile_size * (m_info.BPP + 4) / 8];
|
||||
|
||||
uint index = 0;
|
||||
switch (m_info.BPP) {
|
||||
case 4:
|
||||
byte[] temp4 = new byte[(tile_size + 1) / 2];
|
||||
m_input.Read(temp4, 0, temp4.Length);
|
||||
for (int i = 0; i < temp4.Length; i++) {
|
||||
m_output[index++] = (byte)(temp4[i] & 0x0F);
|
||||
if (index < tile_size)
|
||||
m_output[index++] = (byte)(temp4[i] >> 4);
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
m_input.Read(m_output, 0, m_output.Length);
|
||||
break;
|
||||
|
||||
case 24:
|
||||
case 32:
|
||||
byte[] rgb565 = new byte[tile_size * 2];
|
||||
m_input.Read(rgb565, 0, rgb565.Length);
|
||||
byte[] offsets = new byte[tile_size];
|
||||
m_input.Read(offsets, 0, offsets.Length);
|
||||
byte[] alphas = null;
|
||||
if (m_info.BPP == 32) {
|
||||
alphas = new byte[tile_size];
|
||||
m_input.Read(alphas, 0, alphas.Length);
|
||||
}
|
||||
for (int i = 0; i < tile_size; i++) {
|
||||
ushort pq = BitConverter.ToUInt16(rgb565, i * 2);
|
||||
byte offset_byte = offsets[i];
|
||||
byte r = (byte)(((pq & 0xF800) >> 8) | ((offset_byte >> 5) & 7));
|
||||
byte g = (byte)(((pq & 0x07E0) >> 3) | ((offset_byte >> 3) & 3));
|
||||
byte b = (byte)(((pq & 0x001F) << 3) | (offset_byte & 7));
|
||||
m_output[index++] = b;
|
||||
m_output[index++] = g;
|
||||
m_output[index++] = r;
|
||||
if (alphas != null)
|
||||
m_output[index++] = alphas[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UnpackHep() {
|
||||
if (m_input.ReadUInt32() != 0x00504548) // 'HEP\0'
|
||||
throw new InvalidFormatException();
|
||||
m_input.ReadBytes(0x10);
|
||||
m_width = m_input.ReadUInt32();
|
||||
m_height = m_input.ReadUInt32();
|
||||
m_input.ReadUInt32();
|
||||
Format = PixelFormats.Indexed8;
|
||||
m_output = new byte[m_height * m_width];
|
||||
m_input.Read(m_output, 0, m_output.Length);
|
||||
|
||||
var raw_palette = new byte[0x400];
|
||||
m_input.Read(raw_palette, 0, raw_palette.Length);
|
||||
Palette = GetPaletteFromRaw(raw_palette);
|
||||
}
|
||||
|
||||
public static BitmapPalette GetPaletteFromRaw(byte[] raw_palette) {
|
||||
var colors = new Color[raw_palette.Length / 4];
|
||||
for (int i = 0; i < raw_palette.Length; i += 4) {
|
||||
byte r = raw_palette[i];
|
||||
byte g = raw_palette[i + 1];
|
||||
byte b = raw_palette[i + 2];
|
||||
byte a = raw_palette[i + 3];
|
||||
|
||||
if ((a & 0x80) == 0)
|
||||
a = (byte)(((a << 1) | (a >> 6)) & 0xFF);
|
||||
else
|
||||
a = 0xFF;
|
||||
|
||||
colors[i / 4] = Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
return new BitmapPalette(colors);
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool m_disposed = false;
|
||||
public void Dispose() {
|
||||
if (!m_disposed) {
|
||||
m_input.Dispose();
|
||||
m_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -30,25 +30,25 @@ using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace GameRes.Formats.HuneX {
|
||||
internal class HuffmanNode {
|
||||
public int Weight;
|
||||
public int Index;
|
||||
public HuffmanNode Parent;
|
||||
public HuffmanNode Child0;
|
||||
public HuffmanNode Child1;
|
||||
}
|
||||
internal class HuffmanTree {
|
||||
internal class HuffmanNode {
|
||||
public int Weight;
|
||||
public int Index;
|
||||
public HuffmanNode Parent;
|
||||
public HuffmanNode Child0;
|
||||
public HuffmanNode Child1;
|
||||
}
|
||||
|
||||
internal sealed class HuffmanTree {
|
||||
List<HuffmanNode> m_table;
|
||||
bool m_invert;
|
||||
|
||||
public HuffmanTree(int first_real_entry, Dictionary<int, int> weights, bool invert = false) {
|
||||
m_table = new List<HuffmanNode>(first_real_entry);
|
||||
public HuffmanTree(int[] weights, bool invert = false) {
|
||||
m_table = new List<HuffmanNode>(weights.Length);
|
||||
|
||||
for (int i = 0; i < first_real_entry; i++) {
|
||||
for (int i = 0; i < weights.Length; i++) {
|
||||
m_table.Add(new HuffmanNode {
|
||||
Index = i,
|
||||
Weight = weights.TryGetValue(i, out int w) ? w : 0
|
||||
Weight = weights[i]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace GameRes.Formats.HuneX {
|
||||
}
|
||||
}
|
||||
|
||||
public int DecodeSequence(MsbBitStream input) {
|
||||
public int DecodeSequence(IBitStream input) {
|
||||
HuffmanNode node = m_table[m_table.Count - 1];
|
||||
|
||||
while (node.Child0 != null || node.Child1 != null) {
|
||||
@@ -138,7 +138,7 @@ namespace GameRes.Formats.HuneX {
|
||||
int fill_entries = ReadIntVL(index_bytes);
|
||||
if (fill_entries == 0)
|
||||
fill_entries = first_real_entry;
|
||||
var weights = new Dictionary<int, int>();
|
||||
var weights = new int[first_real_entry]; // idk why this can work xD
|
||||
if (first_real_entry * 4 < (index_bits + 4) * fill_entries) {
|
||||
fill_entries = first_real_entry;
|
||||
for (int i = 0; i < fill_entries; i++) {
|
||||
@@ -151,7 +151,7 @@ namespace GameRes.Formats.HuneX {
|
||||
weights[idx] = ReadIntVL();
|
||||
}
|
||||
}
|
||||
var tree = new HuffmanTree(first_real_entry, weights, true);
|
||||
var tree = new HuffmanTree(weights, true);
|
||||
tree.Build(((first_real_entry + 1) * first_real_entry) >> 1);
|
||||
using (var input = new MsbBitStream(m_input, true)) {
|
||||
while (offset < m_unpacked.Length) {
|
||||
@@ -188,4 +188,99 @@ namespace GameRes.Formats.HuneX {
|
||||
return BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RingBuffer<T> {
|
||||
private readonly T[] m_buffer;
|
||||
private int m_head;
|
||||
private int m_tail;
|
||||
private int m_count;
|
||||
|
||||
public T this[int index] { get { return m_buffer[index]; } }
|
||||
|
||||
public RingBuffer(int capacity) {
|
||||
m_buffer = new T[capacity];
|
||||
}
|
||||
|
||||
public void Append(T item) {
|
||||
m_buffer[m_head] = item;
|
||||
m_head = (m_head + 1) % m_buffer.Length;
|
||||
if (m_count == m_buffer.Length)
|
||||
m_tail = (m_tail + 1) % m_buffer.Length;
|
||||
else
|
||||
m_count++;
|
||||
}
|
||||
|
||||
public void Append(T[] items) {
|
||||
foreach (T item in items)
|
||||
Append(item);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MzxDecoder {
|
||||
Stream m_input;
|
||||
byte[] m_unpacked;
|
||||
|
||||
public MzxDecoder(byte[] buffer) {
|
||||
m_unpacked = new byte[BitConverter.ToUInt32(buffer, 0)];
|
||||
m_input = new MemoryStream(buffer.Skip(4).ToArray());
|
||||
}
|
||||
|
||||
public byte[] Unpack() {
|
||||
int offset = 0;
|
||||
int counter = 0;
|
||||
var ringbuf = new RingBuffer<byte>(128);
|
||||
while (offset < m_unpacked.Length) {
|
||||
if (counter <= 0)
|
||||
counter = 0x1000;
|
||||
byte flag = (byte)m_input.ReadByte();
|
||||
int len = flag >> 2;
|
||||
var buffer = new byte[2];
|
||||
switch (flag & 3) {
|
||||
case 0: // RLE
|
||||
if (counter != 0x1000) {
|
||||
buffer[1] = m_unpacked[offset - 1];
|
||||
buffer[0] = m_unpacked[offset - 2];
|
||||
}
|
||||
offset = Write2(buffer, offset, len + 1);
|
||||
break;
|
||||
case 1: // BACKREF
|
||||
int k = m_input.ReadByte() * 2 + 2;
|
||||
buffer = new byte[len * 2 + 2];
|
||||
int pos = offset - k;
|
||||
k = Math.Min(k, buffer.Length);
|
||||
Buffer.BlockCopy(m_unpacked, pos, buffer, 0, k);
|
||||
for (pos = k; pos < buffer.Length; pos += k) {
|
||||
Buffer.BlockCopy(buffer, 0, buffer, pos, Math.Min(k, buffer.Length - pos));
|
||||
}
|
||||
offset = Write2(buffer, offset, 1);
|
||||
break;
|
||||
case 2: // RINGBUF
|
||||
buffer[0] = ringbuf[len * 2];
|
||||
buffer[1] = ringbuf[len * 2 + 1];
|
||||
offset = Write2(buffer, offset, 1);
|
||||
counter += len;
|
||||
break;
|
||||
case 3: // LITERAL
|
||||
buffer = new byte[len * 2 + 2];
|
||||
m_input.Read(buffer, 0, buffer.Length);
|
||||
offset = Write2(buffer, offset, 1);
|
||||
ringbuf.Append(buffer);
|
||||
break;
|
||||
}
|
||||
counter -= len + 1;
|
||||
}
|
||||
return m_unpacked;
|
||||
}
|
||||
|
||||
int Write2(byte[] buffer, int offset, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
int bytesToWrite = Math.Min(buffer.Length, m_unpacked.Length - offset);
|
||||
if (bytesToWrite <= 0)
|
||||
break;
|
||||
Buffer.BlockCopy(buffer, 0, m_unpacked, offset, bytesToWrite);
|
||||
offset += bytesToWrite;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
240
ArcFormats/HuneX/ImageCBG.cs
Normal file
240
ArcFormats/HuneX/ImageCBG.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
//! \file ImageCBG.cs
|
||||
//! \date 2026-02-19
|
||||
//! \brief HUNEX General Game Engine image format.
|
||||
//
|
||||
// Copyright (C) 2026 by morkt
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GameRes.Formats.HuneX
|
||||
{
|
||||
internal class CbgMetaData : ImageMetaData
|
||||
{
|
||||
public uint StripeHeight;
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class CompressedBGFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "CompressedBG_MT"; } }
|
||||
public override string Description { get { return "HUNEX General Game Engine compressed image format"; } }
|
||||
public override uint Signature { get { return 0x706D6F43; } }
|
||||
|
||||
public CompressedBGFormat ()
|
||||
{
|
||||
Extensions = new string[] { "cbg" };
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException ("BgiFormat.Write not implemented");
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData (IBinaryStream stream)
|
||||
{
|
||||
var header = stream.ReadHeader (0x30);
|
||||
if (!header.AsciiEqual ("CompressedBG_MT"))
|
||||
return null;
|
||||
return new CbgMetaData
|
||||
{
|
||||
Width = header.ToUInt32 (0x10),
|
||||
Height = header.ToUInt32 (0x14),
|
||||
StripeHeight = header.ToUInt32 (0x18),
|
||||
BPP = header.ToInt32 (0x1C),
|
||||
};
|
||||
}
|
||||
|
||||
public override ImageData Read (IBinaryStream stream, ImageMetaData info)
|
||||
{
|
||||
var meta = (CbgMetaData)info as CbgMetaData;
|
||||
using (var reader = new CbgReader (stream.AsStream, meta))
|
||||
{
|
||||
reader.Unpack();
|
||||
return ImageData.Create (meta, reader.Format, null, reader.Data, reader.Stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class CbgReader : LsbBitStream
|
||||
{
|
||||
byte[] m_output;
|
||||
CbgMetaData m_info;
|
||||
int m_pixel_size;
|
||||
|
||||
public byte[] Data { get { return m_output; } }
|
||||
public PixelFormat Format { get; private set; }
|
||||
public int Stride { get; private set; }
|
||||
|
||||
public CbgReader (Stream input, CbgMetaData info) : base (input, true)
|
||||
{
|
||||
m_info = info;
|
||||
m_pixel_size = m_info.BPP / 8;
|
||||
Stride = (int)info.Width * m_pixel_size;
|
||||
switch (m_info.BPP)
|
||||
{
|
||||
case 32: Format = PixelFormats.Bgra32; break;
|
||||
case 24: Format = PixelFormats.Bgr24; break;
|
||||
case 8: Format = PixelFormats.Gray8; break;
|
||||
default: throw new InvalidFormatException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Unpack ()
|
||||
{
|
||||
uint count = (m_info.Height + m_info.StripeHeight - 1) / m_info.StripeHeight;
|
||||
var len = new byte[4];
|
||||
var offsets = new uint[count];
|
||||
m_output = new byte[Stride * m_info.Height];
|
||||
m_input.Position = 0x30;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
m_input.Read (len, 0, 4);
|
||||
offsets[i] = BitConverter.ToUInt32 (len, 0);
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
m_input.Seek (offsets[i], SeekOrigin.Begin);
|
||||
uint height = (uint)Math.Min (m_info.StripeHeight, m_info.Height - m_info.StripeHeight * i);
|
||||
m_input.Read (len, 0, 4);
|
||||
var packed = new byte[BitConverter.ToUInt32 (len, 0)];
|
||||
var weights = ReadWeightTable (m_input, 0x100);
|
||||
var tree = new HuffmanTree (weights);
|
||||
tree.Build(0x1ff);
|
||||
HuffmanDecompress (tree, packed);
|
||||
var stripe_output = new byte[Stride * height];
|
||||
UnpackZeros (packed, stripe_output);
|
||||
ReverseAverageSampling (stripe_output, height);
|
||||
Buffer.BlockCopy (stripe_output, 0, m_output, (int)(Stride * m_info.StripeHeight * i), stripe_output.Length);
|
||||
}
|
||||
}
|
||||
|
||||
static internal int ReadInteger (Stream input)
|
||||
{
|
||||
int v = 0;
|
||||
int code;
|
||||
int code_length = 0;
|
||||
do
|
||||
{
|
||||
code = input.ReadByte();
|
||||
if (-1 == code || code_length >= 32)
|
||||
return -1;
|
||||
v |= (code & 0x7f) << code_length;
|
||||
code_length += 7;
|
||||
}
|
||||
while (0 != (code & 0x80));
|
||||
return v;
|
||||
}
|
||||
|
||||
static protected int[] ReadWeightTable (Stream input, int length)
|
||||
{
|
||||
int[] leaf_nodes_weight = new int[length];
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
int weight = ReadInteger (input);
|
||||
if (-1 == weight)
|
||||
throw new InvalidFormatException ("Invalid compressed stream");
|
||||
leaf_nodes_weight[i] = weight;
|
||||
}
|
||||
return leaf_nodes_weight;
|
||||
}
|
||||
|
||||
void HuffmanDecompress (HuffmanTree tree, byte[] output)
|
||||
{
|
||||
this.Reset();
|
||||
for (int dst = 0; dst < output.Length; dst++)
|
||||
{
|
||||
output[dst] = (byte)tree.DecodeSequence (this);
|
||||
}
|
||||
}
|
||||
|
||||
void UnpackZeros (byte[] input, byte[] output)
|
||||
{
|
||||
int dst = 0;
|
||||
int dec_zero = 0;
|
||||
int src = 0;
|
||||
while (dst < output.Length)
|
||||
{
|
||||
int code_length = 0;
|
||||
int count = 0;
|
||||
byte code;
|
||||
do
|
||||
{
|
||||
if (src >= input.Length)
|
||||
return;
|
||||
|
||||
code = input[src++];
|
||||
count |= (code & 0x7f) << code_length;
|
||||
code_length += 7;
|
||||
}
|
||||
while (0 != (code & 0x80));
|
||||
|
||||
if (dst + count > output.Length)
|
||||
break;
|
||||
|
||||
if (0 == dec_zero)
|
||||
{
|
||||
if (src + count > input.Length)
|
||||
break;
|
||||
Buffer.BlockCopy (input, src, output, dst, count);
|
||||
src += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < count; ++i)
|
||||
output[dst+i] = 0;
|
||||
}
|
||||
dec_zero ^= 1;
|
||||
dst += count;
|
||||
}
|
||||
}
|
||||
|
||||
void ReverseAverageSampling (byte[] output, uint height)
|
||||
{
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
int line = y * Stride;
|
||||
for (int x = 0; x < m_info.Width; ++x)
|
||||
{
|
||||
int pixel = line + x * m_pixel_size;
|
||||
for (int p = 0; p < m_pixel_size; p++)
|
||||
{
|
||||
int avg = 0;
|
||||
if (x > 0)
|
||||
avg += output[pixel + p - m_pixel_size];
|
||||
if (y > 0)
|
||||
avg += output[pixel + p - Stride];
|
||||
if (x > 0 && y > 0)
|
||||
avg /= 2;
|
||||
if (0 != avg)
|
||||
output[pixel + p] += (byte)avg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user