Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 251 additions & 2 deletions ArcFormats/Leaf/ArcPAK.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@
// IN THE SOFTWARE.
//

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using GameRes.Compression;
using GameRes.Utility;
using GameRes.Utility.Serialization;

namespace GameRes.Formats.Leaf
Expand All @@ -38,7 +42,7 @@ public class KcapOpener : ArchiveFormat
public override string Description { get { return "Leaf resource archive"; } }
public override uint Signature { get { return 0x5041434B; } } // 'KCAP'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override bool CanWrite { get { return true; } }

public KcapOpener ()
{
Expand Down Expand Up @@ -160,6 +164,87 @@ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
input.Position = 0;
return ImageFormatDecoder.Create (input);
}

// --- REPACK IMPLEMENTATION ---

public override void Create (Stream output, IEnumerable<Entry> entries, ResourceOptions options, EntryCallback callback)
{
int count = entries.Count();
using (var writer = new BinaryWriter (output, Encoding.ASCII, true))
{
// 1. Write Header (KCAP v2)
writer.Write (0x5041434B); // "KCAP"
writer.Write (0xFFFFFFFF);
writer.Write (0x0000FFFF);
writer.Write (count);

long tableOffset = 16;
long dataOffset = tableOffset + (count * 44);

writer.BaseStream.Position = dataOffset;

var entryInfos = new List<EntryInfo>();
int i = 0;

foreach (var entry in entries)
{
if (callback != null)
callback (i + 1, entry, null);

long currentOffset = writer.BaseStream.Position;

using (var input = File.OpenRead(entry.Name))
{
byte[] rawData = new byte[input.Length];
input.Read (rawData, 0, (int)input.Length);

// Compress
byte[] compressedData = LeafLzss.Compress (rawData);

// Write Data (Prefix + LZSS)
writer.Write ((uint)compressedData.Length);
writer.Write ((uint)rawData.Length);
writer.Write (compressedData);

uint totalSize = (uint)(compressedData.Length + 8);

entryInfos.Add (new EntryInfo
{
Name = Path.GetFileName (entry.Name),
UnpackedSize = (uint)rawData.Length,
Offset = (uint)currentOffset,
PackedSize = totalSize
});
}
i++;
}

// Write File Table
writer.BaseStream.Position = tableOffset;
foreach (var info in entryInfos)
{
writer.Write (1); // Type

byte[] nameBytes = Encodings.cp932.GetBytes (info.Name);
if (nameBytes.Length > 23) Array.Resize (ref nameBytes, 23);
writer.Write (nameBytes);
for (int k = nameBytes.Length; k < 24; k++) writer.Write ((byte)0);

writer.Write (0xFFFFFFFF); // CRC
writer.Write (info.UnpackedSize);
writer.Write (info.Offset);
writer.Write (info.PackedSize);
}
}
}

struct EntryInfo
{
public string Name;
public uint UnpackedSize;
public uint Offset;
public uint PackedSize;
}
}

[Export(typeof(ScriptFormat))]
Expand Down Expand Up @@ -228,4 +313,168 @@ internal struct EntryDefV2 : IEntryDefinition
public bool IsPacked { get { return _is_packed != 0; } }
}
#pragma warning restore 649,169
}

