(Legacy): bunch of formats.

(GSX): K5 archives + K4 images.
(HyperWorks): G images.
(IDA): better support packed entries.
(Logg): ARF, MBM archives, FRM images.
(Omi): DAT archives.
(Rare): X archives.
(RHSS): 'CRG' archives.
(SplushWave): better SWG images support.
This commit is contained in:
morkt 2023-09-07 12:47:22 +04:00
parent 8b23273fa9
commit 419a5f4e31
15 changed files with 1564 additions and 107 deletions

View File

@ -26,7 +26,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
// [000225][Light Plan] My Fairink Yousei Byakuya Monogatari // [000225][Light Plan] My Fair Link Yousei Byakuya Monogatari
namespace GameRes.Formats.Gsx namespace GameRes.Formats.Gsx
{ {

70
Legacy/Gsx/ArcK5.cs Normal file
View File

@ -0,0 +1,70 @@
//! \file ArcK5.cs
//! \date 2023 Aug 27
//! \brief GSX engine resource archive.
//
// Copyright (C) 2023 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 System.Text;
namespace GameRes.Formats.Gsx
{
[Export(typeof(ArchiveFormat))]
public class K5Opener : ArchiveFormat
{
public override string Tag => "K5";
public override string Description => "GSX engine resource archive";
public override uint Signature => 0x01354B; // 'K5'
public override bool IsHierarchic => true;
public override bool CanWrite => false;
public K5Opener ()
{
ContainedFormats = new[] { "K4", "OGG" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_offset = file.View.ReadUInt32 (8);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var dir_name = file.View.ReadString (index_offset, 0x80, Encoding.Unicode);
var name = file.View.ReadString (index_offset+0x80, 0x40, Encoding.Unicode);
name = Path.Combine (dir_name, name);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0xC8);
entry.Size = file.View.ReadUInt32 (index_offset+0xCC);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 0x100;
}
return new ArcFile (file, this, dir);
}
}
}

View File

@ -1,8 +1,8 @@
//! \file ImageK4.cs //! \file ImageK4.cs
//! \date 2019 Feb 07 //! \date 2023 Aug 27
//! \brief Toyo GSX image format. //! \brief GSX engine image format.
// //
// Copyright (C) 2019 by morkt // Copyright (C) 2023 by morkt
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to // of this software and associated documentation files (the "Software"), to
@ -23,6 +23,8 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using GameRes.Utility;
using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
@ -33,16 +35,16 @@ namespace GameRes.Formats.Gsx
{ {
internal class K4MetaData : ImageMetaData internal class K4MetaData : ImageMetaData
{ {
public bool HasAlpha; public byte AlphaMode;
public int FrameCount; public int FrameCount;
} }
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
public class K4Format : ImageFormat public class K4Format : ImageFormat
{ {
public override string Tag { get { return "K4"; } } public override string Tag => "K4";
public override string Description { get { return "Toyo GSX image format"; } } public override string Description => "GSX engine image format";
public override uint Signature { get { return 0x0201344B; } } // 'K4' public override uint Signature => 0x0201344B; // 'K4'
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
@ -51,20 +53,22 @@ namespace GameRes.Formats.Gsx
return null; return null;
if (header[2] != 1 || header[3] != 2) if (header[2] != 1 || header[3] != 2)
return null; return null;
int frame_count = header.ToInt16 (0xC);
if (frame_count <= 0)
return null;
return new K4MetaData { return new K4MetaData {
Width = header.ToUInt16 (4), Width = header.ToUInt16 (4),
Height = header.ToUInt16 (6), Height = header.ToUInt16 (6),
BPP = header[0xF], BPP = header[0xF],
HasAlpha = header[0xB] != 0, AlphaMode = header[0xB],
FrameCount = header.ToUInt16 (0xC), FrameCount = frame_count,
}; };
} }
public override ImageData Read (IBinaryStream file, ImageMetaData info) public override ImageData Read (IBinaryStream file, ImageMetaData info)
{ {
var meta = (K4MetaData)info; var reader = new K4Reader (file, (K4MetaData)info);
return reader.Unpack();
return ImageData.Create (info, format, palette, pixels);
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)
@ -72,4 +76,190 @@ namespace GameRes.Formats.Gsx
throw new System.NotImplementedException ("K4Format.Write not implemented"); throw new System.NotImplementedException ("K4Format.Write not implemented");
} }
} }
internal sealed class K4Reader
{
IBinaryStream m_input;
K4MetaData m_info;
public K4Reader (IBinaryStream input, K4MetaData info)
{
m_input = input;
m_info = info;
}
int m_stride;
int m_pixel_size;
public ImageData Unpack ()
{
uint base_offset = 0x30;
m_input.Position = base_offset;
m_info.Width = m_input.ReadUInt16();
m_info.Height = m_input.ReadUInt16();
m_input.Seek (8, SeekOrigin.Current);
int bpp = m_input.ReadUInt16();
int flags = m_input.ReadUInt16();
m_input.Seek (4, SeekOrigin.Current);
uint alpha_pos = m_input.ReadUInt32();
m_input.Seek (12, SeekOrigin.Current);
int ctl_length = m_input.ReadInt32();
m_pixel_size = bpp / 8;
m_stride = (m_info.iWidth * m_pixel_size + 3) & ~3;
var pixels = new byte[m_info.iHeight * m_stride];
int dst = 0;
bool do_delta = (flags & 1) != 0;
var control_bytes = m_input.ReadBytes (ctl_length - 0x10);
using (var mem = new MemoryStream (control_bytes))
using (var ctl = new MsbBitStream (mem))
using (var data = new MsbBitStream (m_input.AsStream, true))
{
while (dst < pixels.Length)
{
int b = ctl.GetNextBit();
if (-1 == b)
break;
if (b != 0)
{
if (!do_delta)
{
pixels[dst++] = (byte)data.GetBits (8);
}
else if (dst >= m_pixel_size)
{
pixels[dst] = (byte)(pixels[dst - m_pixel_size] + data.GetBits (9) + 1);
++dst;
}
else
{
pixels[dst++] = (byte)data.GetBits (9);
}
}
else
{
int pos, count;
if (ctl.GetNextBit() != 0)
{
pos = data.GetBits (14);
count = data.GetBits (4) + 3;
}
else
{
pos = data.GetBits (9);
count = data.GetBits (3) + 2;
}
int src = dst - pos - 1;
count = Math.Min (count, pixels.Length - dst);
if (!do_delta || dst < m_pixel_size)
{
Binary.CopyOverlapped (pixels, src, dst, count);
dst += count;
}
else
{
while (count --> 0)
{
pixels[dst++] = (byte)(pixels[src] + pixels[src + pos - m_pixel_size + 1] - pixels[src - m_pixel_size]);
++src;
}
}
}
}
}
if (0 == alpha_pos)
{
if (24 == bpp)
return ImageData.CreateFlipped (m_info, PixelFormats.Bgr24, null, pixels, m_stride);
else
return ImageData.CreateFlipped (m_info, PixelFormats.Bgr32, null, pixels, m_stride);
}
if (0xFF == m_info.AlphaMode)
pixels = UnpackAlphaFF (alpha_pos + base_offset, pixels);
else if (0xFE == m_info.AlphaMode)
pixels = UnpackAlphaFE (alpha_pos + base_offset, pixels);
else
throw new NotSupportedException (string.Format ("Not supported alpha channel mode 0x{0:X2}", m_info.AlphaMode));
m_stride = m_info.iWidth * 4;
return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, m_stride);
}
byte[] UnpackAlphaFF (uint alpha_pos, byte[] pixels)
{
m_input.Position = alpha_pos;
var offsets = new int[m_info.iHeight];
for (int i = 0; i < offsets.Length; ++i)
offsets[i] = m_input.ReadInt32();
var output = new byte[m_info.iWidth * m_info.iHeight * 4];
int dst = 0;
for (int y = 0; y < m_info.iHeight; y++)
{
m_input.Position = alpha_pos + offsets[y];
int src = (m_info.iHeight - y - 1) * m_stride;
int dst_a = dst + 3;
for (int x = 0; x < m_info.iWidth; ++x)
{
output[dst ] = pixels[src ];
output[dst+1] = pixels[src+1];
output[dst+2] = pixels[src+2];
dst += 4;
src += m_pixel_size;
}
for (int x = 0; x < m_info.iWidth; )
{
byte alpha = m_input.ReadUInt8();
int count = m_input.ReadUInt8();
count = Math.Min (count, m_info.iWidth - x);
x += count;
if (alpha > 0)
{
alpha = (byte)((alpha * 0xFF) >> 7);
while (count --> 0)
{
output[dst_a] = alpha;
dst_a += 4;
}
}
else
{
dst_a += 4 * count;
}
}
}
return output;
}
byte[] UnpackAlphaFE (uint alpha_pos, byte[] pixels)
{
m_input.Position = alpha_pos;
var output = new byte[m_info.iWidth * m_info.iHeight * 4];
int dst = 0;
for (int y = 0; y < m_info.iHeight; y++)
{
int src = (m_info.iHeight - y - 1) * m_stride;
int dst_a = dst + 3;
for (int x = 0; x < m_info.iWidth; ++x)
{
output[dst ] = pixels[src ];
output[dst+1] = pixels[src+1];
output[dst+2] = pixels[src+2];
dst += 4;
src += m_pixel_size;
}
for (int x = 0; x < m_info.iWidth; x += 8)
{
byte alpha = m_input.ReadUInt8();
int count = Math.Min (8, m_info.iWidth - x);
for (int i = 0; i < count; ++i)
{
output[dst_a] = (byte)-(alpha & 1);
dst_a += 4;
alpha >>= 1;
}
}
}
return output;
}
}
} }

