mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-06 05:38:48 +08:00
feat: HuneX initial support
This commit is contained in:
@@ -124,6 +124,11 @@ namespace GameRes.Formats.BGI
|
||||
{
|
||||
if (!file.View.AsciiEqual (4, "KO ARC20"))
|
||||
return null;
|
||||
return Open (file);
|
||||
}
|
||||
|
||||
protected ArcFile Open (ArcView file)
|
||||
{
|
||||
int count = file.View.ReadInt32 (12);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
@@ -152,7 +157,7 @@ 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+4, "bw "))
|
||||
else if (file.View.AsciiEqual (entry.Offset+5, "w "))
|
||||
entry.Type = "audio";
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
|
||||
@@ -39,13 +39,13 @@ namespace GameRes.Formats.BGI
|
||||
public BgiAudio ()
|
||||
{
|
||||
Signatures = new uint[] { 0x40, 0 };
|
||||
Extensions = new string[] { "bw", "", "_bw" };
|
||||
Extensions = new string[] { "bw", "", "_bw", "hw" };
|
||||
}
|
||||
|
||||
public override SoundInput TryOpen (IBinaryStream file)
|
||||
{
|
||||
var header = file.ReadHeader (8);
|
||||
if (!header.AsciiEqual (4, "bw "))
|
||||
if (!header.AsciiEqual (4, "bw ") && !header.AsciiEqual (4, "hw "))
|
||||
return null;
|
||||
uint offset = header.ToUInt32 (0);
|
||||
if (offset >= file.Length)
|
||||
|
||||
58
ArcFormats/HuneX/ArcHFA.cs
Normal file
58
ArcFormats/HuneX/ArcHFA.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! \file ArcHFA.cs
|
||||
//! \date 2026-01-18
|
||||
//! \brief HUNEX General Game Engine resource archive.
|
||||
//
|
||||
// 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.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.HuneX {
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class HfaOpener : BGI.Arc2Opener {
|
||||
public override string Tag { get { return "HFA"; } }
|
||||
public override string Description { get { return "HuneX general game engine resource archive"; } }
|
||||
public override uint Signature { get { return 0x454e5548; } } // "HUNE"
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public HfaOpener() {
|
||||
Extensions = new string[] { "hfa" };
|
||||
ContainedFormats = new[] { "BGI", "CompressedBG", "BW", "SCR" };
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen(ArcView file) {
|
||||
if (!file.View.AsciiEqual(4, "XGGEFA10"))
|
||||
return null;
|
||||
return Open(file);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry(ArcFile arc, Entry entry) {
|
||||
if (!arc.File.View.AsciiEqual(entry.Offset, "LenZuCompressor"))
|
||||
return base.OpenEntry(arc, entry);
|
||||
var decoder = new LenZuDecoder(arc.File.View.ReadBytes(entry.Offset + 0x20, entry.Size - 0x20));
|
||||
return new BinMemoryStream(decoder.Unpack());
|
||||
}
|
||||
}
|
||||
}
|
||||
191
ArcFormats/HuneX/Decoder.cs
Normal file
191
ArcFormats/HuneX/Decoder.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
//! \file Decoder.cs
|
||||
//! \date 2026-02-22
|
||||
//! \brief HUNEX General Game Engine decompression functions.
|
||||
//
|
||||
// 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.Linq;
|
||||
|
||||
namespace GameRes.Formats.HuneX {
|
||||
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);
|
||||
|
||||
for (int i = 0; i < first_real_entry; i++) {
|
||||
m_table.Add(new HuffmanNode {
|
||||
Index = i,
|
||||
Weight = weights.TryGetValue(i, out int w) ? w : 0
|
||||
});
|
||||
}
|
||||
|
||||
m_invert = invert;
|
||||
}
|
||||
|
||||
public void Build(int max_entries) {
|
||||
int total_weight = m_table.Sum(x => x.Weight);
|
||||
for (int i = m_table.Count; i < max_entries; i++) {
|
||||
HuffmanNode child0 = null, child1 = null;
|
||||
for (int j = 0; j < i; j++) {
|
||||
var node = m_table[j];
|
||||
if (node.Weight == 0 || node.Parent != null)
|
||||
continue;
|
||||
if (child0 == null || node.Weight < child0.Weight) {
|
||||
child1 = child0;
|
||||
child0 = node;
|
||||
}
|
||||
else if (child1 == null || node.Weight < child1.Weight) {
|
||||
child1 = node;
|
||||
}
|
||||
}
|
||||
var parent = new HuffmanNode();
|
||||
if (m_invert) {
|
||||
SetNodeRelation(parent, child1, child0);
|
||||
}
|
||||
else {
|
||||
SetNodeRelation(parent, child0, child1);
|
||||
}
|
||||
m_table.Add(parent);
|
||||
if (parent.Weight >= total_weight)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public int DecodeSequence(MsbBitStream input) {
|
||||
HuffmanNode node = m_table[m_table.Count - 1];
|
||||
|
||||
while (node.Child0 != null || node.Child1 != null) {
|
||||
int bit = input.GetNextBit();
|
||||
node = bit > 0 ? node.Child1 : node.Child0;
|
||||
}
|
||||
|
||||
return node.Index;
|
||||
}
|
||||
|
||||
void SetNodeRelation(HuffmanNode parent, HuffmanNode child0, HuffmanNode child1) {
|
||||
if (child0 != null) {
|
||||
parent.Child0 = child0;
|
||||
child0.Parent = parent;
|
||||
parent.Weight += child0.Weight;
|
||||
}
|
||||
if (child1 != null) {
|
||||
parent.Child1 = child1;
|
||||
child1.Parent = parent;
|
||||
parent.Weight += child1.Weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LenZuSettings {
|
||||
public byte HuffmanTableBitCount;
|
||||
public byte BackrefLowBitCount;
|
||||
public byte BackrefBaseDistance;
|
||||
}
|
||||
|
||||
internal sealed class LenZuDecoder {
|
||||
Stream m_input;
|
||||
byte[] m_unpacked;
|
||||
LenZuSettings m_settings;
|
||||
|
||||
public LenZuDecoder(byte[] buffer) {
|
||||
m_unpacked = new byte[BitConverter.ToUInt32(buffer, 0)];
|
||||
m_settings = new LenZuSettings {
|
||||
HuffmanTableBitCount = Math.Max(buffer[0x11], buffer[0x12]),
|
||||
BackrefLowBitCount = buffer[0x14],
|
||||
BackrefBaseDistance = buffer[0x15]
|
||||
};
|
||||
m_input = new MemoryStream(buffer.Skip(0x16).ToArray());
|
||||
}
|
||||
|
||||
public byte[] Unpack() {
|
||||
int offset = 0;
|
||||
int first_real_entry = 1 << m_settings.HuffmanTableBitCount;
|
||||
int index_bits = (m_settings.HuffmanTableBitCount + 7) / 8;
|
||||
int index_bytes = (index_bits + 7) / 8;
|
||||
int fill_entries = ReadIntVL(index_bytes);
|
||||
if (fill_entries == 0)
|
||||
fill_entries = first_real_entry;
|
||||
var weights = new Dictionary<int, int>();
|
||||
if (first_real_entry * 4 < (index_bits + 4) * fill_entries) {
|
||||
fill_entries = first_real_entry;
|
||||
for (int i = 0; i < fill_entries; i++) {
|
||||
weights[i] = ReadIntVL();
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < fill_entries; i++) {
|
||||
int idx = ReadIntVL(index_bytes);
|
||||
weights[idx] = ReadIntVL();
|
||||
}
|
||||
}
|
||||
var tree = new HuffmanTree(first_real_entry, 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) {
|
||||
int isBackRef = input.GetNextBit();
|
||||
if (isBackRef == -1)
|
||||
break;
|
||||
int length = tree.DecodeSequence(input);
|
||||
if (isBackRef > 0) {
|
||||
length += m_settings.BackrefBaseDistance;
|
||||
int distanceHighBits = tree.DecodeSequence(input);
|
||||
int distanceLowBits = m_settings.BackrefLowBitCount > 0
|
||||
? input.GetBits(m_settings.BackrefLowBitCount) : 0;
|
||||
int distance = (distanceLowBits
|
||||
| (distanceHighBits << m_settings.BackrefLowBitCount))
|
||||
+ m_settings.BackrefBaseDistance;
|
||||
for (int i = 0; i < length; i++) {
|
||||
m_unpacked[offset] = m_unpacked[offset - distance];
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < length + 1; i++) {
|
||||
m_unpacked[offset++] = (byte)input.GetBits(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
return m_unpacked;
|
||||
}
|
||||
}
|
||||
|
||||
int ReadIntVL(int length = sizeof(int)) {
|
||||
var buffer = new byte[Math.Max(sizeof(int), length)];
|
||||
m_input.Read(buffer, 0, length);
|
||||
return BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user