// --- LEAF LZSS COMPRESSION ---
internal static class LeafLzss
{
const int N = 4096;
const int F = 18;
const int THR = 2;
const int NIL = N;

public static byte[] Compress (byte[] input)
{
if (input.Length == 0) return new byte[0];

using (var outStream = new MemoryStream (input.Length))
{
// Arrays size = N + 257 to handle Root Nodes (N+1..N+256) safely
int[] lson = new int[N + 257];
int[] rson = new int[N + 257];
int[] dad = new int[N + 257];
byte[] text_buf = new byte[N + F - 1];

for (int j = N + 1; j <= N + 256; j++) rson[j] = NIL;
for (int j = 0; j < N; j++) dad[j] = NIL;

int match_position = 0, match_length = 0;

void InsertNode (int r_node)
{
int i, p, cmp;
int key_pos = r_node;

// Root for this character
p = N + 1 + text_buf[key_pos];

rson[r_node] = lson[r_node] = NIL;
match_length = 0;

cmp = 1; // Initial state

for (; ; )
{
if (cmp >= 0)
{
if (rson[p] != NIL) p = rson[p];
else { rson[p] = r_node; dad[r_node] = p; return; }
}
else
{
if (lson[p] != NIL) p = lson[p];
else { lson[p] = r_node; dad[r_node] = p; return; }
}

// Compare bytes only after finding a valid child node 'p' (buffer index)
for (i = 1; i < F; i++)
if ((cmp = text_buf[key_pos + i] - text_buf[p + i]) != 0) break;

if (i > match_length)
{
match_position = p;
match_length = i;
if (match_length >= F) break;
}
}

// Replace node logic
dad[r_node] = dad[p]; lson[r_node] = lson[p]; rson[r_node] = rson[p];
dad[lson[p]] = r_node; dad[rson[p]] = r_node;
if (rson[dad[p]] == p) rson[dad[p]] = r_node;
else lson[dad[p]] = r_node;
dad[p] = NIL;
}

void DeleteNode (int p)
{
int q;
if (dad[p] == NIL) return;
if (rson[p] == NIL) q = lson[p];
else if (lson[p] == NIL) q = rson[p];
else
{
q = lson[p];
if (rson[q] != NIL)
{
do { q = rson[q]; } while (rson[q] != NIL);
rson[dad[q]] = lson[q]; dad[lson[q]] = dad[q];
lson[q] = lson[p]; dad[lson[p]] = q;
}
rson[q] = rson[p]; dad[rson[p]] = q;
}
dad[q] = dad[p];
if (rson[dad[p]] == p) rson[dad[p]] = q;
else lson[dad[p]] = q;
dad[p] = NIL;
}

int code_buf_ptr = 1;
byte[] code_buf = new byte[17];
byte mask = 1;
int s = 0, r = N - F;
int len = 0;

for (int j = 0; j < r; j++) text_buf[j] = 0x20;

int bytes_read = 0;
for (len = 0; len < F && bytes_read < input.Length; len++)
text_buf[r + len] = input[bytes_read++];

if (len == 0) return new byte[0];

for (int j = 1; j <= F; j++) InsertNode (r - j);
InsertNode (r);

do
{
if (match_length > len) match_length = len;
if (match_length <= THR)
{
match_length = 1;
code_buf[0] |= mask;
code_buf[code_buf_ptr++] = text_buf[r];
}
else
{
code_buf[code_buf_ptr++] = (byte)(match_position & 0xFF);
code_buf[code_buf_ptr++] = (byte)(((match_position >> 4) & 0xF0) | (match_length - (THR + 1)));
}

if ((mask <<= 1) == 0)
{
outStream.Write (code_buf, 0, code_buf_ptr);
code_buf[0] = 0; code_buf_ptr = 1; mask = 1;
}

int last_match_length = match_length;

int i;
for (i = 0; i < last_match_length && bytes_read < input.Length; i++)
{
DeleteNode (s);
byte c = input[bytes_read++];
text_buf[s] = c;
if (s < F - 1) text_buf[s + N] = c;
s = (s + 1) & (N - 1);
r = (r + 1) & (N - 1);
InsertNode (r);
}

while (bytes_read == input.Length && i++ < last_match_length)
{
DeleteNode (s);
s = (s + 1) & (N - 1);
r = (r + 1) & (N - 1);
if (--len != 0) InsertNode (r);
}

} while (len > 0);

if (code_buf_ptr > 1)
outStream.Write (code_buf, 0, code_buf_ptr);

return outStream.ToArray();
}
}
}
}