421
Legacy/HyperWorks/ImageG.cs Normal file
View File

@ -0,0 +1,421 @@
//! \file ImageG.cs
//! \date 2023 Aug 28
//! \brief HyperWorks image format.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [951207][Love Gun] ACE OF SPADES
// I24 predecessor
namespace GameRes.Formats.HyperWorks
{
[Export(typeof(ImageFormat))]
public class GFormat : ImageFormat
{
public override string Tag => "G";
public override string Description => "HyperWorks indexed image format";
public override uint Signature => 0x1A477D00;
public GFormat ()
{
Signatures = new[] { 0x1A477D00u, 0u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (12);
if (header.ToUInt16 (2) != 0x1A47)
return null;
// not sure if 0x7D00 is a required signature, so rely on filename
if (header.ToUInt16 (0) != 0x7D00 && !file.Name.HasExtension (".G"))
return null;
return new ImageMetaData {
Width = header.ToUInt16 (8),
Height = header.ToUInt16 (10),
OffsetX = header.ToInt16 (4),
OffsetY = header.ToInt16 (6),
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GFormat.Write not implemented");
}
}
internal sealed class GReader
{
IBinaryStream m_input;
ImageMetaData m_info;
int m_stride;
byte[] m_palette_data;
byte[] m_output;
ushort[] m_buffer;
int[] m_line_ptr;
public GReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
m_stride = (m_info.iWidth * m_info.BPP / 8 + 1) & -2;
int s = Math.Max (0x142, m_stride / 2 + 2); // line buffer size
m_buffer = new ushort[s * 3 + 1];
m_line_ptr = new int[3] { 1, 1 + s, 1 + s*2 };
}
public ImageData Unpack ()
{
m_input.Position = 0x0C;
m_palette_data = m_input.ReadBytes (0x30);
m_input.Position = 0x40;
int width = ((m_info.iWidth + 7) & -8);
int rows = ((m_info.iHeight + 1) & -2);
m_output = new byte[m_stride * rows];
InitColorTable();
int blockW = width >> 1;
int blockH = rows >> 1;
SetupBitReader();
for (int y = 0; y < blockH; ++y)
{
var dst = m_line_ptr[2];
m_line_ptr[2] = m_line_ptr[1];
m_line_ptr[1] = m_line_ptr[0];
m_line_ptr[0] = dst;
int x = 0;
while (x < blockW)
{
if (GetNextBit() != 0)
{
m_buffer[dst++] = GetColorFromTable (x);
++x;
}
else
{
int count = ExtractBits (BitTable1);
if (count >= 0x40)
count += ExtractBits (BitTable1);
int idx = ExtractBits (BitTable2) * 2;
int src = m_line_ptr[OffTable[idx + 1]];
src += OffTable[idx] + x;
x += count;
while (count --> 0)
{
m_buffer[dst++] = m_buffer[src++];
}
}
}
UnpackRow (y, blockW, m_line_ptr[0]);
}
var palette = UnpackPalette();
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output, m_stride);
}
void UnpackRow (int y, int width, int buf_pos)
{
int row1 = m_stride * y * 2;
int row2 = row1 + m_stride;
for (int i = 0; i < width; ++i)
{
ushort v = m_buffer[buf_pos++];
ushort v0 = (ushort)((v & 0xF00 | ((v & 0xF000) >> 12)) + 0xA0A);
LittleEndian.Pack (v0, m_output, row2);
row2 += 2;
ushort v1 = (ushort)((((v & 0xF) << 8) | ((v & 0xF0) >> 4)) + 0xA0A);
LittleEndian.Pack (v1, m_output, row1);
row1 += 2;
}
}
byte[] g_palIndexes = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 0xC, 0xD, 0xA, 0xB, 0xE, 0xF };
BitmapPalette UnpackPalette ()
{
var colors = new Color[42];
for (int i = 0; i < 16; ++i)
{
int R = m_palette_data[3 * g_palIndexes[i] ]; R |= R << 4;
int G = m_palette_data[3 * g_palIndexes[i] + 1]; G |= G << 4;
int B = m_palette_data[3 * g_palIndexes[i] + 2]; B |= B << 4;
colors[i+10] = Color.FromRgb ((byte)R, (byte)G, (byte)B);
int b = R & 1;
int c = (sbyte)R >> 1;
if (c < 0)
c += b;
colors[i+26].R = (byte)c;
b = G & 1;
c = (sbyte)G >> 1;
if (c < 0)
c += b;
colors[i+26].G = (byte)c;
b = B & 1;
c = (sbyte)B >> 1;
if (c < 0)
c += b;
colors[i+26].B = (byte)c;
}
return new BitmapPalette (colors);
}
byte[] g_colorTable = new byte[256];
void InitColorTable ()
{
int dst = 0;
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 16; ++j)
g_colorTable[dst++] = (byte)((j + i + 1) & 0xF);
}
ushort GetColorFromTable (int x)
{
ushort b0 = m_buffer[m_line_ptr[1] + x];
int n0 = b0 & 0xF;
int n1 = (b0 >> 4) & 0xF;
int n2 = (b0 >> 8) & 0xF;
int n3 = (b0 >> 12) & 0xF;
ushort b1 = m_buffer[m_line_ptr[1] + x - 1];
int m0 = b1 & 0xF;
int m1 = (b1 >> 4) & 0xF;
int m2 = (b1 >> 8) & 0xF;
int m3 = (b1 >> 12) & 0xF;
ushort b2 = m_buffer[m_line_ptr[0] + x - 1];
int p0 = b2 & 0xF;
int p1 = (b2 >> 4) & 0xF;
int p2 = (b2 >> 8) & 0xF;
int p3 = (b2 >> 12) & 0xF;
int r1 = n1;
if (n1 != n3 && (n1 != m1 || n1 != p1))
{
if (p0 == p1)
r1 = p0;
else
r1 = m2;
}
if (GetNextBit() != 0)
r1 = AdjustColorTable (r1);
int r0 = n0;
if (n0 != n2 && (n0 != m0 || n0 != p0))
{
if (r1 == p1)
r0 = p1;
else
r0 = n3;
}
if (GetNextBit() != 0)
r0 = AdjustColorTable (r0);
int r3 = n3;
if (r1 != n3 && (n3 != m3 || n3 != p3))
{
if (p2 == p3)
r3 = p2;
else
r3 = p0;
}
if (GetNextBit() != 0)
r3 = AdjustColorTable (r3);
int r2 = n2;
if (n2 != r0 && (n2 != m2 || n2 != p2))
{
if (p3 == r3)
r2 = p3;
else
r2 = r1;
}
if (GetNextBit() != 0)
r2 = AdjustColorTable (r2);
return (ushort)((r3 << 12) | (r2 << 8) | (r1 << 4) | r0);
}
byte AdjustColorTable (int idx)
{
int shift_count = ExtractBits (BitTable3);
int i = 16 * idx + shift_count;
byte c = g_colorTable[i];
if (shift_count != 0)
{
while (shift_count --> 0)
{
g_colorTable[i] = g_colorTable[i-1];
--i;
}
g_colorTable[i] = c;
}
return c;
}
int ExtractBits (byte[] table)
{
int idx = ((bits >> 8) & 0xFF) << 1;
int n = table[idx];
if (n != 0)
{
if (n >= bitCount)
{
n -= bitCount;
bits <<= bitCount;
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bitCount = 8;
}
bits <<= n;
bitCount -= n;
return table[idx+1];
}
else
{
bits <<= bitCount;
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bits <<= 8 - bitCount;
int t = table[idx+1];
do
{
int i = GetNextBit();
t = OffTable[2 * t + 54 + i];
}
while (OffTable[2 * t + 54] != 0);
return OffTable[2 * t + 55];
}
}
int bits;
int bitCount;
private void SetupBitReader ()
{
bits = m_input.ReadUInt8() << 8;
bits |= m_input.ReadUInt8();
bitCount = 8;
}
private int GetNextBit ()
{
bits <<= 1;
if (0 == --bitCount)
{
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bitCount = 8;
}
return (bits >> 16) & 1;
}
static readonly byte[] BitTable1 = new byte[] {
3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 8,
0xD, 0, 0, 0, 1, 8, 0xE, 6, 7, 6, 7, 6, 7, 6, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
7, 0xA, 7, 0xA, 0, 2, 0, 7, 7, 0xB, 7, 0xB, 8, 0xF, 0, 3, 6, 8, 6, 8, 6, 8, 6, 8, 8, 0x10, 0, 4,
0, 5, 8, 0x11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 0xC, 7, 0xC, 0, 8, 0, 6, 6, 9, 6,
9, 6, 9, 6, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
static readonly byte[] BitTable2 = new byte[] {
5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7, 0x17, 7, 0x17, 7, 0x16, 7, 0x16, 6, 0x0E, 6,
0x0E, 6, 0x0E, 6, 0x0E, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4,
3, 4, 3, 4, 3, 4, 3, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0C, 6, 0x0C, 6, 0x0C, 6, 0x0C, 5, 6,
5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 7, 0x14, 7,
0x14, 7, 0x18, 7, 0x18, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x11, 6, 0x11, 6, 0x11, 6, 0x11, 6,
0x0F, 6, 0x0F, 6, 0x0F, 6, 0x0F, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5,
0x0A, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1,
6, 0x13, 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x12, 6, 0x12, 6, 0x12, 6, 0x12, 5, 0x0B, 5, 0x0B, 5, 0x0B,
5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 7, 0x19, 7, 0x19, 7, 0x1A, 7, 0x1A, 6, 0x15, 6, 0x15,
6, 0x15, 6, 0x15, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2,
3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
};
static readonly byte[] BitTable3 = new byte[] {
4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5,
4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 7, 8, 8, 0x0A, 8, 0x0B,
4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 6, 7, 6, 7, 6, 7, 7, 9, 7, 9, 0, 0x5F, 8, 0x0C,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
};
static readonly short[] OffTable = new short[] {
0, 1, 1, 1, -1, 0, -1, 1, -4, 0, -2, 0, 2, 1, -2, 1, 4, 1, 0, 2, 1, 2, -1, 2, -8, 0, -4, 1, 8, 1,
-2, 2, 2, 2, -4, 2, 4, 2, 8, 2, -8, 1, -0x10, 0, -8, 2, 0x10, 1, 0x10, 2, -0x10, 1, -0x10, 2,
0x2C, 9, 0x2D, 0x0A, 0x2E, 0x0B, 0x2F, 0x0C, 0x30, 0x0D, 0x31, 0x32, 0x0E, 0x33, 0x0F, 0x10, 0x11,
0x12, 0x13, 0x14, 0x34, 0x15, 0x37, 0x35, 0x16, 0x38, 0x17, 0x39, 0x3D, 0x18, 0x19, 0x36, 0x1A,
0x1B, 0x3A, 0x3C, 0x1C, 0x3B, 0x1D, 0x3E, 0x3F, 0x1E, 0x40, 0x1F, 0x45, 0x46, 0x43, 0x20, 0x21,
0x48, 0x22, 0x41, 0x23, 0x42, 0x24, 0x25, 0x44, 0x47, 0x4F, 0x4A, 0x49, 0x53, 0x4B, 0x4D, 0x57,
0x52, 0x59, 0x58, 0x4E, 0x50, 0x56, 0x26, 0x54, 0x4C, 0x51, 0x55, 0x27, 0x28, 0x5A, 0x2B, 0x29,
0x5B, 0x2A, 0x5C, 0x5D, 0x5E, 0, 0, 0, 0x12, 0, 0x13, 0, 0x14, 0, 0x15, 0, 0x16, 0, 0x17, 0, 0x18,
0, 0x19, 0, 0x1A, 0, 0x1B, 0, 0x1C, 0, 0x1D, 0, 0x1E, 0, 0x1F, 0, 0x20, 0, 0x21, 0, 0x22, 0, 0x23,
0, 0x24, 0, 0x25, 0, 0x26, 0, 0x27, 0, 0x28, 0, 0x29, 0, 0x2A, 0, 0x2B, 0, 0x2C, 0, 0x2D, 0, 0x2E,
0, 0x2F, 0, 0x30, 0, 0x31, 0, 0x32, 0, 0x33, 0, 0x34, 0, 0x35, 0, 0x36, 0, 0x37, 0, 0x38, 0, 0x39,
0, 0x3A, 0, 0x3B, 0, 0x3C, 0, 0x3D, 0, 0x3E, 0, 0x3F, 0, 0x40, 0, 0x80, 0, 0x0C0, 0, 0x100, 0,
0x140, 0x60, 0x61, 0, 0x0D, 0, 0x0E,
};
}
}

View File

@ -23,22 +23,32 @@
// IN THE SOFTWARE. // IN THE SOFTWARE.
// //
using GameRes.Utility;
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media; using System.Windows.Media;
// [970905][Ucom] Winter Kiss
// [980626][Love Gun] ACE OF SPADES 2 // [980626][Love Gun] ACE OF SPADES 2
namespace GameRes.Formats.HyperWorks namespace GameRes.Formats.HyperWorks
{ {
internal class I24MetaData : ImageMetaData
{
public byte Version;
}
[Export(typeof(ImageFormat))] [Export(typeof(ImageFormat))]
public class I24Format : ImageFormat public class I24Format : ImageFormat
{ {
public override string Tag { get { return "I24"; } } public override string Tag => "I24";
public override string Description { get { return "HyperWorks image format"; } } public override string Description => "HyperWorks RGB image format";
public override uint Signature { get { return 0x41343249; } } // 'I24A' public override uint Signature => 0x41343249; // 'I24A'
public I24Format ()
{
Signatures = new[] { 0x41343249u, 0x20343249u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file) public override ImageMetaData ReadMetaData (IBinaryStream file)
{ {
@ -46,16 +56,17 @@ namespace GameRes.Formats.HyperWorks
int bpp = header.ToInt16 (0x10); int bpp = header.ToInt16 (0x10);
if (bpp != 24) if (bpp != 24)
return null; return null;
return new ImageMetaData { return new I24MetaData {
Width = header.ToUInt16 (0xC), Width = header.ToUInt16 (0xC),
Height = header.ToUInt16 (0xE), Height = header.ToUInt16 (0xE),
BPP = bpp, BPP = bpp,
Version = header[3],
}; };
} }
public override ImageData Read (IBinaryStream file, ImageMetaData info) public override ImageData Read (IBinaryStream file, ImageMetaData info)
{ {
var reader = new I24Decoder (file, info); var reader = new I24Decoder (file, (I24MetaData)info);
return reader.Unpack(); return reader.Unpack();
} }
@ -68,9 +79,9 @@ namespace GameRes.Formats.HyperWorks
internal class I24Decoder internal class I24Decoder
{ {
IBinaryStream m_input; IBinaryStream m_input;
ImageMetaData m_info; I24MetaData m_info;
public I24Decoder (IBinaryStream input, ImageMetaData info) public I24Decoder (IBinaryStream input, I24MetaData info)
{ {
m_input = input; m_input = input;
m_info = info; m_info = info;
@ -170,7 +181,9 @@ namespace GameRes.Formats.HyperWorks
short s2 = shift_table[shift_idx + 1]; short s2 = shift_table[shift_idx + 1];
if (shift_token != 0) if (shift_token != 0)
{ {
while (shift_token --> 0) if (m_info.Version == 'A')
{
while (shift_idx > 0)
{ {
shift_table[shift_idx] = shift_table[shift_idx - 2]; shift_table[shift_idx] = shift_table[shift_idx - 2];
shift_table[shift_idx+1] = shift_table[shift_idx - 1]; shift_table[shift_idx+1] = shift_table[shift_idx - 1];
@ -179,6 +192,14 @@ namespace GameRes.Formats.HyperWorks
shift_table[0] = s1; shift_table[0] = s1;
shift_table[1] = s2; shift_table[1] = s2;
} }
else
{
shift_table[shift_idx ] = shift_table[shift_idx - 2];
shift_table[shift_idx + 1] = shift_table[shift_idx - 1];
shift_table[shift_idx - 2] = s1;
shift_table[shift_idx - 1] = s2;
}
}
int src = 4 * (x + s1); int src = 4 * (x + s1);
if (color_token >= 216) if (color_token >= 216)
{ {

View File

@ -30,19 +30,20 @@ using System.IO;
using System.Text; using System.Text;
using GameRes.Compression; using GameRes.Compression;
// [971205][Azlocks] Isle Mystique
// [991001][Inspire] days innocent // [991001][Inspire] days innocent
// [000707][inspire] ambience // [000707][inspire] ambience
namespace GameRes.Formats.Inspire namespace GameRes.Formats.Inspire
{ {
internal class IdaEntry : Entry internal class IdaEntry : PackedEntry
{ {
public uint Flags; public uint Flags;
public uint Key; public uint Key;
} }
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat public class IdaOpener : ArchiveFormat
{ {
public override string Tag { get { return "IDA"; } } public override string Tag { get { return "IDA"; } }
public override string Description { get { return "Inspire resource archive"; } } public override string Description { get { return "Inspire resource archive"; } }
@ -50,6 +51,11 @@ namespace GameRes.Formats.Inspire
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public IdaOpener ()
{
Extensions = new[] { "ida", "mha" };
}
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
int version = file.View.ReadInt32 (4); int version = file.View.ReadInt32 (4);
@ -58,8 +64,9 @@ namespace GameRes.Formats.Inspire
using (var index = file.CreateStream()) using (var index = file.CreateStream())
{ {
var dir = new List<Entry>(); var dir = new List<Entry>();
bool has_packed = false;
long index_pos = 8; long index_pos = 8;
for (;;) do
{ {
index.Position = index_pos; index.Position = index_pos;
uint entry_length = index.ReadUInt32(); uint entry_length = index.ReadUInt32();
@ -76,15 +83,27 @@ namespace GameRes.Formats.Inspire
var entry = FormatCatalog.Instance.Create<IdaEntry> (name); var entry = FormatCatalog.Instance.Create<IdaEntry> (name);
entry.Offset = offset; entry.Offset = offset;
entry.Size = size; entry.Size = entry.UnpackedSize = size;
if (!entry.CheckPlacement (file.MaxOffset)) if (offset > file.MaxOffset || offset < index_pos)
return null; return null;
entry.IsPacked = (flags & 0x14) != 0;
entry.Flags = flags; entry.Flags = flags;
entry.Key = key; entry.Key = key;
has_packed = has_packed || entry.IsPacked;
dir.Add (entry); dir.Add (entry);
} }
while (index_pos < dir[0].Offset);
if (0 == dir.Count) if (0 == dir.Count)
return null; return null;
if (has_packed) // set proper sizes
{
long last_offset = file.MaxOffset;
for (int i = dir.Count - 1; i >= 0; --i)
{
dir[i].Size = (uint)(last_offset - dir[i].Offset);
last_offset = dir[i].Offset;
}
}
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
} }

View File

@ -90,8 +90,11 @@
<Compile Include="Asura\ImageMTG.cs" /> <Compile Include="Asura\ImageMTG.cs" />
<Compile Include="BlackButterfly\ArcDAT.cs" /> <Compile Include="BlackButterfly\ArcDAT.cs" />
<Compile Include="CottonClub\ImageLMG.cs" /> <Compile Include="CottonClub\ImageLMG.cs" />
<Compile Include="Gsx\ArcK5.cs" />
<Compile Include="Gsx\ImageK4.cs" />
<Compile Include="Herb\ArcPAK.cs" /> <Compile Include="Herb\ArcPAK.cs" />
<Compile Include="Herb\ImageGRP.cs" /> <Compile Include="Herb\ImageGRP.cs" />
<Compile Include="HyperWorks\ImageG.cs" />
<Compile Include="James\ImageJMG.cs" /> <Compile Include="James\ImageJMG.cs" />
<Compile Include="BRoom\ArcCPC.cs" /> <Compile Include="BRoom\ArcCPC.cs" />
<Compile Include="BRoom\ArcPK.cs" /> <Compile Include="BRoom\ArcPK.cs" />
@ -125,6 +128,8 @@
<Compile Include="Lazycrew\ImageDAT.cs" /> <Compile Include="Lazycrew\ImageDAT.cs" />
<Compile Include="Liddell\ArcFLK.cs" /> <Compile Include="Liddell\ArcFLK.cs" />
<Compile Include="Liddell\ImageBPA.cs" /> <Compile Include="Liddell\ImageBPA.cs" />
<Compile Include="Logg\ArcARF.cs" />
<Compile Include="Logg\ImageFRM.cs" />
<Compile Include="Melonpan\ArcTTD.cs" /> <Compile Include="Melonpan\ArcTTD.cs" />
<Compile Include="Mermaid\AudioPWV.cs" /> <Compile Include="Mermaid\AudioPWV.cs" />
<Compile Include="Mermaid\ImageGP1.cs" /> <Compile Include="Mermaid\ImageGP1.cs" />
@ -132,6 +137,7 @@
<Compile Include="Mink\ImageFD.cs" /> <Compile Include="Mink\ImageFD.cs" />
<Compile Include="Mmfass\ArcSDA.cs" /> <Compile Include="Mmfass\ArcSDA.cs" />
<Compile Include="Nyoken\ArcZLK.cs" /> <Compile Include="Nyoken\ArcZLK.cs" />
<Compile Include="Omi\ArcDAT.cs" />
<Compile Include="Paprika\ArcPKDAT.cs" /> <Compile Include="Paprika\ArcPKDAT.cs" />
<Compile Include="Paprika\ImageNP.cs" /> <Compile Include="Paprika\ImageNP.cs" />
<Compile Include="PenguinWorks\ArcPAC.cs" /> <Compile Include="PenguinWorks\ArcPAC.cs" />
@ -145,8 +151,10 @@
<Compile Include="PrimeSoft\ImageTHP.cs" /> <Compile Include="PrimeSoft\ImageTHP.cs" />
<Compile Include="ProjectMyu\ImageGAM.cs" /> <Compile Include="ProjectMyu\ImageGAM.cs" />
<Compile Include="Ransel\ArcBCD.cs" /> <Compile Include="Ransel\ArcBCD.cs" />
<Compile Include="Rare\ArcX.cs" />
<Compile Include="Regrips\AudioWRG.cs" /> <Compile Include="Regrips\AudioWRG.cs" />
<Compile Include="Regrips\ImagePRG.cs" /> <Compile Include="Regrips\ImagePRG.cs" />
<Compile Include="Rhss\ArcCRG.cs" />
<Compile Include="Rina\ImageRAD.cs" /> <Compile Include="Rina\ImageRAD.cs" />
<Compile Include="RSystem\ArcRAD.cs" /> <Compile Include="RSystem\ArcRAD.cs" />
<Compile Include="RSystem\ImageRSG.cs" /> <Compile Include="RSystem\ImageRSG.cs" />

141
Legacy/Logg/ArcARF.cs Normal file
View File

@ -0,0 +1,141 @@
//! \file ArcARF.cs
//! \date 2023 Sep 03
//! \brief Logg resource archive.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
// [980417][Logg] Tenshi Kourin
// [980828][Logg] Kazeiro no Romance
namespace GameRes.Formats.Logg
{
[Export(typeof(ArchiveFormat))]
public class ArfOpener : ArchiveFormat
{
public override string Tag => "ARF";
public override string Description => "Logg archive file";
public override uint Signature => 0;
public override bool IsHierarchic => true;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
uint index = 4;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index);
if (offset <= index || offset > file.MaxOffset)
return null;
uint size = file.View.ReadUInt32 (index+4);
byte name_len = file.View.ReadByte (index+8);
var name = file.View.ReadString (index+9, name_len);
var entry = Create<PackedEntry> (name);
entry.Offset = offset;
entry.UnpackedSize = size;
dir.Add (entry);
index += name_len + 9u;
if (index > dir[0].Offset)
return null;
}
long last_offset = file.MaxOffset;
for (int i = count-1; i >= 0; --i)
{
var entry = dir[i] as PackedEntry;
entry.Size = (uint)(last_offset - entry.Offset);
last_offset = entry.Offset;
if (string.IsNullOrEmpty (entry.Name))
dir.RemoveAt (i);
else
entry.IsPacked = entry.Size != entry.UnpackedSize;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
if (!pent.IsPacked)
return base.OpenEntry (arc, entry);
var input = arc.File.CreateStream (pent.Offset, pent.Size);
var output = new byte[pent.UnpackedSize];
Decompress (input, output);
return new BinMemoryStream (output, pent.Name);
}
void Decompress (IBinaryStream input, byte[] output)
{
using (var bits = new LsbBitStream (input.AsStream, true))
{
int dst = 0;
while (dst < output.Length)
{
if (bits.GetNextBit() == 0)
{
output[dst++] = (byte)bits.GetBits (8);
}
else
{
int count;
if (bits.GetNextBit() == 0)
count = 2;
else if (bits.GetNextBit() == 0)
count = 3;
else if (bits.GetNextBit() == 0)
count = 4;
else if (bits.GetNextBit() == 0)
count = 5;
else
{
switch (bits.GetBits (2))
{
case 0: count = 6; break;
case 1: count = bits.GetBits (2) + 7; break;
case 2: count = bits.GetBits (4) + 11; break;
case 3: count = bits.GetBits (10) + 26; break;
default: throw new EndOfStreamException();
}
}
int offset;
if (bits.GetNextBit() == 0)
offset = bits.GetBits (8);
else if (bits.GetNextBit() == 0)
offset = bits.GetBits (10) + 0x100;
else
offset = bits.GetBits (12) + 0x500;
Binary.CopyOverlapped (output, dst - offset - 1, dst, count);
dst += count;
}
}
}
}
}
}

View File

@ -36,11 +36,11 @@ namespace GameRes.Formats.Logg
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
public class MbmOpener : ArchiveFormat public class MbmOpener : ArchiveFormat
{ {
public override string Tag { get { return "MBM"; } } public override string Tag => "MBM";
public override string Description { get { return "Logg Adv engine resource archive"; } } public override string Description => "Logg Adv engine resource archive";
public override uint Signature { get { return 0; } } public override uint Signature => 0;
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic => false;
public override bool CanWrite { get { return false; } } public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
@ -63,19 +63,27 @@ namespace GameRes.Formats.Logg
IDictionary<uint, string> GetArchiveIndex (ArcView file) IDictionary<uint, string> GetArchiveIndex (ArcView file)
{ {
uint last_offset = FileListMap.Value.Keys.Last(); string list_name;
if (!ArcSizeToFileListMap.TryGetValue ((uint)file.MaxOffset, out list_name))
return null;
var file_map = ReadFileList (list_name);
uint last_offset = file_map.Keys.Last();
if (last_offset != file.MaxOffset) if (last_offset != file.MaxOffset)
return null; return null;
return FileListMap.Value; return file_map;
} }
Lazy<IDictionary<uint, string>> FileListMap = new Lazy<IDictionary<uint, string>> (ReadFileList); static readonly Dictionary<uint, string> ArcSizeToFileListMap = new Dictionary<uint, string> {
{ 0x0AB0F5F4, "logg_pl.lst" },
{ 0x0BFFD3DA, "logg_ak.lst" },
{ 0x09809196, "logg_th.lst" },
};
static IDictionary<uint, string> ReadFileList () static IDictionary<uint, string> ReadFileList (string list_name)
{ {
var file_map = new SortedDictionary<uint,string>(); var file_map = new SortedDictionary<uint,string>();
var comma = new char[] {','}; var comma = new char[] {','};
FormatCatalog.Instance.ReadFileList ("logg_pl.lst", line => { FormatCatalog.Instance.ReadFileList (list_name, line => {
var parts = line.Split (comma, 2); var parts = line.Split (comma, 2);
uint offset = uint.Parse (parts[0], NumberStyles.HexNumber); uint offset = uint.Parse (parts[0], NumberStyles.HexNumber);
if (2 == parts.Length) if (2 == parts.Length)

63
Legacy/Logg/ImageFRM.cs Normal file
View File

@ -0,0 +1,63 @@
//! \file ImageFRM.cs
//! \date 2023 Sep 03
//! \brief Logg image file.
//
// Copyright (C) 2023 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
namespace GameRes.Formats.Logg
{
[Export(typeof(ImageFormat))]
public class FrmFormat : ImageFormat
{
public override string Tag { get => "FRM"; }
public override string Description { get => "Logg image format"; }
public override uint Signature { get => 0x4D5246; } // 'FRM'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
return new ImageMetaData {
Width = header.ToUInt32 (4),
Height = header.ToUInt32 (8),
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 0x0C;
int stride = file.ReadInt32();
var palette = ReadPalette (file.AsStream);
var pixels = file.ReadBytes (stride * info.iHeight);
return ImageData.Create (info, PixelFormats.Indexed8, palette, pixels, stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("FrmFormat.Write not implemented");
}
}
}

213
Legacy/Omi/ArcDAT.cs Normal file
View File

@ -0,0 +1,213 @@
//! \file ArcDAT.cs
//! \date 2023 Sep 04
//! \brief OMI Script Engine resource archive.
//
// Copyright (C) 2023 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 GameRes.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
namespace GameRes.Formats.Omi
{
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag => "DAT/OMI";
public override string Description => "OMI Script Engine resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
internal const uint DefaultKey = 7654321u;
public DatOpener ()
{
ContainedFormats = new[] { "BMP", "TGA", "WAV", "TXT" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "scrdat"))
return null;
using (var input = file.CreateStream())
using (var index = new DecryptedStream (input, DefaultKey, 0))
{
var line = index.ReadLine();
int count = int.Parse (line);
if (!IsSaneCount (count))
return null;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = index.ReadLine();
line = index.ReadLine();
uint size = uint.Parse (line);
var entry = Create<PackedEntry> (name);
entry.Size = size;
entry.IsPacked = entry.Type == "image";
dir.Add (entry);
}
long data_pos = index.Position;
for (int i = 0; i < count; ++i)
{
dir[i].Offset = data_pos;
if (!dir[i].CheckPlacement (file.MaxOffset))
return null;
data_pos += dir[i].Size;
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
input = new DecryptedStream (input, DefaultKey, (uint)entry.Offset);
if (!pent.IsPacked)
return input;
using (var packed = new BinaryStream (input, pent.Name))
{
var unpacked = DecompressRle (packed);
if (pent.UnpackedSize == 0)
pent.UnpackedSize = (uint)unpacked.Length;
return new BinMemoryStream (unpacked, pent.Name);
}
}
internal static byte[] DecompressRle (IBinaryStream input)
{
int size = input.ReadInt32();
var output = new byte[size * 2];
ushort rle_marker = input.ReadUInt16();
int dst = 0;
while (dst < output.Length)
{
input.Read (output, dst, 2);
if (output.ToUInt16 (dst) == rle_marker)
{
input.Read (output, dst, 2);
dst += 2;
int count = input.ReadUInt16() - 1;
if (count > 0)
{
count *= 2;
Binary.CopyOverlapped (output, dst-2, dst, count);
dst += count;
}
}
else
{
dst += 2;
}
}
return output;
}
}
internal class DecryptedStream : InputProxyStream
{
private uint m_key;
static readonly Encoding Encoding = Encodings.cp932;
public override bool CanSeek { get => false; }
public override long Position
{
get => BaseStream.Position;
set => throw new NotSupportedException ("Stream.Position property is not supported");
}
public DecryptedStream (Stream stream, uint key, uint start_offset) : base (stream)
{
if (start_offset > 0)
{
do
{
key = 5 * key - 3;
}
while (--start_offset > 0);
}
m_key = key;
}
public override int Read (byte[] buffer, int offset, int count)
{
int read = BaseStream.Read (buffer, offset, count);
Decrypt (buffer, offset, read);
return read;
}
byte[] m_byte_buffer = new byte[1];
public override int ReadByte ()
{
int b = BaseStream.ReadByte();
if (-1 != b)
{
m_byte_buffer[0] = (byte)b;
Decrypt (m_byte_buffer, 0, 1);
b = m_byte_buffer[0];
}
return b;
}
internal void Decrypt (byte[] data, int offset, int count)
{
for (int i = 0; i < count; ++i)
{
data[offset+i] = (byte)(Binary.RotByteR (data[offset+i], 1) - m_key);
m_key = 5 * m_key - 3;
}
}
byte[] m_buffer;
public string ReadLine ()
{
if (null == m_buffer)
m_buffer = new byte[32];
int size = 0;
for (;;)
{
int b = ReadByte();
if (-1 == b || '\n' == b)
break;
if (m_buffer.Length == size)
{
Array.Resize (ref m_buffer, checked(size/2*3));
}
m_buffer[size++] = (byte)b;
}
return Encoding.GetString (m_buffer, 0, size);
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
}
}

132
Legacy/Rare/ArcX.cs Normal file
View File

@ -0,0 +1,132 @@
//! \file ArcX.cs
//! \date 2023 Sep 02
//! \brief Rare archive format.
//
// Copyright (C) 2023 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;
// [990528][Rare] Seisen Ren'ai Yuugi
namespace GameRes.Formats.Rare
{
[Export(typeof(ArchiveFormat))]
public class XOpener : ArchiveFormat
{
public override string Tag => "X/RARE";
public override string Description => "Rare resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
static readonly Dictionary<string, Tuple<uint, int>> KnownExeMap = new Dictionary<string, Tuple<uint, int>> {
{ "seisen.exe", Tuple.Create (0x3A9A0u, 715) },
};
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "PP.X"))
return null;
string full_exe_name = null;
Tuple<uint, int> index_pos = null;
foreach (var exe_name in KnownExeMap.Keys)
{
full_exe_name = VFS.ChangeFileName (file.Name, exe_name);
if (VFS.FileExists (full_exe_name))
{
index_pos = KnownExeMap[exe_name];
break;
}
}
if (null == index_pos)
return null;
uint index_offset = index_pos.Item1;
int count = index_pos.Item2;
using (var index = VFS.OpenView (full_exe_name))
{
index.View.Reserve (index_offset, (uint)count * 12u);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var entry = new PackedEntry {
Name = string.Format ("PP#{0:D5}.BMP", i),
Type = "image",
Offset = index.View.ReadUInt32 (index_offset),
Size = index.View.ReadUInt32 (index_offset+4),
UnpackedSize = index.View.ReadUInt32 (index_offset+8),
IsPacked = true,
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 12;
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = (PackedEntry)entry;
using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
{
var output = new byte[pent.UnpackedSize];
Decompress (input, output);
return new BinMemoryStream (output, entry.Name);
}
}
internal static void Decompress (IBinaryStream input, byte[] output)
{
var frame = new byte[0x400];
int frame_pos = 1;
int dst = 0;
using (var bits = new MsbBitStream (input.AsStream, true))
{
while (dst < output.Length)
{
int ctl = bits.GetNextBit();
if (-1 == ctl)
break;
if (ctl != 0)
{
int v = bits.GetBits (8);
output[dst++] = frame[frame_pos++ & 0x3FF] = (byte)v;
}
else
{
int offset = bits.GetBits (10);
int count = bits.GetBits (5) + 2;
while (count --> 0)
{
byte v = frame[offset++ & 0x3FF];
output[dst++] = frame[frame_pos++ & 0x3FF] = v;
}
}
}
}
}
}
}

83
Legacy/Rhss/ArcCRG.cs Normal file
View File

@ -0,0 +1,83 @@
//! \file ArcCRG.cs
//! \date 2023 Aug 28
//! \brief Raishū Hyōjun Script System resource archive.
//
// Copyright (C) 2023 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 GameRes.Compression;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Rhss
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag => "DAT/CRG";
public override string Description => "RHSS engine resource archive";
public override uint Signature => 0x00475243; // 'CRG'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_pos = 8;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_pos+8, 0x30);
var entry = Create<PackedEntry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos);
entry.Size = file.View.ReadUInt32 (index_pos+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 60;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = entry as PackedEntry;
if (null != pent)
{
if (!pent.IsPacked && arc.File.View.AsciiEqual (entry.Offset, "CMP\0"))
{
pent.IsPacked = true;
pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset + 0x4C);
}
if (pent.IsPacked)
{
Stream input = arc.File.CreateStream (entry.Offset+0x50, entry.Size-0x50);
input = new ZLibStream (input, CompressionMode.Decompress);
return new XoredStream (input, 0xFF);
}
}
return base.OpenEntry (arc, entry);
}
}
}

View File

@ -27,6 +27,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows.Media;
// [030817][Splush Wave] Knock Out -Taisengata Datsui Mahjong-
namespace GameRes.Formats.SplushWave namespace GameRes.Formats.SplushWave
{ {
@ -129,5 +132,92 @@ namespace GameRes.Formats.SplushWave
} }
return new BinMemoryStream (output, 0, dst); return new BinMemoryStream (output, 0, dst);
} }
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var fent = (FlkEntry)entry;
var input = BinaryStream.FromStream (OpenEntry (arc, fent), fent.Name);
if ((fent.Flags & 0x10) == 0)
return ImageFormatDecoder.Create (input);
try
{
var info = Swg.ReadMetaData (input) as SwgMetaData;
if (null == info)
{
input.Position = 0;
return new ImageFormatDecoder(input);
}
return new Swg1ImageDecoder (input, info);
}
catch
{
input.Dispose();
throw;
}
}
static readonly ResourceInstance<SwgFormat> s_swg = new ResourceInstance<SwgFormat> ("SWG");
internal static SwgFormat Swg { get => s_swg.Value; }
}
internal sealed class Swg1ImageDecoder : BinaryImageDecoder
{
SwgMetaData m_info;
public Swg1ImageDecoder (IBinaryStream input, SwgMetaData info) : base (input, info)
{
SourceFormat = DatOpener.Swg;
m_info = info;
}
static readonly byte[] PlaneMap = { 3, 2, 1, 0 };
protected override ImageData GetImageData ()
{
m_input.Position = m_info.DataOffset;
int stride = 4 * m_info.iWidth;
int plane_size = m_info.iWidth * m_info.iHeight;
var output = new byte[stride * m_info.iHeight];
ushort[] ctl_buf = new ushort[m_info.iHeight];
for (int c = 0; c < 4; ++c)
{
int compress_method = ReadU16BE();
if (0 == compress_method)
{
int dst = PlaneMap[c] + stride * (m_info.iHeight - 1);
for (int y = 0; y < m_info.iHeight; ++y)
{
for (int x = 0; x < stride; x += 4)
{
output[dst+x] = m_input.ReadUInt8();
}
dst -= stride;
}
continue;
}
if (compress_method != 1)
throw new InvalidFormatException();
for (int y = 0; y < m_info.iHeight; ++y)
{
ctl_buf[y] = ReadU16BE();
}
int row = PlaneMap[c];
for (int y = 0; y < m_info.iHeight; ++y)
{
int dst = row;
int row_size = ctl_buf[y];
SwgFormat.DecompressRow (m_input, row_size, output, dst, 4);
row += stride;
}
}
return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride);
}
ushort ReadU16BE ()
{
ushort le = m_input.ReadUInt16();
return (ushort)(le >> 8 | le << 8);
}
} }
} }

View File

@ -68,7 +68,7 @@ namespace GameRes.Formats.SplushWave
{ {
var meta = (SwgMetaData)info; var meta = (SwgMetaData)info;
PixelFormat format = meta.BPP == 8 ? PixelFormats.Indexed8 PixelFormat format = meta.BPP == 8 ? PixelFormats.Indexed8
: meta.BPP == 32 ? PixelFormats.Bgr32 : PixelFormats.Bgr24; : meta.BPP == 32 ? PixelFormats.Bgra32 : PixelFormats.Bgr24;
BitmapPalette palette = null; BitmapPalette palette = null;
if (meta.BPP == 8) if (meta.BPP == 8)
{ {
@ -77,36 +77,40 @@ namespace GameRes.Formats.SplushWave
} }
int stride = meta.iWidth * meta.BPP / 8; int stride = meta.iWidth * meta.BPP / 8;
file.Position = meta.DataOffset; file.Position = meta.DataOffset;
// var pixels = new byte[stride * meta.iHeight]; var pixels = new byte[stride * meta.iHeight];
var pixels = new byte[4 * meta.iWidth * meta.iHeight];
if (!meta.IsCompressed) if (!meta.IsCompressed)
{ {
file.Read (pixels, 0, pixels.Length); file.Read (pixels, 0, pixels.Length);
return ImageData.CreateFlipped (meta, format, palette, pixels, stride); return ImageData.CreateFlipped (meta, format, palette, pixels, stride);
} }
var input = file.ReadBytes ((int)(file.Length - file.Position)); if (!Decompress (file, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight))
if (!Decompress (input, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight))
throw new InvalidFormatException ("Invalid SWG file."); throw new InvalidFormatException ("Invalid SWG file.");
return ImageData.CreateFlipped (meta, format, palette, pixels, stride); return ImageData.CreateFlipped (meta, format, palette, pixels, stride);
} }
bool Decompress (byte[] input, byte[] output, int channels, int width, int height) static readonly byte[] PlaneMap = { 2, 1, 0, 3 };
bool Decompress (IBinaryStream input, byte[] output, int channels, int width, int height)
{ {
int src = 0; long start_pos = input.Position;
if (input[0] != 0 || input[1] != 1) byte hi = input.ReadUInt8();
byte lo = input.ReadUInt8();
if (hi != 0 || lo != 1)
{ {
input.Position = start_pos;
int n = 0; int n = 0;
for (int i = 0; i < channels; ++i) for (int i = 0; i < channels; ++i)
{ {
if (0 == input[i]) if (0 == input.ReadByte())
++n; ++n;
} }
if (n != channels) if (n != channels)
return false; return false;
src = 4; input.Position = start_pos + 4;
hi = input.ReadUInt8();
lo = input.ReadUInt8();
} }
int compress_method = input[src+1] + (input[src] << 8); int compress_method = lo | hi << 8;
src += 2;
if (0 == compress_method) if (0 == compress_method)
{ {
for (int i = 0; i < channels; ++i) for (int i = 0; i < channels; ++i)
@ -115,7 +119,7 @@ namespace GameRes.Formats.SplushWave
int count = height * width; int count = height * width;
while (count --> 0) while (count --> 0)
{ {
output[pos] = input[src++]; output[pos] = input.ReadUInt8();
pos += channels; pos += channels;
} }
} }
@ -123,61 +127,55 @@ namespace GameRes.Formats.SplushWave
} }
if (compress_method != 1) if (compress_method != 1)
return false; return false;
int dst = 0; int stride = width * channels;
int v33 = src; var row_sizes = input.ReadBytes (2 * height * channels);
int v37 = height * channels; int ctl_pos = 0;
src += 2 * v37; for (int c = 0; c < channels; ++c)
for (int row = 0; row < v37; ++row) for (int y = height - 1; y >= 0; --y)
{ {
int y = row % height; int dst = stride * y + PlaneMap[c];
dst = channels * (width * (height - y - 1) + 1) - row / height - 1; int row_size = row_sizes[ctl_pos+1] | row_sizes[ctl_pos] << 8;
if (dst > output.Length) ctl_pos += 2;
DecompressRow (input, row_size, output, dst, channels);
}
return true; return true;
int v24 = 0; }
int v36 = input[v33+1] + (input[v33] << 8);
v33 += 2; internal static void DecompressRow (IBinaryStream input, int row_size, byte[] output, int dst, int step)
do
{ {
byte lo = input[src]; int x = 0;
byte hi = input[src+1]; while (x < row_size)
if (lo != 0)
{ {
if (lo < 0x81) byte ctl = input.ReadUInt8();
if (ctl == 0)
{ {
++src; byte v = input.ReadUInt8();
int count = lo + 1; x += 2;
v24 += count + 1; output[dst] = v;
dst += step;
}
else if (ctl < 0x81u)
{
int count = ctl + 1;
x += count + 1;
while (count --> 0) while (count --> 0)
{ {
output[dst] = input[src++]; output[dst] = input.ReadUInt8();
dst += channels; dst += step;
} }
} }
else else
{ {
src += 2; byte v = input.ReadUInt8();
v24 += 2; x += 2;
int count = Math.Min (0x101 - lo, output.Length - dst); int count = 0x101 - ctl;
while (count --> 0) while (count --> 0)
{ {
output[dst] = hi; output[dst] = v;
dst += channels; dst += step;
} }
} }
} }
else
{
src += 2;
v24 += 2;
output[dst] = hi;
dst += channels;
}
if (dst >= output.Length)
return true;
}
while (v24 < v36);
}
return true;
} }
public override void Write (Stream file, ImageData image) public override void Write (Stream file, ImageData image)