This commit is contained in:
Crsky 2023-10-28 18:21:20 +08:00
commit 92debb1175
90 changed files with 10589 additions and 792 deletions

View File

@ -118,6 +118,12 @@ namespace GameRes.Formats.Ankh
entry.UnpackedSize = header.ToUInt32 (0);
entry.IsPacked = true;
}
else if (header.AsciiEqual (0, "zfd "))
{
entry.ChangeType (ImageFormat.Tga);
entry.UnpackedSize = header.ToUInt32 (4);
entry.IsPacked = true;
}
else if (header.AsciiEqual (4, "OggS"))
{
entry.ChangeType (OggAudio.Instance);
@ -177,6 +183,8 @@ namespace GameRes.Formats.Ankh
return OpenTpw (arc, pent);
if (arc.File.View.AsciiEqual (entry.Offset+4, "HDJ\0"))
return OpenImage (arc, pent);
if (arc.File.View.AsciiEqual (entry.Offset, "zfd "))
return OpenZfd (arc, pent);
if (entry.Size > 12)
{
byte type = arc.File.View.ReadByte (entry.Offset+4);
@ -243,6 +251,12 @@ namespace GameRes.Formats.Ankh
}
}
Stream OpenZfd (ArcFile arc, PackedEntry entry)
{
var input = arc.File.CreateStream (entry.Offset+8, entry.Size-8);
return new ZLibStream (input, CompressionMode.Decompress);
}
internal static void UnpackTpw (IBinaryStream input, byte[] output)
{
input.Position = 8;

View File

@ -123,6 +123,8 @@
<Compile Include="AdvSys\ImageGWD.cs" />
<Compile Include="Ail\ArcLNK2.cs" />
<Compile Include="AIRNovel\ArcAIR.cs" />
<Compile Include="CsWare\AudioWAV.cs" />
<Compile Include="CsWare\ImageGDT.cs" />
<Compile Include="FC01\ArcBDT.cs" />
<Compile Include="FC01\BdtTables.cs" />
<Compile Include="GScripter\ArcDATA.cs" />
@ -170,9 +172,13 @@
<Compile Include="NScripter\Script.cs" />
<Compile Include="Psp\ArcQPK.cs" />
<Compile Include="ScrPlayer\ImageIMG.cs" />
<Compile Include="SingleFileArchive.cs" />
<Compile Include="Software House Parsley\ArcCG3.cs" />
<Compile Include="Software House Parsley\ArcScn.cs" />
<Compile Include="TechGian\ArcBIN.cs" />
<Compile Include="Unity\ScriptDSM.cs" />
<Compile Include="Xuse\ArcNT.cs" />
<Compile Include="Xuse\ArcWVB.cs" />
<Compile Include="Zyx\ImageXMG.cs" />
<Compile Include="AudioAIFF.cs" />
<Compile Include="Basil\ArcMIF.cs" />
@ -751,7 +757,6 @@
<Compile Include="AZSys\ArcAZSys.cs" />
<Compile Include="Ethornell\ArcBGI.cs" />
<Compile Include="Ffa\ArcBlackPackage.cs" />
<None Include="Macromedia\ArcCCT.cs" />
<Compile Include="Cherry\ArcCherry.cs" />
<Compile Include="Circus\ArcCircus.cs" />
<Compile Include="ArcCommon.cs" />

View File

@ -0,0 +1,92 @@
//! \file AudioWAV.cs
//! \date 2023 Oct 26
//! \brief Obscure C's ware WAVE file encoding.
//
// 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;
// [960405][C's Ware] GLO-RI-A ~Kindan no Ketsuzoku~
namespace GameRes.Formats.CsWare
{
[Export(typeof(AudioFormat))]
[ExportMetadata("Priority", 1)] // should be tried before generic WAVE format
public class WavAudio : AudioFormat
{
public override string Tag => "WAV/CSWARE";
public override string Description => "C's ware encoded audio";
public override uint Signature => 0x46464952; // 'RIFF'
public override bool CanWrite => false;
public override SoundInput TryOpen (IBinaryStream file)
{
var header = file.ReadHeader (0x2E);
if (header[0x14] != 1 || header[0x15] != 0xFF
|| !header.AsciiEqual (8, "WAVEfmt ")
|| !header.AsciiEqual (0x26, "data"))
return null;
var format = new WaveFormat {
FormatTag = 1,
Channels = header.ToUInt16 (0x16),
SamplesPerSecond = header.ToUInt32 (0x18),
AverageBytesPerSecond = header.ToUInt32 (0x1C) * 2,
BlockAlign = (ushort)(header.ToUInt16 (0x20) * 2),
BitsPerSample = 16,
};
uint input_size = header.ToUInt32 (0x2A);
var samples = new byte[input_size * 2];
Decode (file, samples);
var decoded = new BinMemoryStream (samples, file.Name);
file.Dispose();
return new RawPcmInput (decoded, format);
}
void Decode (IBinaryStream input, byte[] output)
{
int dst = 0;
while (input.PeekByte() != -1)
{
sbyte sample = input.ReadInt8();
LittleEndian.Pack (SampleMap[sample + 128], output, dst);
dst += 2;
}
}
static readonly short[] SampleMap = InitSampleMap();
static short[] InitSampleMap ()
{
var map = new short[256];
for (int i = 1; i <= 127; ++i)
{
map[128 + i] = (short)(Math.Pow (10.0, ((double)i + 44.8637) / 38.0597) - 14.5342);
map[128 - i] = (short)-map[i + 128];
}
map[0] = -0x8000;
return map;
}
}
}

View File

@ -0,0 +1,569 @@
//! \file ImageGDT.cs
//! \date 2023 Sep 29
//! \brief AGS engine image format (PC-98).
//
// 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;
namespace GameRes.Formats.CsWare
{
internal class GdtMetaData : ImageMetaData
{
public byte Flags;
public bool HasPalette => (Flags & 0x80) != 0;
public bool IsDouble => (Flags & 0x40) == 0;
}
[Export(typeof(ImageFormat))]
public class GdtFormat : ImageFormat
{
public override string Tag => "GDT";
public override string Description => "AGS engine image format";
public override uint Signature => 0x314144; // 'DA1'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (16);
var info = new GdtMetaData {
OffsetX = header[8] << 3,
OffsetY = header.ToUInt16 (0xA),
Width = (uint)header[9] << 3,
Height = header.ToUInt16 (0xC),
BPP = 4,
Flags = header[0xF],
};
return info;
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GdtReader (file, (GdtMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GdtFormat.Write not implemented");
}
}
internal class GdtReader
{
IBinaryStream m_input;
GdtMetaData m_info;
int m_stride;
int m_output_stride;
public BitmapPalette Palette { get; private set; }
public GdtReader (IBinaryStream file, GdtMetaData info)
{
m_input = file;
m_info = info;
m_stride = info.iWidth >> 3;
m_output_stride = info.iWidth >> 1;
}
byte[][] m_planes;
public ImageData Unpack ()
{
m_input.Position = 0x10;
if (m_info.HasPalette)
{
Palette = ReadPalette();
}
var packed_sizes = new ushort[4];
for (int i = 0; i < 4; ++i)
packed_sizes[i] = m_input.ReadUInt16();
long plane_pos = m_input.Position;
int plane_size = m_stride * m_info.iHeight;
m_planes = new byte[][] {
new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size],
};
Action<int> UnpackPlane = UnpackSingle;
if (m_info.IsDouble)
UnpackPlane = UnpackDouble;
for (int i = 0; i < 4; ++i)
{
m_input.Position = plane_pos;
plane_pos += packed_sizes[i];
UnpackPlane (i);
}
var pixels = new byte[m_output_stride * m_info.iHeight];
FlattenPlanes (pixels);
PixelFormat format;
if (null == Palette)
format = PixelFormats.Gray4;
else
format = PixelFormats.Indexed4;
return ImageData.Create (m_info, format, Palette, pixels, m_output_stride);
}
void UnpackSingle (int plane_index)
{
int h = m_info.iHeight;
int w = m_stride;
int dst = 0;
while (w --> 0)
{
Unpack8Line (plane_index, dst);
dst += h;
}
}
void UnpackDouble (int plane_index)
{
var output = m_planes[plane_index];
int h = m_info.iHeight;
int width = m_stride;
int dst = 0;
if ((m_info.OffsetX & 8) != 0)
{
Unpack8Line (plane_index, dst);
--width;
dst += h;
}
if ((m_input.Position & 1) != 0)
m_input.Seek (1, SeekOrigin.Current);
if (1 == width)
{
Unpack8Line (plane_index, dst);
return;
}
while (m_input.PeekByte() != -1)
{
byte op = m_input.ReadUInt8();
byte ctl = m_input.ReadUInt8();
if (ctl < 0x80)
{
if (0 == ctl)
continue;
ushort w = (ushort)(((op & 0xF) << 8 | (op & 0xF0) >> 4) * 0x11);
int count = ctl;
Fill (output, dst , count, w);
Fill (output, dst+h, count, w);
dst += count * 2;
}
else if (ctl < 0xC0)
{
int count = ctl & 0x3F;
int w = op & 0xF | (op & 0xF0) << 4;
uint d = (uint)(w | (w & 0x0303) << 18 | (w & 0x0C0C) << 14);
d = Binary.BigEndian (d | d << 4);
Fill (output, dst , count, d);
Fill (output, dst+h, count, d);
dst += count * 4;
}
else if (ctl < 0xD0)
{
byte b = (byte)((ctl & 0xF) | ctl << 4);
int count = op;
Fill (output, dst , count, b);
Fill (output, dst+h, count, b);
dst += count;
}
else if (ctl < 0xD2)
{
int count = (ctl & 1) << 8 | op;
while (count --> 0)
{
output[dst ] = m_input.ReadUInt8();
output[dst+h] = m_input.ReadUInt8();
}
}
else if (0xD2 == ctl)
{
dst += op;
}
else if (ctl < 0xF3)
{
int count = op;
int off = 0;
switch (ctl)
{
case 0xD3: off = 16; break;
case 0xD4: off = 12; break;
case 0xD5: off = 8; break;
case 0xD6: off = 4; break;
case 0xD7: off = 2; break;
case 0xD8: off = 1; break;
case 0xD9: off = h * 2 + 8; break;
case 0xDA: off = h * 2 + 4; break;
case 0xDB: off = h * 2 + 2; break;
case 0xDC: off = h * 2 + 1; break;
case 0xDD: off = h * 2; break;
case 0xDE: off = h * 2 - 1; break;
case 0xDF: off = h * 2 - 2; break;
case 0xE0: off = h * 2 - 4; break;
case 0xE1: off = h * 2 - 8; break;
case 0xE2: off = h * 4 + 8; break;
case 0xE3: off = h * 4 + 4; break;
case 0xE4: off = h * 4 + 2; break;
case 0xE5: off = h * 4 + 1; break;
case 0xE6: off = h * 4; break;
case 0xE7: off = h * 4 - 1; break;
case 0xE8: off = h * 4 - 2; break;
case 0xE9: off = h * 4 - 4; break;
case 0xEA: off = h * 4 - 8; break;
case 0xEB: off = h * 6 + 4; break;
case 0xEC: off = h * 6 + 2; break;
case 0xED: off = h * 6 + 1; break;
case 0xEE: off = h * 6; break;
case 0xEF: off = h * 6 - 1; break;
case 0xF0: off = h * 6 - 2; break;
case 0xF1: off = h * 6 - 4; break;
case 0xF2: off = h * 8; break;
}
Binary.CopyOverlapped (output, dst-off, dst, count);
Binary.CopyOverlapped (output, dst-off+h, dst+h, count);
dst += count;
}
else if (ctl < 0xFC)
{
int count = op;
var source = m_planes[(ctl - 0xF3) % 3];
if (ctl < 0xF6)
{
Buffer.BlockCopy (source, dst, output, dst, count);
Buffer.BlockCopy (source, dst+h, output, dst+h, count);
dst += count;
}
else if (ctl > 0xF8)
{
var source1 = m_planes[ctl & 1];
var source2 = m_planes[ctl & 2];
while (count --> 0)
{
output[dst] = (byte)(source1[dst] & source2[dst]);
output[dst+h] = (byte)(source1[dst+h] & source2[dst+h]);
++dst;
}
}
else
{
while (count --> 0)
{
output[dst] = (byte)~source[dst];
output[dst+h] = (byte)~source[dst+h];
++dst;
}
}
}
else if (0xFC == ctl)
{
int count = op;
byte b = m_input.ReadUInt8();
Fill (output, dst , count, b);
Fill (output, dst+h, count, b);
dst += count;
}
else if (0xFD == ctl)
{
if (op < 0x80)
{
int count = op;
ushort w = m_input.ReadUInt16();
Fill (output, dst , count, w);
Fill (output, dst+h, count, w);
dst += count * 2;
}
else
{
int count = op & 0x7F;
ushort w1 = m_input.ReadUInt16();
ushort w2 = m_input.ReadUInt16();
Fill (output, dst , count, (ushort)(w1 << 8 | w2 & 0xFF));
Fill (output, dst+h, count, (ushort)(w1 & 0xFF | w2 >> 8));
dst += count * 2;
}
}
else if (0xFE == ctl)
{
int count = op & 0x3F;
if (op < 0x80)
{
byte b0 = m_input.ReadUInt8();
byte b1 = m_input.ReadUInt8();
int d = b1 | b0 << 16;
d = d & 0x0F000F | (d & 0xF000F0) << 4;
d *= 0x11;
d = Binary.BigEndian (d);
Fill (output, dst , count, (uint)d);
Fill (output, dst+h, count, (uint)d);
}
else
{
uint d0 = m_input.ReadUInt32();
uint d1 = m_input.ReadUInt32();
uint p0 = d0 << 24 | d0 & 0xFF0000 | (d1 & 0xFF) << 8 | (d1 & 0xFF0000) >> 16;
uint p1 = (d0 & 0xFF00) << 16 | (d0 & 0xFF000000) >> 8 | d1 & 0xFF00 | (d1 & 0xFF000000) >> 24;
Fill (output, dst , count, p0);
Fill (output, dst+h, count, p1);
}
dst += count * 4;
}
else // 0xFF
{
dst += h;
width -= 2;
if (0 == width)
break;
if (1 == width)
{
Unpack8Line (plane_index, dst);
break;
}
}
}
}
void Unpack8Line (int plane_index, int dst)
{
var output = m_planes[plane_index];
int h = m_info.iHeight;
int end_pos = dst + h;
while (m_input.PeekByte() != -1)
{
byte ctl = m_input.ReadUInt8();
if (ctl < 0x40)
{
byte b = 0;
if (ctl >= 0x20)
b = 0xFF;
int count = ctl & 0x1F;
if (0 == count)
count = m_input.ReadUInt8();
Fill (output, dst, count, b);
dst += count;
}
else if (ctl < 0xA0)
{
int count = ctl & 0x1F;
if (0 == count)
count = m_input.ReadUInt8();
int src_plane = (ctl - 0x40) >> 5;
Buffer.BlockCopy (m_planes[src_plane], dst, output, dst, count);
dst += count;
}
else if (ctl < 0xF0)
{
int count = ctl & 0xF;
if (0 == count)
count = m_input.ReadUInt8();
switch (ctl & 0xF0)
{
case 0xA0: Binary.CopyOverlapped (output, dst-16, dst, count); break;
case 0xB0: Binary.CopyOverlapped (output, dst-8, dst, count); break;
case 0xC0: Binary.CopyOverlapped (output, dst-4, dst, count); break;
case 0xD0: Binary.CopyOverlapped (output, dst-2, dst, count); break;
case 0xE0: Binary.CopyOverlapped (output, dst-h*2, dst, count); break;
}
dst += count;
}
else if (ctl < 0xF9)
{
int count = ctl & 0xF;
if (0 == count)
count = m_input.ReadUInt8();
m_input.Read (output, dst, count);
dst += count;
}
else if (0xF9 == ctl)
{
dst += m_input.ReadUInt8();
}
else if (0xFA == ctl)
{
int count = m_input.ReadUInt8();
byte b = m_input.ReadUInt8();
Fill (output, dst, count, b);
dst += count;
}
else if (0xFB == ctl)
{
int count = m_input.ReadUInt8();
int b = count >> 7;
count &= 0x7F;
while (count --> 0)
{
output[dst] = (byte)~m_planes[b][dst];
++dst;
}
}
else if (0xFC == ctl)
{
int count = m_input.ReadUInt8();
if ((count & 0x80) != 0)
{
count &= 0x7F;
byte b = m_input.ReadUInt8();
ushort d = (ushort)(b & 0xF | (b & 0xF0) << 4);
d |= (ushort)(d << 4);
Fill (output, dst, count, d);
dst += count * 2;
}
else
{
while (count --> 0)
{
output[dst] = (byte)~m_planes[2][dst];
++dst;
}
}
}
else if (0xFD == ctl)
{
int count = m_input.ReadUInt8();
if ((count & 0x80) != 0)
{
byte b = m_input.ReadUInt8();
uint d = (uint)(b & 0xF | b << 4 | (b & 0xF0) << 8);
if ((count & 0x40) != 0)
{
b = m_input.ReadUInt8();
d |= (uint)((b & 0xF) << 16 | b << 20 | (b & 0xF0) << 24);
}
else
{
d |= (d & 0x3F3F) << 18 | (d & 0xC0C0) << 10;
}
count &= 0x3F;
Fill (output, dst, count, d);
dst += count * 4;
}
else
{
ushort w = m_input.ReadUInt16();
Fill (output, dst, count, w);
dst += count * 2;
}
}
else if (0xFE == ctl)
{
int count = m_input.ReadUInt8();
int b = count & 0xC0;
if (b != 0)
{
count &= 0x3F;
b >>= 6;
while (count --> 0)
{
output[dst] = (byte)(m_planes[b & 1][dst] & m_planes[b & 2][dst]);
++dst;
}
}
else
{
uint u = m_input.ReadUInt32();
Fill (output, dst, count, u);
dst += count * 4;
}
}
else // 0xFF
{
break;
}
}
}
void FlattenPlanes (byte[] output)
{
int plane_size = m_planes[0].Length;
int src = 0;
for (int x = 0; x < m_output_stride; x += 4)
{
int dst = x;
for (int y = 0; y < m_info.iHeight; ++y)
{
byte b0 = m_planes[0][src];
byte b1 = m_planes[1][src];
byte b2 = m_planes[2][src];
byte b3 = m_planes[3][src];
++src;
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) >> 0));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
output[dst+j/2] = px;
}
dst += m_output_stride;
}
}
}
static void Fill (byte[] output, int dst, int count, byte pixel)
{
while (count --> 0)
{
output[dst++] = pixel;
}
}
static void Fill (byte[] output, int dst, int count, ushort pixel)
{
count <<= 1;
for (int i = 0; i < count; i += 2)
{
LittleEndian.Pack (pixel, output, dst+i);
}
}
static void Fill (byte[] output, int dst, int count, uint pixel)
{
count <<= 2;
for (int i = 0; i < count; i += 4)
{
LittleEndian.Pack (pixel, output, dst+i);
}
}
BitmapPalette ReadPalette ()
{
using (var bits = new MsbBitStream (m_input.AsStream, true))
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
int b = bits.GetBits (4) * 0x11;
int r = bits.GetBits (4) * 0x11;
int g = bits.GetBits (4) * 0x11;
colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
}
}
}

View File

@ -55,7 +55,7 @@ namespace GameRes.Formats.TinkerBell
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0x18);
entry.Size = file.View.ReadUInt32 (index_offset+0x10);
if (!entry.CheckPlacement (file.MaxOffset))
if (entry.Offset <= index_offset || !entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}

View File

@ -40,6 +40,7 @@ namespace GameRes.Formats
Section m_overlay;
uint m_image_base = 0;
List<ImageSection> m_section_list;
bool? m_is_NE;
public ExeFile (ArcView file)
{
@ -56,9 +57,18 @@ namespace GameRes.Formats
/// </summary>
public Section Whole { get; private set; }
public bool IsWin16 => m_is_NE ?? (m_is_NE = IsNe()).Value;
private bool IsNe ()
{
uint ne_offset = View.ReadUInt32 (0x3C);
return ne_offset < m_file.MaxOffset-2 && View.AsciiEqual (ne_offset, "NE");
}
/// <summary>
/// Dictionary of executable file sections.
/// </summary>
///
public IReadOnlyDictionary<string, Section> Sections
{
get
@ -255,6 +265,11 @@ namespace GameRes.Formats
private void InitSectionTable ()
{
if (IsWin16)
{
InitNe();
return;
}
long pe_offset = GetHeaderOffset();
int opt_header = View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader
long section_table = pe_offset+opt_header+0x18;
@ -294,6 +309,26 @@ namespace GameRes.Formats
m_section_list = list;
}
void InitNe ()
{
uint ne_offset = m_file.View.ReadUInt32 (0x3C);
int segment_count = m_file.View.ReadUInt16 (ne_offset + 0x1C);
uint seg_table = m_file.View.ReadUInt16 (ne_offset + 0x22) + ne_offset;
int shift = m_file.View.ReadUInt16 (ne_offset + 0x32);
uint last_seg_end = 0;
for (int i = 0; i < segment_count; ++i)
{
uint offset = (uint)m_file.View.ReadUInt16 (seg_table) << shift;
uint size = m_file.View.ReadUInt16 (seg_table+2);
if (offset + size > last_seg_end)
last_seg_end = offset + size;
}
m_overlay.Offset = last_seg_end;
m_overlay.Size = (uint)(m_file.MaxOffset - last_seg_end);
m_section_table = new Dictionary<string, Section>(); // these are empty for 16-bit executables
m_section_list = new List<ImageSection>(); //
}
/// <summary>
/// Helper class for executable file resources access.
/// </summary>

View File

@ -38,23 +38,30 @@ namespace GameRes.Formats
public MbImageFormat ()
{
Extensions = new[] { "bmp", "gra" };
Extensions = new[] { "bmp", "gra", "xxx" };
}
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
int c1 = stream.ReadByte();
int c2 = stream.ReadByte();
// MB/MC/MK/CL/XX
switch (c1)
{
case 'M':
if ('B' != c2 && 'C' != c2)
if ('B' != c2 && 'C' != c2 && 'K' != c2)
return null;
break;
case 'C':
if ('L' != c2)
return null;
break;
case 'X':
if ('X' != c2)
return null;
break;
default:
return null;
}
using (var bmp = OpenAsBitmap (stream))
return Bmp.ReadMetaData (bmp);

View File

@ -89,8 +89,8 @@ namespace GameRes.Formats.KiriKiri
public Xp3Opener ()
{
Signatures = new uint[] { 0x0d335058, 0 };
Extensions = new[] { "XP3", "EXE" };
Signatures = new uint[] { 0x0d335058, 0x00905A4D, 0 };
Extensions = new[] { "xp3", "exe" };
ContainedFormats = new[] { "TLG", "BMP", "PNG", "JPEG", "OGG", "WAV", "TXT" };
}

View File

@ -60,6 +60,7 @@ namespace GameRes.Formats.Lilim
if (!name_buf.SequenceEqual (IndexLink) && !name_buf.SequenceEqual (IndexEnd))
return null;
string last_name = null;
long current_offset = 0;
var dir = new List<Entry> (0x3E);
while (current_offset < file.MaxOffset)
@ -79,6 +80,9 @@ namespace GameRes.Formats.Lilim
if (-1 == name_length)
name_length = name_buf.Length;
var name = Encodings.cp932.GetString (name_buf, 0, name_length);
if (last_name == name || string.IsNullOrWhiteSpace (name))
return null;
last_name = name;
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = file.View.ReadUInt32 (current_offset+0x10);
entry.Size = file.View.ReadUInt32 (current_offset+0x14);

View File

@ -45,7 +45,7 @@ namespace GameRes.Formats.LiveMaker
public VffOpener ()
{
Extensions = new string[] { "dat", "exe" };
Signatures = new uint[] { 0x666676, 0 };
Signatures = new uint[] { 0x666676, 0x00905A4D, 0 };
}
public override ArcFile TryOpen (ArcView file)
@ -66,6 +66,8 @@ namespace GameRes.Formats.LiveMaker
&& (0x5A4D == (signature & 0xFFFF))) // 'MZ'
{
base_offset = SkipExeData (index_file);
if (base_offset >= file.MaxOffset)
return null;
signature = index_file.View.ReadUInt32 (base_offset);
}
else if (!file.Name.HasExtension (".dat"))

View File

@ -50,7 +50,7 @@ namespace GameRes.Formats.MAI
public CmFormat ()
{
Extensions = new string[] { "cm" };
Extensions = new string[] { "cmp" };
}
public override void Write (Stream file, ImageData image)
@ -60,24 +60,22 @@ namespace GameRes.Formats.MAI
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
if ('C' != stream.ReadByte() || 'M' != stream.ReadByte())
var header = stream.ReadHeader (0x20);
if ('C' != header[0] || 'M' != header[1])
return null;
var header = stream.ReadBytes (0x1e);
if (header.Length != 0x1e)
if (1 != header[0x0E])
return null;
if (1 != header[0x0c])
return null;
uint size = LittleEndian.ToUInt32 (header, 0);
uint size = LittleEndian.ToUInt32 (header, 2);
if (size != stream.Length)
return null;
var info = new CmMetaData();
info.Width = LittleEndian.ToUInt16 (header, 4);
info.Height = LittleEndian.ToUInt16 (header, 6);
info.Colors = LittleEndian.ToUInt16 (header, 8);
info.BPP = header[0x0a];
info.IsCompressed = 0 != header[0x0b];
info.DataOffset = LittleEndian.ToUInt32 (header, 0x0e);
info.DataLength = LittleEndian.ToUInt32 (header, 0x12);
info.Width = LittleEndian.ToUInt16 (header, 6);
info.Height = LittleEndian.ToUInt16 (header, 8);
info.Colors = LittleEndian.ToUInt16 (header, 0x0A);
info.BPP = header[0x0C];
info.IsCompressed = 0 != header[0x0D];
info.DataOffset = LittleEndian.ToUInt32 (header, 0x10);
info.DataLength = LittleEndian.ToUInt32 (header, 0x14);
if (info.DataLength > size)
return null;
return info;
@ -158,7 +156,7 @@ namespace GameRes.Formats.MAI
public AmFormat ()
{
Extensions = new string[] { "am", "ami" };
Extensions = new string[] { "amp", "ami" };
}
public override void Write (Stream file, ImageData image)
@ -235,6 +233,8 @@ namespace GameRes.Formats.MAI
m_pixels = new byte[m_width*m_height*4];
}
static readonly Color Default8bppTransparencyColor = Color.FromRgb (0, 0xFE, 0);
public void Unpack ()
{
if (m_info.Colors > 0)
@ -262,13 +262,23 @@ namespace GameRes.Formats.MAI
m_pixels[dst+3] = alpha;
};
else
{
const int alphaScale = 0x11;
var alphaColor = Color.FromRgb (0, 0xFE, 0);
copy_pixel = (src, dst, alpha) => {
var color = Palette.Colors[m_output[src]];
if (Default8bppTransparencyColor == color)
alpha = 0;
else if (0 == alpha)
alpha = 0xFF;
else
alpha *= alphaScale;
m_pixels[dst] = color.B;
m_pixels[dst+1] = color.G;
m_pixels[dst+2] = color.R;
m_pixels[dst+3] = alpha;
};
}
int src_stride = m_width * m_pixel_size;
for (int y = 0; y < m_height; ++y)
{

View File

@ -190,6 +190,9 @@ namespace GameRes.Formats.Apple
break;
}
case 0x001E: // DefHilite
break;
case 0x0090:
case 0x0091:
case 0x0098:
@ -291,8 +294,10 @@ namespace GameRes.Formats.Apple
byte[] RepackPixels (Pixmap pixmap)
{
int bpp = m_info.BPP;
if (bpp <= 16)
if (bpp < 16)
return m_buffer;
else if (16 == bpp)
return Repack16bpp();
int bytes_per_pixel = bpp / 8;
int stride = m_info.iWidth * bytes_per_pixel;
var pixels = new byte[stride * m_info.iHeight];
@ -323,6 +328,17 @@ namespace GameRes.Formats.Apple
return pixels;
}
byte[] Repack16bpp () // swap 16bit pixels to little-endian order
{
for (int p = 1; p < m_buffer.Length; p += 2)
{
byte b = m_buffer[p-1];
m_buffer[p-1] = m_buffer[p];
m_buffer[p] = b;
}
return m_buffer;
}
void SetFormat (Pixmap pixmap)
{
int bpp = null == pixmap ? 8 : pixmap.BPP;

View File

@ -1,256 +0,0 @@
//! \file ArcCCT.cs
//! \date Fri Jun 26 01:15:26 2015
//! \brief Macromedia Director archive access implementation.
//
// Copyright (C) 2015 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;
using System.Text;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.Macromedia
{
[Export(typeof(ArchiveFormat))]
public class CctOpener : ArchiveFormat
{
public override string Tag { get { return "CCT"; } }
public override string Description { get { return "Macromedia Shockwave resource archive"; } }
public override uint Signature { get { return 0x52494658; } } // 'XFIR'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public CctOpener ()
{
Extensions = new string[] { "cct", "dcr" };
}
public override ArcFile TryOpen (ArcView file)
{
uint id = file.View.ReadUInt32 (8);
if (id != 0x46474443 && id != 0x4647444D) // 'CDGF' || 'MDGF'
return null;
var reader = new CctReader (file);
var dir = reader.ReadIndex();
if (null != dir)
{
var arc = new ArcFile (file, this, dir);
SetEntryTypes (arc);
return arc;
}
return null;
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var packed_entry = entry as PackedEntry;
if (null == packed_entry || !packed_entry.IsPacked)
return input;
else
return new ZLibStream (input, CompressionMode.Decompress);
}
private void SetEntryTypes (ArcFile arc)
{
foreach (var entry in arc.Dir.OrderBy (x => x.Offset))
{
if (entry.Name.EndsWith (".edim"))
entry.Type = DetectEdimType (arc, entry);
else if (entry.Name.EndsWith (".bitd"))
entry.Type = "image";
}
}
private string DetectEdimType (ArcFile arc, Entry entry)
{
using (var input = OpenEntry (arc, entry))
{
uint signature = (uint)input.ReadByte() << 24;
signature |= (uint)input.ReadByte() << 16;
signature |= (uint)input.ReadByte() << 8;
signature |= (byte)input.ReadByte();
if (0xffd8ffe0 == signature)
return "image";
uint real_size = (entry as PackedEntry).UnpackedSize;
if (signature > 0xffff || signature+4 > real_size)
return "";
var header = new byte[signature+0x10];
if (header.Length != input.Read (header, 0, header.Length))
return "";
if (0xff == header[signature])
return "audio";
return "";
}
}
}
internal class CctReader
{
ArcView m_file;
long m_offset;
public CctReader (ArcView file)
{
m_file = file;
m_offset = 0x0C;
}
byte[] m_size_buffer = new byte[10];
public List<Entry> ReadIndex ()
{
uint section_size = ReadSectionSize ("Fver");
m_offset += section_size;
section_size = ReadSectionSize ("Fcdr");
/*
int Mcdr_size;
var Mcdr = ZlibUnpack (m_offset, section_size, out Mcdr_size);
*/
m_offset += section_size;
uint abmp_size = ReadSectionSize ("ABMP");
int max_count = m_file.View.Read (m_offset, m_size_buffer, 0, Math.Min (10, abmp_size));
int size_offset = 0;
ReadValue (m_size_buffer, ref size_offset, max_count);
max_count -= size_offset;
int bmp_unpacked_size = (int)ReadValue (m_size_buffer, ref size_offset, max_count);
m_offset += size_offset;
abmp_size -= (uint)size_offset;
int index_size;
var index = ZlibUnpack (m_offset, abmp_size, out index_size, bmp_unpacked_size);
m_offset += abmp_size;
section_size = ReadSectionSize ("FGEI");
if (0 != section_size)
throw new NotSupportedException();
int index_offset = 0;
ReadValue (index, ref index_offset, index_size-index_offset);
ReadValue (index, ref index_offset, index_size-index_offset);
int entry_count = (int)ReadValue (index, ref index_offset, index_size-index_offset);
if (entry_count <= 0 || entry_count > 0xfffff)
return null;
var type_buf = new char[4];
var dir = new List<Entry> (entry_count);
for (int i = 0; i < entry_count; ++i)
{
uint id = ReadValue (index, ref index_offset, index_size-index_offset);
uint offset = ReadValue (index, ref index_offset, index_size-index_offset);
uint size = ReadValue (index, ref index_offset, index_size-index_offset);
uint unpacked_size = ReadValue (index, ref index_offset, index_size-index_offset);
uint flag = ReadValue (index, ref index_offset, index_size-index_offset);
if (index_size-index_offset < 4)
return null;
uint type_id = LittleEndian.ToUInt32 (index, index_offset);
index_offset += 4;
if (0 == type_id || uint.MaxValue == offset)
continue;
Encoding.ASCII.GetChars (index, index_offset-4, 4, type_buf, 0);
var entry = new PackedEntry
{
Name = CreateName (id, type_buf),
Offset = (long)m_offset + offset,
Size = size,
UnpackedSize = unpacked_size,
IsPacked = 0 == flag,
};
if (entry.CheckPlacement (m_file.MaxOffset))
{
dir.Add (entry);
}
}
return dir;
}
string CreateName (uint id, char[] type_buf)
{
Array.Reverse (type_buf);
int t = 3;
while (t >= 0 && ' ' == type_buf[t])
t--;
if (t >= 0)
{
string ext = new string (type_buf, 0, t+1);
return string.Format ("{0:D8}.{1}", id, ext.ToLowerInvariant());
}
else
return id.ToString ("D8");
}
byte[] ZlibUnpack (long offset, uint size, out int actual_size, int unpacked_size_hint = 0)
{
using (var input = m_file.CreateStream (offset, size))
using (var zstream = new ZLibStream (input, CompressionMode.Decompress))
using (var mem = new MemoryStream (unpacked_size_hint))
{
zstream.CopyTo (mem);
actual_size = (int)mem.Length;
return mem.GetBuffer();
}
}
uint ReadSectionSize (string id_str)
{
uint id = ConvertId (id_str);
if (id != m_file.View.ReadUInt32 (m_offset))
throw new InvalidFormatException();
m_offset += 4;
if (5 != m_file.View.Read (m_offset, m_size_buffer, 0, 5))
throw new InvalidFormatException();
int off_count = 0;
uint size = ReadValue (m_size_buffer, ref off_count, 5);
m_offset += off_count;
return size;
}
static uint ReadValue (byte[] buffer, ref int offset, int length)
{
uint n = 0;
for (int off_count = 0; off_count < length; ++off_count)
{
uint bits = buffer[offset++];
n = (n << 7) | (bits & 0x7F);
if (0 == (bits & 0x80))
return n;
}
throw new InvalidFormatException();
}
static uint ConvertId (string id)
{
if (id.Length != 4)
throw new ArgumentException ("Invalid section id");
uint n = 0;
for (int i = 0; i < 4; ++i)
n = (n << 8) | (byte)id[i];
return n;
}
}
}

View File

@ -38,46 +38,58 @@ namespace GameRes.Formats.Macromedia
[Export(typeof(ArchiveFormat))]
public class DxrOpener : ArchiveFormat
{
public override string Tag { get => "DXR"; }
public override string Description { get => "Macromedia Director resource archive"; }
public override uint Signature { get => 0x52494658; } // 'XFIR'
public override bool IsHierarchic { get => false; }
public override bool CanWrite { get => false; }
public override string Tag => "DXR";
public override string Description => "Macromedia Director resource archive";
public override uint Signature => SignatureXFIR; // 'XFIR'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public const uint SignatureXFIR = 0x52494658u;
public const uint SignatureRIFX = 0x58464952u;
public DxrOpener ()
{
Extensions = new[] { "dxr", "cxt", "cct", "dcr" };
Signatures = new[] { 0x52494658u, 0x58464952u };
Extensions = new[] { "dxr", "cxt", "cct", "dcr", "dir", "exe" };
Signatures = new[] { SignatureXFIR, SignatureRIFX, 0x00905A4Du, 0u };
}
internal static readonly HashSet<string> RawChunks = new HashSet<string> {
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", //"snd "
"RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "File"
};
internal bool ConvertText = true;
public override ArcFile TryOpen (ArcView file)
{
long base_offset = 0;
if (file.View.AsciiEqual (0, "MZ"))
base_offset = LookForXfir (file);
uint signature = file.View.ReadUInt32 (base_offset);
if (signature != SignatureXFIR && signature != SignatureRIFX)
return null;
using (var input = file.CreateStream())
{
ByteOrder ord = input.Signature == 0x52494658u ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
ByteOrder ord = signature == SignatureXFIR ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
var reader = new Reader (input, ord);
reader.Position = 4;
uint length = reader.ReadU32();
reader.Position = base_offset;
var context = new SerializationContext();
var dir_file = new DirectorFile();
if (!dir_file.Deserialize (context, reader))
return null;
var dir = new List<Entry> ();
ImportMedia (dir_file, dir);
if (dir_file.Codec != "APPL")
ImportMedia (dir_file, dir);
foreach (DirectorEntry entry in dir_file.Directory)
{
if (entry.Size != 0 && entry.Offset != -1 && RawChunks.Contains (entry.FourCC))
{
entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim());
if ("snd " == entry.FourCC)
entry.Type = "audio";
if ("File" == entry.FourCC)
{
entry.Offset -= 8;
entry.Size += 8;
}
dir.Add (entry);
}
}
@ -155,7 +167,7 @@ namespace GameRes.Formats.Macromedia
entry = ImportBitmap (piece, dir_file, cast);
else if (piece.Type == DataType.Sound)
entry = ImportSound (piece, dir_file);
if (entry != null)
if (entry != null && entry.Size > 0)
dir.Add (entry);
}
}
@ -381,6 +393,57 @@ namespace GameRes.Formats.Macromedia
zstream.Read (data, 0, data.Length);
return new BinMemoryStream (data, entry.Name);
}
static readonly byte[] s_xfir = { (byte)'X', (byte)'F', (byte)'I', (byte)'R' };
long LookForXfir (ArcView file)
{
var exe = new ExeFile (file);
long pos;
if (exe.IsWin16)
{
pos = exe.FindString (exe.Overlay, s_xfir);
if (pos < 0)
return 0;
}
else
{
pos = exe.Overlay.Offset;
if (pos >= file.MaxOffset)
return 0;
if (file.View.AsciiEqual (pos, "10JP") || file.View.AsciiEqual (pos, "59JP"))
{
pos = file.View.ReadUInt32 (pos+4);
}
}
if (pos >= file.MaxOffset || !file.View.AsciiEqual (pos, "XFIR"))
return 0;
// TODO threat 'LPPA' archives the normal way, like archives that contain entries.
// the problem is, DXR archives contained within 'LPPA' have their offsets relative to executable file,
// so have to figure out way to handle it.
if (!file.View.AsciiEqual (pos+8, "LPPA"))
return pos;
var appl = new DirectorFile();
var context = new SerializationContext();
using (var input = file.CreateStream())
{
var reader = new Reader (input, ByteOrder.LittleEndian);
input.Position = pos + 12;
if (!appl.ReadMMap (context, reader))
return 0;
foreach (var entry in appl.Directory)
{
// only the first XFIR entry is matched here, but archive may contain multiple sub-archives.
if (entry.FourCC == "File")
{
if (file.View.AsciiEqual (entry.Offset-8, "XFIR")
&& !file.View.AsciiEqual (entry.Offset, "artX"))
return entry.Offset-8;
}
}
return 0;
}
}
}
internal class BitmapEntry : PackedEntry
@ -407,10 +470,16 @@ namespace GameRes.Formats.Macromedia
Left = reader.ReadI16();
Bottom = reader.ReadI16();
Right = reader.ReadI16();
reader.Skip (0x0C);
BitDepth = reader.ReadU16() & 0xFF; // ???
reader.Skip (2);
Palette = reader.ReadI16();
if (data.Length > 0x16)
{
reader.Skip (0x0C);
BitDepth = reader.ReadU16() & 0xFF; // ???
if (data.Length >= 0x1C)
{
reader.Skip (2);
Palette = reader.ReadI16();
}
}
}
}
}

View File

@ -1,6 +1,6 @@
//! \file AudioEDIM.cs
//! \date Fri Jun 26 06:52:33 2015
//! \brief Selen wrapper around MP3 stream.
//! \brief Macromedia Director wrapper around MP3 stream.
//
// Copyright (C) 2015 by morkt
//
@ -33,7 +33,7 @@ namespace GameRes.Formats.Selen
public class EdimAudio : Mp3Audio
{
public override string Tag { get { return "EDIM"; } }
public override string Description { get { return "Selen audio format (MP3)"; } }
public override string Description { get { return "Macromedia Director audio format (MP3)"; } }
public override uint Signature { get { return 0x40010000; } }
public override bool CanWrite { get { return false; } }

View File

@ -75,6 +75,9 @@ namespace GameRes.Formats.Macromedia
DirectorConfig m_config = new DirectorConfig();
List<Cast> m_casts = new List<Cast>();
Dictionary<int, byte[]> m_ilsMap = new Dictionary<int, byte[]>();
string m_codec;
public string Codec => m_codec;
public bool IsAfterBurned { get; private set; }
@ -96,14 +99,14 @@ namespace GameRes.Formats.Macromedia
public bool Deserialize (SerializationContext context, Reader reader)
{
reader.Position = 8;
string codec = reader.ReadFourCC();
if (codec == "MV93" || codec == "MC95")
reader.Skip (8);
m_codec = reader.ReadFourCC();
if (m_codec == "MV93" || m_codec == "MC95")
{
if (!ReadMMap (context, reader))
return false;
}
else if (codec == "FGDC" || codec == "FGDM")
else if (m_codec == "FGDC" || m_codec == "FGDM")
{
IsAfterBurned = true;
if (!ReadAfterBurner (context, reader))
@ -111,7 +114,7 @@ namespace GameRes.Formats.Macromedia
}
else
{
Trace.WriteLine (string.Format ("Unknown codec '{0}'", codec), "DXR");
Trace.WriteLine (string.Format ("Unknown m_codec '{0}'", m_codec), "DXR");
return false;
}
return ReadKeyTable (context, reader)
@ -119,7 +122,7 @@ namespace GameRes.Formats.Macromedia
&& ReadCasts (context, reader);
}
bool ReadMMap (SerializationContext context, Reader reader)
internal bool ReadMMap (SerializationContext context, Reader reader)
{
if (reader.ReadFourCC() != "imap")
return false;

View File

@ -71,7 +71,7 @@ namespace GameRes.Formats.Macromedia
: info.BPP == 8 ? PixelFormats.Indexed8
: info.BPP == 16 ? PixelFormats.Bgr555
: info.DepthType == 0x87 // i have no clue what this is
|| info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A
|| info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A
: PixelFormats.Bgra32; // depth type 0x82/84/85/86/8C
m_palette = palette;
}
@ -151,32 +151,7 @@ namespace GameRes.Formats.Macromedia
{
for (int line = 0; line < m_output.Length; line += m_stride)
{
int x = 0;
while (x < m_stride)
{
int b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (x + count > m_stride)
throw new InvalidFormatException();
if (b > 0x7f)
{
b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
for (int i = 0; i < count; ++i)
m_output[line + x++] = (byte)b;
}
else
{
m_input.Read (m_output, line + x, count);
x += count;
}
}
UnpackScanLine (m_output, line);
}
return m_output;
}
@ -186,33 +161,7 @@ namespace GameRes.Formats.Macromedia
var scan_line = new byte[m_stride];
for (int line = 0; line < m_output.Length; line += m_stride)
{
int x = 0;
while (x < m_stride)
{
int b = m_input.ReadByte();
if (-1 == b)
break; // one in 5000 images somehow stumbles here
// throw new InvalidFormatException ("Unexpected end of file");
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (x + count > m_stride)
throw new InvalidFormatException();
if (b > 0x7f)
{
b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
for (int i = 0; i < count; ++i)
scan_line[x++] = (byte)b;
}
else
{
m_input.Read (scan_line, x, count);
x += count;
}
}
UnpackScanLine (scan_line, 0);
int dst = line;
for (int i = 0; i < m_width; ++i)
{
@ -222,6 +171,36 @@ namespace GameRes.Formats.Macromedia
}
}
void UnpackScanLine (byte[] scan_line, int pos)
{
int x = 0;
while (x < m_stride)
{
int b = m_input.ReadByte();
if (-1 == b)
break; // one in 5000 images somehow stumbles here
int count = b;
if (b > 0x7f)
count = (byte)-(sbyte)b;
++count;
if (x + count > m_stride)
throw new InvalidFormatException();
if (b > 0x7f)
{
b = m_input.ReadByte();
if (-1 == b)
throw new InvalidFormatException ("Unexpected end of file");
for (int i = 0; i < count; ++i)
scan_line[pos + x++] = (byte)b;
}
else
{
m_input.Read (scan_line, pos+x, count);
x += count;
}
}
}
#region IDisposable Members
bool m_disposed = false;

View File

@ -107,6 +107,9 @@ namespace GameRes.Formats.NScripter
catch { /* ignore parse errors */ }
if (zero_signature || !file.Name.HasExtension (".nsa"))
return null;
uint signature = file.View.ReadUInt32 (0);
if ((signature & 0xFFFFFF) == 0x90FBFF) // looks like mp3 file
return new WrapSingleFileArchive (file, Path.GetFileNameWithoutExtension (file.Name)+".mp3");
var password = QueryPassword();
if (string.IsNullOrEmpty (password))

View File

@ -39,6 +39,11 @@ namespace GameRes.Formats.Otemoto
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public TlzOpener ()
{
ContainedFormats = new[] { "BMP", "SCR" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0xC);
@ -61,7 +66,7 @@ namespace GameRes.Formats.Otemoto
if (0 == name_length || name_length > 0x100)
return null;
entry.Name = file.View.ReadString (index_offset+0x10, name_length);
entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name);
entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name, ContainedFormats);
entry.IsPacked = entry.UnpackedSize != entry.Size;
dir.Add (entry);
index_offset += 0x10 + name_length;
@ -78,4 +83,9 @@ namespace GameRes.Formats.Otemoto
return new LzssStream (input);
}
}
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "SNR")]
[ExportMetadata("Target", "SCR")]
internal class SnrFormat : ResourceAlias { }
}

View File

@ -45,7 +45,7 @@ namespace GameRes.Formats.Otemoto
[Export(typeof(ImageFormat))]
public class MagFormat : ImageFormat
{
public override string Tag { get { return "MAG"; } }
public override string Tag { get { return "MAG/MAKI02"; } }
public override string Description { get { return "Otemoto image format"; } }
public override uint Signature { get { return 0x494B414D; } } // 'MAKI02'

View File

@ -111,7 +111,7 @@ namespace GameRes.Formats.RealLive
int offset = input.ReadUInt16();
int count = (offset & 0xF) + 2;
offset >>= 4;
Binary.CopyOverlapped (output, dst-offset, dst, count);
Binary.CopyOverlapped (output, dst-offset-1, dst, count);
dst += count;
}
}

View File

@ -0,0 +1,67 @@
//! \file SingleFileArchive.cs
//! \date 2023 Oct 05
//! \brief represent single file as an archive for convenience.
//
// 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;
namespace GameRes.Formats
{
public class WrapSingleFileArchive : ArcFile
{
internal static readonly ArchiveFormat Format = new SingleFileArchiveFormat();
public WrapSingleFileArchive (ArcView file, Entry entry)
: base (file, Format, new List<Entry> { entry })
{
}
public WrapSingleFileArchive (ArcView file, string entry_name)
: base (file, Format, new List<Entry> { CreateEntry (file, entry_name) })
{
}
private static Entry CreateEntry (ArcView file, string name)
{
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = 0;
entry.Size = (uint)file.MaxOffset;
return entry;
}
/// this format is not registered in catalog and only accessible via WrapSingleFileArchive.Format singleton.
private class SingleFileArchiveFormat : ArchiveFormat
{
public override string Tag => "DAT/BOGUS";
public override string Description => "Not an archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
return new WrapSingleFileArchive (file, System.IO.Path.GetFileName (file.Name));
}
}
}
}

View File

@ -0,0 +1,170 @@
//! \file ArcCG3.cs
//! \date 2023 Oct 10
//! \brief Software House Parsley CG 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
// [050610][Software House Parsley] Desert Time Mugen no Meikyuu PE
namespace GameRes.Formats.Parsley
{
[Export(typeof(ArchiveFormat))]
public class DesertCgOpener : ArchiveFormat
{
public override string Tag { get { return "CG/DESERT"; } }
public override string Description { get { return "Software House Parsley CG archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public DesertCgOpener ()
{
Extensions = new string[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "CG"))
return null;
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
uint index_pos = 4;
var filename_table = LookupFileNameTable (file, count);
Func<int, string> get_entry_name;
if (filename_table != null)
get_entry_name = n => filename_table[n];
else
get_entry_name = n => string.Format ("CG#{0:D4}");
long last_offset = count * 4 + 4;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++ i)
{
uint offset = file.View.ReadUInt32 (index_pos);
if (0 == offset)
break;
if (offset <= last_offset || offset >= file.MaxOffset)
return null;
var entry = new Entry {
Name = get_entry_name (i),
Type = "image",
Offset = offset,
};
dir.Add (entry);
last_offset = offset;
index_pos += 4;
}
if (0 == dir.Count)
return null;
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);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
try
{
return new DesertCgDecoder (input);
}
catch
{
input.Dispose();
throw;
}
}
internal static Dictionary<string, uint> FileNameTableMap = new Dictionary<string, uint> {
{ @"..\DTime.exe", 0x49E348 },
};
List<string> LookupFileNameTable (ArcView file, int count)
{
try
{
var dir_name = Path.GetDirectoryName (file.Name);
foreach (var source in FileNameTableMap.Keys)
{
var src_name = Path.Combine (dir_name, source);
if (File.Exists (src_name))
{
using (var src = new ArcView (src_name))
{
var exe = new ExeFile (src);
long offset = exe.GetAddressOffset (FileNameTableMap[source]);
if (offset >= src.MaxOffset || offset + 0x104 * count > src.MaxOffset)
return null;
var dir = new List<string> (count);
for (int i = 0; i < count; ++i)
{
var name = src.View.ReadString (offset, 0x104);
dir.Add (name);
offset += 0x104;
}
return dir;
}
}
}
}
catch { } // ignore errors
return null;
}
}
internal class DesertCgDecoder : BinaryImageDecoder
{
public DesertCgDecoder (IBinaryStream input) : base (input, ReadMetaData (input))
{
}
static ImageMetaData ReadMetaData (IBinaryStream input)
{
return new ImageMetaData {
Width = input.ReadUInt32(),
Height = input.ReadUInt32(),
BPP = 8,
};
}
protected override ImageData GetImageData ()
{
m_input.Position = 8;
var palette = ImageFormat.ReadPalette (m_input.AsStream, 0x100, PaletteFormat.RgbX);
int stride = (Info.iWidth + 3) & ~3;
var pixels = m_input.ReadBytes (stride * Info.iHeight);
return ImageData.Create (Info, PixelFormats.Indexed8, palette, pixels, stride);
}
}
}

View File

@ -100,7 +100,7 @@ namespace GameRes.Formats.Triangle
return null;
unpacked_size &= (int)~0xC0000000;
stream.Position = data_offset;
byte[] bmp = UnpackBitmap (stream.AsStream, pack_type, packed_size, 0x26);
byte[] bmp = UnpackBitmap (stream, pack_type, packed_size, 0x26);
if (bmp[0] != 'B' && bmp[0] != 'C' || bmp[1] != 'M')
return null;
return new IafMetaData
@ -121,7 +121,7 @@ namespace GameRes.Formats.Triangle
{
var meta = (IafMetaData)info;
stream.Position = meta.DataOffset;
var bitmap = UnpackBitmap (stream.AsStream, meta.PackType, meta.PackedSize, meta.UnpackedSize);
var bitmap = UnpackBitmap (stream, meta.PackType, meta.PackedSize, meta.UnpackedSize);
if ('C' == bitmap[0])
{
bitmap[0] = (byte)'B';
@ -155,19 +155,24 @@ namespace GameRes.Formats.Triangle
return Bmp.Read (bmp, info);
}
internal static byte[] UnpackBitmap (Stream stream, int pack_type, int packed_size, int unpacked_size)
internal static byte[] UnpackBitmap (IBinaryStream stream, int pack_type, int packed_size, int unpacked_size)
{
if (2 == pack_type)
{
uint signature = stream.ReadUInt32();
stream.Seek (-4, SeekOrigin.Current);
using (var reader = new RleReader (stream, packed_size, unpacked_size))
{
reader.Unpack();
if (0x014D0142 == signature)
reader.UnpackV2();
else
reader.Unpack();
return reader.Data;
}
}
else if (0 == pack_type)
{
using (var reader = new LzssReader (stream, packed_size, unpacked_size))
using (var reader = new LzssReader (stream.AsStream, packed_size, unpacked_size))
{
reader.Unpack();
return reader.Data;
@ -261,15 +266,15 @@ namespace GameRes.Formats.Triangle
internal class RleReader : IDataUnpacker, IDisposable
{
BinaryReader m_input;
IBinaryStream m_input;
byte[] m_output;
int m_size;
public byte[] Data { get { return m_output; } }
public RleReader (Stream input, int input_length, int output_length)
public RleReader (IBinaryStream input, int input_length, int output_length)
{
m_input = new ArcView.Reader (input);
m_input = input;
m_output = new byte[output_length];
m_size = input_length;
}
@ -280,8 +285,8 @@ namespace GameRes.Formats.Triangle
int dst = 0;
while (dst < m_output.Length && src < m_size)
{
byte b = m_input.ReadByte();
int count = m_input.ReadByte();
byte b = m_input.ReadUInt8();
int count = m_input.ReadUInt8();
src += 2;
count = Math.Min (count, m_output.Length - dst);
for (int i = 0; i < count; i++)
@ -295,11 +300,11 @@ namespace GameRes.Formats.Triangle
int dst = 0;
while (dst < m_output.Length && src < m_size)
{
byte ctl = m_input.ReadByte();
byte ctl = m_input.ReadUInt8();
++src;
if (0 == ctl)
{
int count = m_input.ReadByte();
int count = m_input.ReadUInt8();
++src;
count = Math.Min (count, m_output.Length - dst);
int read = m_input.Read (m_output, dst, count);
@ -309,7 +314,7 @@ namespace GameRes.Formats.Triangle
else
{
int count = ctl;
byte b = m_input.ReadByte();
byte b = m_input.ReadUInt8();
++src;
count = Math.Min (count, m_output.Length - dst);
@ -320,24 +325,8 @@ namespace GameRes.Formats.Triangle
}
#region IDisposable Members
bool disposed = false;
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (!disposed)
{
if (disposing)
{
m_input.Dispose();
}
disposed = true;
}
}
#endregion
}

189
ArcFormats/Xuse/ArcNT.cs Normal file
View File

@ -0,0 +1,189 @@
//! \file ArcNT.cs
//! \date 2023 Oct 17
//! \brief Xuse 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
namespace GameRes.Formats.Xuse
{
[Export(typeof(ArchiveFormat))]
public class BgOpener : ArchiveFormat
{
public override string Tag => "BG/Xuse";
public override string Description => "Xuse bitmap archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public BgOpener ()
{
Extensions = new[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
var arc_name = Path.GetFileName (file.Name);
if (!arc_name.StartsWith ("bg00", StringComparison.OrdinalIgnoreCase) &&
!arc_name.StartsWith ("sbg", StringComparison.OrdinalIgnoreCase))
return null;
long rem;
uint entry_size = arc_name[0] == 's' ? 0x96400u : 0x4B400u;
int count = (int)Math.DivRem (file.MaxOffset, entry_size, out rem);
if (rem != 0 || !IsSaneCount (count))
return null;
uint offset = 0;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var entry = new Entry {
Name = string.Format ("{0}#{1:D4}", arc_name, i),
Type = "image",
Offset = offset,
Size = entry_size,
};
dir.Add (entry);
offset += entry_size;
}
return new ArcFile (file, this, dir);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
uint height = entry.Size == 0x4B400u ? 480u : 960u;
var info = new ImageMetaData { Width = 640, Height = height, BPP = 8 };
return new BgImageDecoder (input, info);
}
}
internal class BgImageDecoder : BinaryImageDecoder
{
public BgImageDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
{
}
protected override ImageData GetImageData ()
{
m_input.Position = 0;
var pixels = m_input.ReadBytes (Info.iWidth * Info.iHeight);
var palette = ImageFormat.ReadPalette (m_input.AsStream);
return ImageData.CreateFlipped (Info, PixelFormats.Indexed8, palette, pixels, Info.iWidth);
}
}
[Export(typeof(ArchiveFormat))]
public class HOpener : ArchiveFormat
{
public override string Tag => "H/Xuse";
public override string Description => "Xuse bitmap archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public HOpener ()
{
Extensions = new[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
var arc_name = Path.GetFileName (file.Name);
if (!arc_name.StartsWith ("H", StringComparison.OrdinalIgnoreCase))
return null;
long rem;
uint entry_size = arc_name.EndsWith ("W") ? 0x2A700u : 0x25480u;
int count = (int)Math.DivRem (file.MaxOffset, entry_size, out rem);
if (!IsSaneCount (count) || rem != 0)
return null;
uint offset = 0;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var entry = new Entry {
Name = string.Format ("{0}#{1:D4}", arc_name, i),
Type = "image",
Offset = offset,
Size = entry_size,
};
dir.Add (entry);
offset += entry_size;
}
return new ArcFile (file, this, dir);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
uint width = entry.Size == 0x25480u ? 280u : 320u;
var info = new ImageMetaData { Width = width, Height = 480, BPP = 8 };
return new HImageDecoder (input, info);
}
}
internal class HImageDecoder : BinaryImageDecoder
{
public HImageDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
{
}
protected override ImageData GetImageData ()
{
m_input.Position = 0;
int stride8bpp = Info.iWidth;
int alpha_stride = (Info.iWidth / 8 + 1) & -2;
var pixels = m_input.ReadBytes (stride8bpp * Info.iHeight);
var alpha = m_input.ReadBytes (alpha_stride * Info.iHeight);
m_input.Seek (-0x400, SeekOrigin.End);
var palette = ImageFormat.ReadPalette (m_input.AsStream);
int stride32bpp = Info.iWidth * 4;
var bgra = new byte[stride32bpp * Info.iHeight];
var colors = palette.Colors;
int src = pixels.Length - stride8bpp;
int asrc = alpha.Length - alpha_stride;
int dst_row = 0;
for (int y = 0; y < Info.iHeight; ++y)
{
int dst = dst_row;
for (int x = 0; x < Info.iWidth; ++x)
{
var c = colors[pixels[src+x]];
bgra[dst ] = c.B;
bgra[dst+1] = c.G;
bgra[dst+2] = c.R;
int A = (alpha[asrc + (x >> 3)] << (x & 7)) & 0x80;
bgra[dst+3] = (byte)(A == 0 ? 0xFF : 0);
dst += 4;
}
src -= stride8bpp;
asrc -= alpha_stride;
dst_row += stride32bpp;
}
return ImageData.Create (Info, PixelFormats.Bgra32, null, bgra, stride32bpp);
}
}
}

91
ArcFormats/Xuse/ArcWVB.cs Normal file
View File

@ -0,0 +1,91 @@
//! \file ArcWVB.cs
//! \date 2023 Oct 17
//! \brief Xuse audio 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;
namespace GameRes.Formats.Xuse
{
[Export(typeof(ArchiveFormat))]
public class WvbOpener : ArchiveFormat
{
public override string Tag => "WVB";
public override string Description => "Xuse audio resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int first_offset = file.View.ReadInt32 (4) - 1;
if (first_offset < 8 || first_offset >= file.MaxOffset)
return null;
int count = first_offset / 8;
if ((first_offset & 7) != 0 || !IsSaneCount (count))
return null;
uint fmt_size = file.View.ReadUInt32 (first_offset);
if (!file.View.AsciiEqual (first_offset+fmt_size+4, "data"))
return null;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
uint index_pos = 0;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index_pos+4);
if (0 == offset)
break;
var entry = new Entry {
Name = string.Format ("{0}#{1:D2}", base_name, i),
Type = "audio",
Size = file.View.ReadUInt32 (index_pos) - 8,
Offset = offset - 1,
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.Size += 16;
dir.Add (entry);
index_pos += 8;
}
if (0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var header = new byte[0x10];
LittleEndian.Pack (AudioFormat.Wav.Signature, header, 0);
LittleEndian.Pack (entry.Size - 8u, header, 4);
LittleEndian.Pack (0x45564157, header, 8); // 'WAVE'
LittleEndian.Pack (0x20746d66, header, 12); // 'fmt '
var data = arc.File.CreateStream (entry.Offset, entry.Size - 16);
return new PrefixStream (header, data);
}
}
}

View File

@ -105,7 +105,7 @@ namespace GameRes.Formats.YuRis
public YpfOpener ()
{
Signatures = new uint[] { 0x00465059, 0 };
Signatures = new uint[] { 0x00465059, 0x00905A4D, 0 };
}
static public Dictionary<string, YpfScheme> KnownSchemes { get { return DefaultScheme.KnownSchemes; } }

View File

@ -52,30 +52,37 @@ namespace GameRes.Formats.Zyx
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
int count = file.ReadInt16();
if (count <= 0)
if (count < 0 || count > 0x100)
return null;
var tiles = new Tile[count];
for (int i = 0; i < count; ++i)
Tile[] tiles = null;
if (count > 0)
{
var tile = new Tile();
tile.Left = file.ReadInt16();
tile.Top = file.ReadInt16();
if (tile.Left < 0 || tile.Top < 0)
return null;
tile.Right = file.ReadInt16();
tile.Bottom = file.ReadInt16();
if (tile.Right <= tile.Left || tile.Bottom <= tile.Top)
return null;
tiles[i] = tile;
tiles = new Tile[count];
for (int i = 0; i < count; ++i)
{
var tile = new Tile();
tile.Left = file.ReadInt16();
tile.Top = file.ReadInt16();
if (tile.Left < 0 || tile.Top < 0)
return null;
tile.Right = file.ReadInt16();
tile.Bottom = file.ReadInt16();
if (tile.Right <= tile.Left || tile.Bottom <= tile.Top)
return null;
tiles[i] = tile;
}
}
int width = file.ReadInt16();
int height = file.ReadInt16();
if (width <= 0 || height <= 0)
return null;
foreach (var tile in tiles)
if (tiles != null)
{
if (tile.Right > width || tile.Bottom > height)
return null;
foreach (var tile in tiles)
{
if (tile.Right > width || tile.Bottom > height)
return null;
}
}
return new SplMetaData
{

View File

@ -37,11 +37,11 @@ namespace GameRes.Formats.Microsoft
[ExportMetadata("Priority", -1)]
public class ExeOpener : ArchiveFormat
{
public override string Tag { get => "EXE"; }
public override string Description { get => "Windows executable resources"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => true; }
public override bool CanWrite { get => false; }
public override string Tag => "EXE";
public override string Description => "Windows executable resources";
public override uint Signature => 0;
public override bool IsHierarchic => true;
public override bool CanWrite => false;
public ExeOpener ()
{
@ -158,8 +158,6 @@ namespace GameRes.Formats.Microsoft
return id;
}
static readonly byte[] VS_VERSION_INFO = Encoding.Unicode.GetBytes ("VS_VERSION_INFO");
Stream OpenVersion (byte[] data, string name)
{
var input = new BinMemoryStream (data, name);
@ -177,17 +175,23 @@ namespace GameRes.Formats.Microsoft
input.Position = pos;
if (input.ReadUInt32() != 0xFEEF04BDu)
break;
input.Position = pos + value_length;
int str_info_length = input.ReadUInt16();
value_length = input.ReadUInt16();
type = input.ReadUInt16();
if (value_length != 0)
break;
if (input.ReadCString (Encoding.Unicode) != "StringFileInfo")
int info_length = value_length;
bool found_string_info = false;
do
{
pos += info_length;
input.Position = pos;
info_length = input.ReadUInt16();
value_length = input.ReadUInt16();
type = input.ReadUInt16();
found_string_info = input.ReadCString (Encoding.Unicode) == "StringFileInfo";
}
while (!found_string_info && input.PeekByte() != -1);
if (!found_string_info)
break;
pos = (input.Position + 3) & -4L;
input.Position = pos;
int info_length = input.ReadUInt16();
info_length = input.ReadUInt16();
long end_pos = pos + info_length;
value_length = input.ReadUInt16();
type = input.ReadUInt16();
@ -197,7 +201,7 @@ namespace GameRes.Formats.Microsoft
using (var text = new StreamWriter (output, new UTF8Encoding (false), 512, true))
{
string block_name = input.ReadCString (Encoding.Unicode);
text.WriteLine ("BLOCK \"{0}\"\n{{", block_name);
text.WriteLine ("BLOCK \"{0}\"\r\n{{", block_name);
long next_pos = (input.Position + 3) & -4L;
while (next_pos < end_pos)
{

View File

@ -27,17 +27,19 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
namespace GameRes.Formats.Microsoft
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
[ExportMetadata("Priority", -2)]
public class NeExeOpener : ArchiveFormat
{
public override string Tag { get => "EXE/NE"; }
public override string Description { get => "Windows 16-bit executable resources"; }
public override uint Signature { get => 0; }
public override bool IsHierarchic { get => true; }
public override bool CanWrite { get => false; }
public override string Tag => "EXE/NE";
public override string Description => "Windows 16-bit executable resources";
public override uint Signature => 0;
public override bool IsHierarchic => true;
public override bool CanWrite => false;
static readonly Dictionary<int, string> TypeMap = new Dictionary<int, string> {
{ 1, "RT_CURSOR" },
@ -53,10 +55,10 @@ namespace GameRes.Formats.Microsoft
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (0, "MZ"))
if (!file.View.AsciiEqual (0, "MZ") || file.MaxOffset < 0x40)
return null;
uint ne_offset = file.View.ReadUInt32 (0x3C);
if (!file.View.AsciiEqual (ne_offset, "NE"))
if (ne_offset > file.MaxOffset-2 || !file.View.AsciiEqual (ne_offset, "NE"))
return null;
uint res_table_offset = file.View.ReadUInt16 (ne_offset+0x24) + ne_offset;
if (res_table_offset <= ne_offset || res_table_offset >= file.MaxOffset)
@ -84,13 +86,17 @@ namespace GameRes.Formats.Microsoft
int offset = file.View.ReadUInt16 (res_table_offset) << shift;
uint size = (uint)file.View.ReadUInt16 (res_table_offset+2) << shift;
int res_id = file.View.ReadUInt16 (res_table_offset+6);
if ((res_id & 0x8000) != 0)
res_id &= 0x7FFF;
res_table_offset += 12;
string name = res_id.ToString ("D5");
name = string.Join ("/", dir_name, name);
var entry = new Entry {
var entry = new NeResourceEntry {
Name = name,
Offset = offset,
Size = size,
NativeName = res_id,
NativeType = type_id,
};
dir.Add (entry);
}
@ -99,5 +105,79 @@ namespace GameRes.Formats.Microsoft
return null;
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var rent = (NeResourceEntry)entry;
if (rent.NativeType == 16)
return OpenVersion (arc, rent);
return base.OpenEntry (arc, entry);
}
Encoding DefaultEncoding = Encodings.cp932;
Stream OpenVersion (ArcFile arc, NeResourceEntry entry)
{
uint data_length = arc.File.View.ReadUInt16 (entry.Offset);
var input = arc.File.CreateStream (entry.Offset, data_length);
for (;;)
{
input.ReadUInt16();
int value_length = input.ReadUInt16();
if (0 == value_length)
break;
if (input.ReadCString (DefaultEncoding) != "VS_VERSION_INFO")
break;
long pos = (input.Position + 3) & -4L;
input.Position = pos;
if (input.ReadUInt32() != 0xFEEF04BDu)
break;
input.Position = pos + value_length;
int str_info_length = input.ReadUInt16();
value_length = input.ReadUInt16();
if (value_length != 0)
break;
if (input.ReadCString (DefaultEncoding) != "StringFileInfo")
break;
pos = (input.Position + 3) & -4L;
input.Position = pos;
int info_length = input.ReadUInt16();
long end_pos = pos + info_length;
value_length = input.ReadUInt16();
if (value_length != 0)
break;
var output = new MemoryStream();
using (var text = new StreamWriter (output, DefaultEncoding, 512, true))
{
string block_name = input.ReadCString (DefaultEncoding);
text.WriteLine ("BLOCK \"{0}\"\n{{", block_name);
long next_pos = (input.Position + 3) & -4L;
while (next_pos < end_pos)
{
input.Position = next_pos;
info_length = input.ReadUInt16();
value_length = input.ReadUInt16();
next_pos = (next_pos + info_length + 3) & -4L;
string key = input.ReadCString (DefaultEncoding);
input.Position = (input.Position + 3) & -4L;
string value = value_length != 0 ? input.ReadCString (value_length, DefaultEncoding)
: String.Empty;
text.WriteLine ("\tVALUE \"{0}\", \"{1}\"", key, value);
}
text.WriteLine ("}");
}
input.Dispose();
output.Position = 0;
return output;
}
input.Position = 0;
return input;
}
}
internal class NeResourceEntry : Entry
{
public int NativeType;
public int NativeName;
}
}

View File

@ -197,8 +197,8 @@ namespace GameRes
return null;
}
public static AudioFormat Wav { get { return s_WavFormat.Value; } }
public static AudioFormat Wav => s_WavFormat.Value;
static readonly Lazy<AudioFormat> s_WavFormat = new Lazy<AudioFormat> (() => FormatCatalog.Instance.AudioFormats.FirstOrDefault (x => x.Tag == "WAV"));
static readonly ResourceInstance<AudioFormat> s_WavFormat = new ResourceInstance<AudioFormat> ("WAV");
}
}

View File

@ -833,10 +833,7 @@ namespace GameRes
return false;
// now, compare length of filename portion of the path
int filename_index = path.LastIndexOfAny (PathSeparatorChars);
if (-1 == filename_index)
filename_index = 0;
else
filename_index++;
filename_index++;
int filename_portion_length = path.Length - filename_index;
return filename.Length == filename_portion_length;
}

View File

@ -189,15 +189,15 @@ namespace GameRes
return FormatCatalog.Instance.ImageFormats.FirstOrDefault (x => x.Tag == tag);
}
static readonly Lazy<ImageFormat> s_JpegFormat = new Lazy<ImageFormat> (() => FindByTag ("JPEG"));
static readonly Lazy<ImageFormat> s_PngFormat = new Lazy<ImageFormat> (() => FindByTag ("PNG"));
static readonly Lazy<ImageFormat> s_BmpFormat = new Lazy<ImageFormat> (() => FindByTag ("BMP"));
static readonly Lazy<ImageFormat> s_TgaFormat = new Lazy<ImageFormat> (() => FindByTag ("TGA"));
static readonly ResourceInstance<ImageFormat> s_JpegFormat = new ResourceInstance<ImageFormat> ("JPEG");
static readonly ResourceInstance<ImageFormat> s_PngFormat = new ResourceInstance<ImageFormat> ("PNG");
static readonly ResourceInstance<ImageFormat> s_BmpFormat = new ResourceInstance<ImageFormat> ("BMP");
static readonly ResourceInstance<ImageFormat> s_TgaFormat = new ResourceInstance<ImageFormat> ("TGA");
public static ImageFormat Jpeg { get { return s_JpegFormat.Value; } }
public static ImageFormat Png { get { return s_PngFormat.Value; } }
public static ImageFormat Bmp { get { return s_BmpFormat.Value; } }
public static ImageFormat Tga { get { return s_TgaFormat.Value; } }
public static ImageFormat Jpeg => s_JpegFormat.Value;
public static ImageFormat Png => s_PngFormat.Value;
public static ImageFormat Bmp => s_BmpFormat.Value;
public static ImageFormat Tga => s_TgaFormat.Value;
/// <summary>
/// Desereialize color map from <paramref name="input"/> stream, consisting of specified number of

View File

@ -47,6 +47,7 @@ namespace GameRes
}
[Export(typeof(ImageFormat))]
[ExportMetadata("Priority", 10)]
public sealed class BmpFormat : ImageFormat
{
public override string Tag { get { return "BMP"; } }

View File

@ -34,6 +34,7 @@ using GameRes.Utility;
namespace GameRes
{
[Export(typeof(ImageFormat))]
[ExportMetadata("Priority", 10)]
public class JpegFormat : ImageFormat
{
public override string Tag { get { return "JPEG"; } }

239
Legacy/Adv98/ImageGPC.cs Normal file
View File

@ -0,0 +1,239 @@
//! \file ImageGPC.cs
//! \date 2023 Sep 22
//! \brief Adv98 engine image format (PC-98).
//
// 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;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Adv98
{
internal class GpcMetaData : ImageMetaData
{
public long PaletteOffset;
public long DataOffset;
public int Interleaving;
}
[Export(typeof(ImageFormat))]
public class GpcFormat : ImageFormat
{
public override string Tag => "GPC/PC98";
public override string Description => "Adv98 engine image format";
public override uint Signature => 0x38394350; // 'PC98'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x20);
if (!header.AsciiEqual (4, ")GPCFILE \0"))
return null;
uint info_pos = header.ToUInt32 (0x18);
var info = new GpcMetaData
{
Interleaving = header.ToUInt16 (0x10),
PaletteOffset = header.ToUInt32 (0x14),
DataOffset = info_pos + 0x10,
BPP = 4,
};
file.Position = info_pos;
info.Width = file.ReadUInt16();
info.Height = file.ReadUInt16();
file.Position = info_pos + 0xA;
info.OffsetX = file.ReadInt16();
info.OffsetY = file.ReadInt16();
return info;
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GpcReader (file, (GpcMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GpcFormat.Write not implemented");
}
}
internal class GpcReader
{
IBinaryStream m_input;
GpcMetaData m_info;
int m_stride;
public BitmapPalette Palette { get; private set; }
public GpcReader (IBinaryStream input, GpcMetaData info)
{
m_input = input;
m_info = info;
}
public ImageData Unpack ()
{
m_input.Position = m_info.PaletteOffset;
Palette = ReadPalette();
int plane_stride = (m_info.iWidth + 7) >> 3;
int row_size = plane_stride * 4 + 1;
var data = new byte[row_size * m_info.iHeight];
m_input.Position = m_info.DataOffset;
UnpackData (data);
RestoreData (data, row_size);
m_stride = plane_stride * 4;
var pixels = new byte[m_stride * m_info.iHeight];
ConvertTo8bpp (data, pixels, plane_stride);
return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, pixels, m_stride);
}
void ConvertTo8bpp (byte[] input, byte[] output, int plane_stride)
{
int interleaving_step = m_stride * m_info.Interleaving;
int src_row = 1;
int dst_row = 0;
int i = 0;
for (int y = 0; y < m_info.iHeight; ++y)
{
if (dst_row >= output.Length)
{
dst_row = m_stride * ++i;
}
int p0 = src_row;
int p1 = p0 + plane_stride;
int p2 = p1 + plane_stride;
int p3 = p2 + plane_stride;
src_row = p3 + plane_stride + 1;
int dst = dst_row;
for (int x = plane_stride; x > 0; --x)
{
byte b0 = input[p0++];
byte b1 = input[p1++];
byte b2 = input[p2++];
byte b3 = input[p3++];
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
output[dst++] = px;
}
}
dst_row += interleaving_step;
}
}
void UnpackData (byte[] output)
{
int dst = 0;
int ctl = 0;
int ctl_mask = 0;
while (dst < output.Length)
{
if (0 == ctl_mask)
{
ctl = m_input.ReadByte();
if (-1 == ctl)
break;
ctl_mask = 0x80;
}
if ((ctl & ctl_mask) != 0)
{
int cmd = m_input.ReadByte();
for (int cmd_mask = 0x80; cmd_mask != 0; cmd_mask >>= 1)
{
if ((cmd & cmd_mask) != 0)
output[dst++] = m_input.ReadUInt8();
else
++dst;
}
}
else
{
dst += 8;
}
ctl_mask >>= 1;
}
}
void RestoreData (byte[] data, int stride)
{
int src = 0;
for (int y = 0; y < m_info.iHeight; ++y)
{
int interleave = data[src];
if (interleave != 0)
{
byte lastValue = 0;
for (int i = 0; i < interleave; ++i)
{
int pos = 1 + i;
while (pos < stride)
{
data[src + pos] ^= lastValue;
lastValue = data[src + pos];
pos += interleave;
}
}
}
if (y > 0)
{
int prev = src - stride;
int length = (stride - 1) & -4;
for (int x = 1; x <= length; ++x)
{
data[src + x] ^= data[prev + x];
}
}
src += stride;
}
}
BitmapPalette ReadPalette ()
{
int count = m_input.ReadUInt16();
int elem_size = m_input.ReadUInt16();
if (elem_size != 2)
throw new InvalidFormatException (string.Format ("Invalid palette element size {0}", elem_size));
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
int v = m_input.ReadUInt16();
int r = (v >> 4) & 0xF;
int g = (v >> 8) & 0xF;
int b = (v ) & 0xF;
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
// colors[0].A = 0; // force transparency
return new BitmapPalette (colors);
}
}
}

366
Legacy/Adviz/ImageBIZ.cs Normal file
View File

@ -0,0 +1,366 @@
//! \file ImageBIZ.cs
//! \date 2023 Sep 30
//! \brief ADVIZ engine 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 System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [970829][Ange] Coin
namespace GameRes.Formats.Adviz
{
[Export(typeof(ImageFormat))]
public class BizFormat : ImageFormat
{
public override string Tag => "BIZ";
public override string Description => "ADVIZ engine image format";
public override uint Signature => 0;
const byte DefaultKey = 0x39;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".BIZ"))
return null;
var header = file.ReadHeader (4);
uint width = header.ToUInt16 (0);
uint height = header.ToUInt16 (2);
if (width * height + 4 != file.Length)
return null;
return new ImageMetaData {
Width = width,
Height = height,
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var palette = ReadPalette (file.Name, 0x300, (pal, off) => ReadPalette (pal, off, 0x100, PaletteFormat.Rgb));
if (null == palette)
throw new FileNotFoundException ("Unable to retrieve palette.");
file.Position = 4;
var pixels = file.ReadBytes (info.iWidth * info.iHeight);
byte key = DefaultKey;
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] ^= key;
key += pixels[i];
}
return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, pixels, info.iWidth);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("BizFormat.Write not implemented");
}
internal delegate BitmapPalette PaletteReader (ArcView file, int offset);
static readonly Regex TachieRe = new Regex (@"^(T[^._]+_)[2-9][^.]*\.GIZ$", RegexOptions.Compiled);
internal static BitmapPalette ReadPalette (string base_name, int pal_size, PaletteReader read_pal)
{
var dir_name = Path.GetDirectoryName (base_name);
var grp_tbl_name = Path.Combine (dir_name, @"..\GRP_TBL.SYS");
var plt_tbl_name = Path.Combine (dir_name, @"..\PLT_TBL.SYS");
if (!File.Exists (grp_tbl_name) || !File.Exists (plt_tbl_name))
return null;
int index = 0;
uint grp_size = 0;
base_name = Path.GetFileName (base_name).ToUpperInvariant();
var name = base_name;
var ext = Path.GetExtension (name).TrimStart('.');
var match = TachieRe.Match (name);
if (match.Success)
name = match.Groups[1].Value + "1";
else
name = Path.GetFileNameWithoutExtension (name);
if (name.Length < 8)
name += ' ';
using (var grp = new ArcView (grp_tbl_name))
{
grp_size = (uint)grp.MaxOffset;
int pos = 0;
while (pos + 12 <= grp.MaxOffset)
{
if (grp.View.AsciiEqual (pos, name) &&
grp.View.AsciiEqual (pos+8, ext))
{
break;
}
++index;
pos += 12;
}
if (pos >= grp.MaxOffset)
return null;
}
using (var pal = new ArcView (plt_tbl_name))
{
uint plt_size = (uint)pal.MaxOffset;
var id = new GrpIdentifier (grp_size, plt_size);
IGrpMapper mapper;
if (!GrpMap.TryGetValue (id, out mapper))
mapper = new DirectMapper();
index = mapper.GetPaletteIndex (index, base_name);
int pal_offset = index * pal_size;
if (pal_offset + pal_size > pal.MaxOffset)
{
int count = (int)(pal.MaxOffset / pal_size) - 1;
pal_offset = count * pal_size;
}
return read_pal (pal, pal_offset);
}
}
static readonly Dictionary<GrpIdentifier, IGrpMapper> GrpMap = new Dictionary<GrpIdentifier, IGrpMapper> {
{ new GrpIdentifier (1584, 139008), new GrpShiftMapper (52) },
{ new GrpIdentifier (2160, 12288),
new GrpNameMapper { NameMap = new Dictionary<string, int> {
{ "BG01.BIZ", 14 },
{ "BG02.BIZ", 8 },
{ "BG03.BIZ", 8 },
{ "BG04.BIZ", 8 },
{ "BG05.BIZ", 8 },
{ "BG06.BIZ", 8 },
{ "BG07.BIZ", 8 },
{ "BG08.BIZ", 8 },
{ "BG09.BIZ", 8 },
{ "BG10.BIZ", 8 },
{ "BG11.BIZ", 8 },
{ "BG12.BIZ", 8 },
{ "BG13.BIZ", 8 },
{ "BG14.BIZ", 8 },
{ "BG15.BIZ", 8 },
{ "BG16.BIZ", 8 },
{ "BG17.BIZ", 8 },
{ "BG18.BIZ", 8 },
{ "BG19.BIZ", 8 },
{ "CA01.BIZ", 12 },
{ "CA02.BIZ", 12 },
{ "CA03.BIZ", 4 },
{ "CA04.BIZ", 8 },
{ "CA05.BIZ", 8 },
{ "CA06.BIZ", 8 },
{ "CA07.BIZ", 8 },
{ "CA08.BIZ", 8 },
{ "CA09.BIZ", 8 },
{ "CA10.BIZ", 8 },
{ "CA11.BIZ", 8 },
{ "CA12.BIZ", 8 },
{ "CA13.BIZ", 8 },
{ "CA14.BIZ", 8 },
{ "CA15.BIZ", 8 },
{ "CA16.BIZ", 8 },
{ "CA17.BIZ", 8 },
{ "CA18.BIZ", 8 },
{ "CA19.BIZ", 8 },
{ "CA20.BIZ", 8 },
{ "CA21.BIZ", 8 },
{ "CA22.BIZ", 8 },
{ "CA23.BIZ", 8 },
{ "CA24.BIZ", 8 },
{ "CA25.BIZ", 8 },
{ "CA26.BIZ", 8 },
{ "CA27.BIZ", 8 },
{ "CA28.BIZ", 8 },
{ "CA29.BIZ", 8 },
{ "CA30.BIZ", 8 },
{ "CA31.BIZ", 8 },
{ "CA32.BIZ", 8 },
{ "CA33.BIZ", 8 },
{ "CA34.BIZ", 8 },
{ "CA35.BIZ", 8 },
{ "CA36.BIZ", 8 },
{ "CA37.BIZ", 8 },
{ "CA38.BIZ", 8 },
{ "CA39.BIZ", 8 },
{ "CA40.BIZ", 8 },
{ "CA41.BIZ", 8 },
{ "CA42.BIZ", 8 },
{ "CA43.BIZ", 8 },
{ "CA44.BIZ", 8 },
{ "CA45.BIZ", 8 },
{ "CA46.BIZ", 8 },
{ "CA47.BIZ", 8 },
{ "CA48.BIZ", 8 },
{ "CA49.BIZ", 8 },
{ "CA50.BIZ", 8 },
{ "CA51.BIZ", 8 },
{ "CA52.BIZ", 8 },
{ "CA53.BIZ", 8 },
{ "CA54.BIZ", 8 },
{ "CA55.BIZ", 8 },
{ "CA56.BIZ", 8 },
{ "CA57.BIZ", 8 },
{ "CA58.BIZ", 8 },
{ "CA59.BIZ", 8 },
{ "CA60.BIZ", 8 },
{ "E02.BIZ", 8 },
{ "E03.BIZ", 8 },
{ "E04.BIZ", 8 },
{ "E05.BIZ", 8 },
{ "E06.BIZ", 8 },
{ "E07.BIZ", 8 },
{ "E08.BIZ", 8 },
{ "E09.BIZ", 8 },
{ "E10.BIZ", 8 },
{ "E11.BIZ", 8 },
{ "E12.BIZ", 8 },
{ "E13.BIZ", 8 },
{ "E14.BIZ", 8 },
{ "E15.BIZ", 8 },
{ "E16.BIZ", 8 },
{ "E17.BIZ", 8 },
{ "E18.BIZ", 8 },
{ "END.BIZ", 6 },
{ "IPL.BIZ", 12 },
{ "S01.BIZ", 8 },
{ "S02.BIZ", 8 },
{ "S03.BIZ", 8 },
{ "S04.BIZ", 8 },
{ "S05.BIZ", 8 },
{ "S06.BIZ", 8 },
{ "S07.BIZ", 8 },
{ "S08.BIZ", 8 },
{ "S09.BIZ", 8 },
{ "S10.BIZ", 8 },
{ "S11.BIZ", 8 },
{ "S12.BIZ", 8 },
{ "S13.BIZ", 8 },
{ "S14.BIZ", 8 },
{ "S15.BIZ", 8 },
{ "S16.BIZ", 8 },
{ "S17.BIZ", 8 },
{ "S18.BIZ", 8 },
{ "S19.BIZ", 8 },
{ "S20.BIZ", 8 },
{ "S21.BIZ", 8 },
{ "S22.BIZ", 8 },
{ "S23.BIZ", 8 },
{ "S24.BIZ", 8 },
{ "S25.BIZ", 8 },
{ "S26.BIZ", 8 },
{ "S27.BIZ", 8 },
{ "S28.BIZ", 8 },
{ "S29.BIZ", 8 },
{ "S30.BIZ", 8 },
{ "S31.BIZ", 8 },
{ "S32.BIZ", 8 },
{ "S33.BIZ", 8 },
{ "S34.BIZ", 8 },
{ "S35.BIZ", 8 },
{ "S36.BIZ", 8 },
{ "S37.BIZ", 8 },
{ "S38.BIZ", 8 },
{ "S39.BIZ", 8 },
{ "S40.BIZ", 8 },
{ "S41.BIZ", 8 },
{ "S42.BIZ", 8 },
{ "S43.BIZ", 8 },
{ "S44.BIZ", 8 },
{ "S45.BIZ", 8 },
{ "S46.BIZ", 8 },
{ "S47.BIZ", 8 },
{ "S48.BIZ", 8 },
{ "S49.BIZ", 8 },
{ "T01.BIZ", 8 },
{ "T02.BIZ", 8 },
{ "T03.BIZ", 8 },
{ "WAKU1.BIZ", 8 },
{ "WAKU2.BIZ", 8 },
} } },
};
}
public struct GrpIdentifier
{
public uint GrpSize;
public uint PltSize;
public GrpIdentifier (uint grp_size, uint plt_size)
{
GrpSize = grp_size;
PltSize = plt_size;
}
public override int GetHashCode ()
{
return (int)((GrpSize + 1) * (PltSize + 1));
}
public override bool Equals (object obj)
{
if (null == obj)
return false;
var other = (GrpIdentifier)obj;
return this.GrpSize == other.GrpSize && this.PltSize == other.PltSize;
}
}
internal interface IGrpMapper
{
int GetPaletteIndex (int id, string name);
}
internal class DirectMapper : IGrpMapper
{
public int GetPaletteIndex (int id, string name)
{
return id;
}
}
internal class GrpShiftMapper : IGrpMapper
{
int m_shift;
public GrpShiftMapper (int shift)
{
m_shift = shift;
}
public int GetPaletteIndex (int id, string name)
{
return id + m_shift;
}
}
internal class GrpNameMapper : IGrpMapper
{
public Dictionary<string, int> NameMap;
public int GetPaletteIndex (int id, string name)
{
int index;
if (NameMap.TryGetValue (name, out index))
return index;
return id;
}
}
}

View File

@ -1,6 +1,6 @@
//! \file ImageBIZ.cs
//! \file ImageBIZ2.cs
//! \date 2018 Feb 11
//! \brief Sorciere compressed image.
//! \brief ADVIZ engine compressed image.
//
// Copyright (C) 2018 by morkt
//
@ -29,14 +29,15 @@ using System.Windows.Media;
using GameRes.Compression;
// [000225][Sorciere] Karei
// [011012][Ange] Nyuunyuu
namespace GameRes.Formats.Sorciere
namespace GameRes.Formats.Adviz
{
[Export(typeof(ImageFormat))]
public class BizFormat : ImageFormat
public class Biz2Format : ImageFormat
{
public override string Tag { get { return "BIZ"; } }
public override string Description { get { return "Sorciere compressed image"; } }
public override string Tag { get { return "BIZ/2"; } }
public override string Description { get { return "ADVIZ engine compressed image"; } }
public override uint Signature { get { return 0x325A4942; } } // 'BIZ2'
public override ImageMetaData ReadMetaData (IBinaryStream file)
@ -52,13 +53,34 @@ namespace GameRes.Formats.Sorciere
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 8;
using (var input = new LzssStream (file.AsStream, LzssMode.Decompress, true))
using (var lzss = new LzssStream (file.AsStream, LzssMode.Decompress, true))
using (var input = new BinaryStream (lzss, file.Name))
{
int stride = (int)info.Width * 3;
var pixels = new byte[stride * (int)info.Height];
if (pixels.Length != input.Read (pixels, 0, pixels.Length))
int stride = info.iWidth * 3;
var rgb = new byte[stride * info.Height];
if (rgb.Length != input.Read (rgb, 0, rgb.Length))
throw new InvalidFormatException();
return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride);
if (input.PeekByte() != -1) // possible alpha channel
{
var alpha = input.ReadBytes (rgb.Length);
if (alpha.Length == rgb.Length)
{
int stride32bpp = info.iWidth * 4;
var rgba = new byte[stride32bpp * info.iHeight];
int src = 0;
int dst = 0;
while (src < rgb.Length)
{
rgba[dst++] = rgb[src ];
rgba[dst++] = rgb[src+1];
rgba[dst++] = rgb[src+2];
rgba[dst++] = alpha[src]; // presumably it's grayscale and R/G/B values are equal
src += 3;
}
return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, rgba, stride32bpp);
}
}
return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, rgb, stride);
}
}

309
Legacy/Adviz/ImageGIZ.cs Normal file
View File

@ -0,0 +1,309 @@
//! \file ImageGIZ.cs
//! \date 2023 Oct 02
//! \brief ADVIZ engine image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [960830][Ange] Coin
namespace GameRes.Formats.Adviz
{
internal class GizMetaData : ImageMetaData
{
public byte RleCode;
public byte PlaneMap;
public bool HasPalette;
}
[Export(typeof(ImageFormat))]
public class Giz3Format : ImageFormat
{
public override string Tag => "GIZ";
public override string Description => "ADVIZ engine image format";
public override uint Signature => 0x335A4947; // 'GIZ3'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
int xy = header.ToUInt16 (4);
return new GizMetaData {
Width = (uint)header.ToUInt16 (6) << 3,
Height = header.ToUInt16 (8),
OffsetX = (xy % 0x50) << 3,
OffsetY = xy / 0x50,
HasPalette = header[0xC] != 0,
PlaneMap = header[0xE],
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Giz3Reader (file, (GizMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Giz3Format.Write not implemented");
}
}
internal class Giz3Reader
{
IBinaryStream m_input;
GizMetaData m_info;
BitmapPalette m_palette;
int m_stride;
int m_output_stride;
public Giz3Reader (IBinaryStream input, GizMetaData info)
{
m_input = input;
m_info = info;
}
byte[] m_buffer;
byte[] m_output;
public ImageData Unpack ()
{
m_input.Position = 0x10;
if (m_info.HasPalette)
m_palette = ReadPalette();
else
m_palette = BitmapPalettes.Gray16; // palette is stored somewhere else
long data_pos = m_input.Position;
ReadHuffmanTree();
data_pos += m_dataOffset;
m_bitCount = 1;
m_stride = m_info.iWidth >> 3;
m_buffer = new byte[0x2000];
m_output_stride = m_info.iWidth >> 1;
m_output = new byte[m_output_stride * m_info.iHeight];
m_input.Position = data_pos;
UnpackBits();
return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, m_output, m_output_stride);
}
int m_dataOffset;
int m_gizColumn;
int m_outputPos1;
int m_outputPos2;
void UnpackBits () // sub_15426
{
m_gizColumn = 0;
m_outputPos1 = 0;
m_outputPos2 = 0;
int dst = 0;
for (int x = 0; x < m_stride; ++x)
{
int src1 = m_outputPos1;
int src2 = 0;
for (int j = 0; j < 2; ++j)
{
src2 = m_outputPos1;
int plane_mask = 1;
for (int i = 0; i < 4; ++i)
{
if ((m_info.PlaneMap & plane_mask) == 0)
{
UnpackPlane (m_outputPos1);
}
plane_mask <<= 1;
m_outputPos1 += 0x800;
m_outputPos2 += 0x800;
}
m_gizColumn = (m_gizColumn + 1) & 3;
m_outputPos1 = m_gizColumn << 9;
m_outputPos2 = 0;
}
CopyPlanes (src1, src2, dst);
dst += 4;
}
}
void CopyPlanes (int src1, int src2, int dst)
{
for (int y = 0; y < m_info.iHeight; ++y)
{
int b0 = m_buffer[src1+y ] << 4 | m_buffer[src2+y ];
int b1 = m_buffer[src1+y+0x0800] << 4 | m_buffer[src2+y+0x0800];
int b2 = m_buffer[src1+y+0x1000] << 4 | m_buffer[src2+y+0x1000];
int b3 = m_buffer[src1+y+0x1800] << 4 | m_buffer[src2+y+0x1800];
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
m_output[dst+j/2] = px;
}
dst += m_output_stride;
}
}
int m_root;
ushort[] m_treeTable;
void ReadHuffmanTree ()
{
m_dataOffset = m_input.ReadUInt16();
m_treeTable = new ushort[(m_dataOffset-2) * 2 / 3 + 1];
int di = 0;
for (int si = 2; si + 2 < m_dataOffset; si += 3)
{
ushort bx = m_input.ReadUInt16();
int ax = bx & 0xFFF;
if ((ax & 0x800) == 0)
ax = (ax - 2) >> 1;
m_treeTable[di++] = (ushort)ax;
ax = m_input.ReadUInt8() << 4;
ax |= bx >> 12;
if ((ax & 0x800) == 0)
ax = (ax - 2) >> 1;
m_treeTable[di++] = (ushort)ax;
}
m_root = di - 2;
}
byte ReadToken ()
{
int token = m_root;
do
{
if (GetNextBit())
++token;
token = m_treeTable[token];
}
while ((token & 0x800) == 0);
return (byte)token;
}
void UnpackPlane (int dst)
{
int y = 0;
while (y < m_info.iHeight)
{
byte ctl = ReadToken();
if (ctl < 0x10)
{
m_buffer[dst++] = ctl;
++y;
}
else
{
int count = ReadToken() + 2;
ctl -= 0x10;
switch (ctl)
{
case 0:
for (int i = 0; i < count; ++i)
m_buffer[dst+i] = 0;
break;
case 1:
for (int i = 0; i < count; ++i)
m_buffer[dst+i] = 0xF;
break;
case 2:
Binary.CopyOverlapped (m_buffer, dst-1, dst, count);
break;
case 3:
Binary.CopyOverlapped (m_buffer, dst-2, dst, count);
break;
case 4:
case 5:
case 6:
{
int off = (ctl - 3) << 11;
Binary.CopyOverlapped (m_buffer, dst-off, dst, count);
break;
}
case 7:
{
int src = dst - m_outputPos1;
int ax = (m_gizColumn - 1) & 3;
src += (ax << 9) + m_outputPos2;
Binary.CopyOverlapped (m_buffer, src, dst, count);
break;
}
case 8:
{
int src = dst - m_outputPos1;
int ax = (m_gizColumn - 2) & 3;
src += (ax << 9) + m_outputPos2;
Binary.CopyOverlapped (m_buffer, src, dst, count);
break;
}
}
dst += count;
y += count;
}
}
}
BitmapPalette ReadPalette ()
{
const int count = 16;
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
byte b = m_input.ReadUInt8();
byte r = m_input.ReadUInt8();
byte g = m_input.ReadUInt8();
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
int m_bitCount;
int m_bits;
bool GetNextBit ()
{
if (--m_bitCount == 0)
{
m_bits = m_input.ReadUInt16();
m_bitCount = 16;
}
bool bit = (m_bits & 0x8000) != 0;
m_bits <<= 1;
return bit;
}
}
}

231
Legacy/Adviz/ImageGIZ2.cs Normal file
View File

@ -0,0 +1,231 @@
//! \file ImageGIZ2.cs
//! \date 2023 Oct 02
//! \brief ADVIZ engine image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [951027][Ange] Leap Toki ni Sarawareta Shoujo
namespace GameRes.Formats.Adviz
{
[Export(typeof(ImageFormat))]
public class GizFormat : ImageFormat
{
public override string Tag => "GIZ/2";
public override string Description => "ADVIZ engine image format";
public override uint Signature => 0x325A4947; // 'GIZ2'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
int xy = header.ToUInt16 (4);
return new GizMetaData {
Width = (uint)header.ToUInt16 (6) << 3,
Height = header.ToUInt16 (8),
OffsetX = (xy % 0x50) << 3,
OffsetY = xy / 0x50,
RleCode = header[0xC],
PlaneMap = header[0xE],
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Giz2Reader (file, (GizMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GizFormat.Write not implemented");
}
}
internal class Giz2Reader
{
IBinaryStream m_input;
GizMetaData m_info;
BitmapPalette m_palette;
public BitmapPalette Palette => m_palette;
public Giz2Reader (IBinaryStream input, GizMetaData info)
{
m_input = input;
m_info = info;
}
int m_stride;
byte[][] m_planes;
int m_output_stride;
byte[] m_output;
public ImageData Unpack ()
{
m_palette = BizFormat.ReadPalette (m_input.Name, 0x30, (pal, off) => ReadPalette (pal, off));
if (null == m_palette)
{
// m_palette = BitmapPalettes.Gray16;
throw new FileNotFoundException ("Unable to retrieve palette.");
}
m_input.Position = 0x10;
m_stride = m_info.iWidth >> 3;
int plane_size = m_info.iHeight;
m_planes = new byte[][] {
new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size],
};
m_output_stride = m_info.iWidth >> 1;
m_output = new byte[m_output_stride * m_info.iHeight];
int dst = 0;
for (int x = 0; x < m_stride; ++x)
{
int plane_mask = 1;
for (int i = 0; i < 4; ++i)
{
if ((m_info.PlaneMap & plane_mask) == 0)
UnpackPlane (m_planes[i], 0);
plane_mask <<= 1;
}
CopyPlanes (dst);
dst += 4;
}
return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, m_output, m_output_stride);
}
bool UnpackPlane (byte[] output, int dst)
{
for (int y = 0; y < m_info.iHeight; )
{
byte b = m_input.ReadUInt8();
int ctl = (b - m_info.RleCode) & 0xFF;
if (2 == ctl)
{
output[dst++] = m_input.ReadUInt8();
}
else if (ctl < 4)
{
if (0 == ctl)
b = 0;
else if (1 == ctl)
b = 0xFF;
else
b = m_input.ReadUInt8();
int count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1;
y += count;
while (count --> 0)
{
output[dst++] = b;
}
continue;
}
else if (ctl < 7)
{
byte b0 = m_input.ReadUInt8();
byte b1 = m_input.ReadUInt8();
int count;
if (4 == ctl)
{
count = ((b1 - 1) & 0x7F) + 1;
if (b1 < 0x80)
b1 = Binary.RotByteL (b0, 1);
else
b1 = Binary.RotByteR (b0, 1);
}
else if (5 == ctl)
{
count = ((b1 - 1) & 0x7F) + 1;
if (b1 < 0x80)
b1 = Binary.RotByteL (b0, 2);
else
b1 = Binary.RotByteR (b0, 2);
}
else
{
count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1;
count *= 2;
}
y += count;
do
{
output[dst++] = b0;
if (--count <= 0)
break;
output[dst++] = b1;
}
while (--count > 0);
continue;
}
else
{
output[dst++] = b;
}
++y;
}
return true;
}
void CopyPlanes (int dst)
{
for (int y = 0; y < m_info.iHeight; ++y)
{
int b0 = m_planes[0][y];
int b1 = m_planes[1][y];
int b2 = m_planes[2][y];
int b3 = m_planes[3][y];
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
m_output[dst+j/2] = px;
}
dst += m_output_stride;
}
}
BitmapPalette ReadPalette (ArcView file, int offset)
{
const int count = 16;
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
byte b = file.View.ReadByte (offset++);
byte r = file.View.ReadByte (offset++);
byte g = file.View.ReadByte (offset++);
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
}
}

99
Legacy/AyPio/ArcDLB.cs Normal file
View File

@ -0,0 +1,99 @@
//! \file ArcDLB.cs
//! \date 2023 Oct 13
//! \brief AyPio resource archive (PC-98).
//
// 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;
namespace GameRes.Formats.AyPio
{
[Export(typeof(ArchiveFormat))]
public class DlbOpener : ArchiveFormat
{
public override string Tag => "DLB";
public override string Description => "UK2 engine resource archive";
public override uint Signature => 0x64203C3C; // '<< d'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (3, "dlb file Ver1.00>>\0"))
return null;
int count = file.View.ReadInt16 (0x16);
if (!IsSaneCount (count))
return null;
using (var index = file.CreateStream())
{
index.Position = 0x18;
var dir = ReadIndex (index, count);
return new ArcFile (file, this, dir);
}
}
internal List<Entry> ReadIndex (IBinaryStream index, int count)
{
var max_offset = index.Length;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = index.ReadCString (0xD);
var entry = Create<Entry> (name);
entry.Offset = index.ReadUInt32();
entry.Size = index.ReadUInt32();
if (!entry.CheckPlacement (max_offset))
return null;
dir.Add (entry);
}
return dir;
}
}
[Export(typeof(ArchiveFormat))]
public class Dlb0Opener : DlbOpener
{
public override string Tag => "DLB/V0";
public override string Description => "UK2 engine resource archive";
public override uint Signature => 0;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasExtension (".DLB"))
return null;
int count = file.View.ReadInt16 (0);
if (!IsSaneCount (count))
return null;
uint first_offset = file.View.ReadUInt32 (0xF);
if (first_offset != count * 0x15 + 2)
return null;
using (var index = file.CreateStream())
{
index.Position = 2;
var dir = ReadIndex (index, count);
return new ArcFile (file, this, dir);
}
}
}
}

192
Legacy/AyPio/AudioVOC.cs Normal file
View File

@ -0,0 +1,192 @@
//! \file AudioVOC.cs
//! \date 2023 Oct 19
//! \brief AyPio ADPCM-compressed audio.
//
// 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.ComponentModel.Composition;
namespace GameRes.Formats.AyPio
{
[Export(typeof(AudioFormat))]
public class VocAudio : AudioFormat
{
public override string Tag => "VOC/UK2";
public override string Description => "UK2 engine compressed audio";
public override uint Signature => 0x81564157; // 'WAV\x81'
public override bool CanWrite => false;
public override SoundInput TryOpen (IBinaryStream file)
{
var header = file.ReadHeader (0x3C);
if (!header.AsciiEqual (0x38, "RIFF"))
return null;
var decoder = new VocDecoder (file);
var samples = decoder.Decode();
var stream = new BinMemoryStream (samples, file.Name);
file.Dispose();
return new RawPcmInput (stream, decoder.Format);
}
}
internal sealed class VocDecoder
{
IBinaryStream m_input;
int m_sample_count;
byte m_channels;
byte m_bits_per_sample;
byte[] m_prev_sample = new byte[2];
long m_start_pos;
public WaveFormat Format { get; private set; }
public VocDecoder (IBinaryStream input)
{
m_input = input;
var header = input.ReadHeader (0x38);
Format = new WaveFormat {
FormatTag = header.ToUInt16 (0x21),
Channels = header.ToUInt16 (0x23),
SamplesPerSecond = header.ToUInt32 (0x25),
AverageBytesPerSecond = header.ToUInt32 (0x29),
BlockAlign = header.ToUInt16 (0x2D),
BitsPerSample = header.ToUInt16 (0x2F),
};
m_sample_count = header.ToInt32 (0x18);
m_channels = header[8];
m_prev_sample[0] = header[0xC];
m_prev_sample[1] = header[0x10];
m_bits_per_sample = header[0x20];
m_output = new byte[m_sample_count << 1];
m_output[0] = header[0xA];
m_output[1] = header[0xB];
m_output[2] = header[0xE];
m_output[3] = header[0xF];
m_start_pos = header.ToUInt32 (4) + header.ToUInt32 (0x14);
}
byte[] m_output;
int[] m_samples;
int m_src;
public byte[] Decode ()
{
m_input.Position = m_start_pos;
m_src = 0;
BuildSamples();
int count = m_sample_count - m_channels;
int src = 0;
int pos = 0;
while (src < count)
{
byte sample = GetSample();
int v7 = sample & 7;
int channel;
if (m_channels == 1 || (src & 1) == 0)
channel = 0;
else
channel = 1;
byte prev = m_prev_sample[channel];
int s = m_samples[89 * v7 + prev];
if ((sample & 8) != 0)
s = -s;
s += m_output.ToInt16 (pos);
pos += 2;
LittleEndian.Pack (Clamp (s), m_output, pos);
int p = IndexTable[v7] + prev;
if (p < 0)
p = 0;
else if (p > 88)
p = 88;
m_prev_sample[channel] = (byte)p;
++src;
}
return m_output;
}
byte m_current_sample;
byte GetSample ()
{
if (0 == (m_src & 1))
m_current_sample = m_input.ReadUInt8();
++m_src;
byte sample = m_current_sample;
m_current_sample >>= 4;
return sample &= 0xF;
}
short Clamp (int sample)
{
if (sample > 0x7FFF)
sample = 0x7FFF;
else if (sample < -0x8000)
sample = -0x8000;
return (short)sample;
}
void BuildSamples ()
{
int b = 1 << (m_bits_per_sample - 1);
int i = 0;
m_samples = new int[89 * b];
while (i < 89)
{
int ii = i;
int j = 0;
while (j < b)
{
double d = 0.0;
int a = 1;
int c = b;
do
{
if (j % a >= a / 2)
{
d += (double)StepTable[ii] / (double)c;
}
a <<= 1;
c >>= 1;
}
while (a <= b);
++j;
m_samples[i] = (int)d;
i += 89;
}
i = ii + 1;
}
}
static readonly short[] StepTable = {
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1C, 0x1F,
0x22, 0x25, 0x29, 0x2D, 0x32, 0x37, 0x3C, 0x42, 0x49, 0x50, 0x58, 0x61, 0x6B, 0x76, 0x82, 0x8F,
0x9D, 0x0AD, 0x0BE, 0x0D1, 0x0E6, 0x0FD, 0x117, 0x133, 0x151, 0x173, 0x198, 0x1C1, 0x1EE, 0x220,
0x256, 0x292, 0x2D4, 0x31C, 0x36C, 0x3C3, 0x424, 0x48E, 0x502, 0x583, 0x610, 0x6AB, 0x756, 0x812,
0x8E0, 0x9C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E,
0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF,
};
static readonly sbyte[] IndexTable = { -1, -1, -1, -1, 1, 2, 3, 4 };
}
}

204
Legacy/AyPio/ImagePDT.cs Normal file
View File

@ -0,0 +1,204 @@
//! \file ImagePDT.cs
//! \date 2023 Oct 13
//! \brief AyPio image format (PC-98).
//
// 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;
using System.Windows.Media.Imaging;
// [960726][AyPio] Chuushaki
// [970314][AyPio] Stars
namespace GameRes.Formats.AyPio
{
internal class PdtMetaData : ImageMetaData
{
public byte Rle1;
public byte Rle2;
}
[Export(typeof(ImageFormat))]
public class PdtFormat : ImageFormat
{
public override string Tag => "PDT/UK2";
public override string Description => "UK2 engine image format";
public override uint Signature => 0;
public PdtFormat ()
{
Extensions = new[] { "pdt", "anm" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (file.ReadByte() != 0x34)
return null;
file.Position = 0x21;
byte rle1 = file.ReadUInt8();
byte rle2 = file.ReadUInt8();
int left = file.ReadUInt16();
int top = file.ReadUInt16();
int right = file.ReadUInt16();
int bottom = file.ReadUInt16();
int width = (right - left + 1) << 3;
int height = (((bottom - top) >> 1) + 1) << 1;
if (width <= 0 || height <= 0 || width > 2048 || height > 512)
return null;
return new PdtMetaData {
Width = (uint)width,
Height = (uint)height,
OffsetX = left << 3,
OffsetY = top,
BPP = 4,
Rle1 = rle1,
Rle2 = rle2,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Pdt4Reader (file, (PdtMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("PdtFormat.Write not implemented");
}
}
internal class Pdt4Reader
{
IBinaryStream m_input;
PdtMetaData m_info;
public Pdt4Reader (IBinaryStream input, PdtMetaData info)
{
m_input = input;
m_info = info;
}
int m_stride;
int m_rows;
public ImageData Unpack ()
{
m_input.Position = 1;
var palette = ReadPalette();
m_input.Position = 0x2B;
int output_stride = m_info.iWidth >> 1;
int height = m_info.iHeight;
m_stride = m_info.iWidth >> 3;
m_rows = height >> 1;
int plane_size = m_stride * height;
var planes = new byte[][] {
new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size]
};
for (int i = 0; i < 4; ++i)
UnpackPlane (planes[i]);
var pixels = new byte[output_stride * height];
FlattenPlanes (planes, pixels);
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, pixels, output_stride);
}
void UnpackPlane (byte[] output)
{
for (int x = 0; x < m_stride; ++x)
{
int dst = x;
int y = 0;
while (y < m_rows)
{
int count = 1;
byte p0, p1;
byte ctl = m_input.ReadUInt8();
if (ctl == m_info.Rle1)
{
count = m_input.ReadUInt8();
p0 = m_input.ReadUInt8();
p1 = m_input.ReadUInt8();
}
else if (ctl == m_info.Rle2)
{
count = m_input.ReadUInt8();
p1 = p0 = m_input.ReadUInt8();
}
else
{
p0 = ctl;
p1 = m_input.ReadUInt8();
}
while (count --> 0)
{
output[dst] = p0;
dst += m_stride;
output[dst] = p1;
dst += m_stride;
++y;
}
}
}
}
void FlattenPlanes (byte[][] planes, byte[] output)
{
int plane_size = planes[0].Length;
int dst = 0;
for (int src = 0; src < plane_size; ++src)
{
int b0 = planes[0][src];
int b1 = planes[1][src];
int b2 = planes[2][src];
int b3 = planes[3][src];
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
output[dst++] = px;
}
}
}
BitmapPalette ReadPalette ()
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
ushort rgb = m_input.ReadUInt16();
int b = (rgb & 0xF) * 0x11;
int r = ((rgb >> 4) & 0xF) * 0x11;
int g = ((rgb >> 8) & 0xF) * 0x11;
colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
}
}

252
Legacy/AyPio/ImagePDT5.cs Normal file
View File

@ -0,0 +1,252 @@
//! \file ImagePDT5.cs
//! \date 2023 Oct 16
//! \brief AyPio 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;
// [960726][AyPio] Chuushaki
namespace GameRes.Formats.AyPio
{
[Export(typeof(ImageFormat))]
public class Pdt5Format : ImageFormat
{
public override string Tag => "PDT/5";
public override string Description => "UK2 engine image format";
public override uint Signature => 0;
public Pdt5Format ()
{
Extensions = new[] { "pdt", "anm" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (file.ReadByte() != 0x35)
return null;
file.Position = 0x21;
int left = file.ReadUInt16();
int top = file.ReadUInt16();
int right = file.ReadUInt16();
int bottom = file.ReadUInt16();
int width = (right - left + 1) << 3;
int height = bottom - top + 1;
if (width <= 0 || height <= 0 || width > 640 || height > 1024)
return null;
return new ImageMetaData {
Width = (uint)width,
Height = (uint)height,
OffsetX = left << 3,
OffsetY = top,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Pdt5Reader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Pdt5Format.Write not implemented");
}
}
internal class Pdt5Reader
{
IBinaryStream m_input;
ImageMetaData m_info;
public Pdt5Reader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
byte[] m_buffer;
public ImageData Unpack ()
{
m_input.Position = 1;
var palette = ReadPalette();
m_input.Position = 0x29;
int width = m_info.iWidth;
int height = m_info.iHeight;
int output_stride = m_info.iWidth;
var pixels = new byte[output_stride * height];
InitFrame();
InitBitReader();
byte px = 0;
m_buffer = new byte[1932];
int output_pos = 0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
if (GetNextBit() != 0)
{
if (GetNextBit() != 0)
{
if (GetNextBit() != 0)
{
int count = GetCount() + 2;
int pos = 1290 + x;
x += count - 1;
while (count --> 0)
{
m_buffer[pos++] = px;
}
}
else
{
int count = GetCount() + 1;
px = m_buffer[1289 + x];
int src = x + 1288;
int dst = x + 1290;
Binary.CopyOverlapped (m_buffer, src, dst, count * 2);
x += count * 2 - 1;
}
}
else
{
px = GetPixel (x);
m_buffer[x + 1290] = px;
}
}
else
{
int count = 0;
byte b = GetPixel (x);
while (GetNextBit() != 1)
++count;
int src = 0x10 * b + count;
px = m_frame[src];
m_buffer[x + 1290] = px;
while (count --> 0)
{
m_frame[src] = m_frame[src-1];
--src;
}
m_frame[src] = px;
}
}
Buffer.BlockCopy (m_buffer, 1290, pixels, output_pos, width);
output_pos += output_stride;
Buffer.BlockCopy (m_buffer, 644, m_buffer, 0, 1288);
}
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride);
}
byte GetPixel (int src)
{
byte px = m_buffer[src + 647];
if (m_buffer[src + 4] != px)
{
byte v = m_buffer[src + 2];
if (v != px)
{
px = m_buffer[src + 645];
if (px != v && m_buffer[src] != px)
return m_buffer[src + 2];
}
}
return px;
}
byte[] m_frame;
void InitFrame ()
{
m_frame = new byte[0x110];
for (int j = 0; j < 0x110; j += 0x10)
{
for (byte i = 0; i < 0x10; ++i)
m_frame[j + i] = i;
}
}
int GetCount ()
{
int count = 0;
int bits = 1;
while (GetNextBit() != 1)
{
count += bits;
bits <<= 1;
}
if (bits > 1)
{
do
{
if (GetNextBit() != 0)
count += bits;
bits >>= 1;
}
while (bits != 0);
}
return count;
}
uint m_bits;
int m_bit_count;
void InitBitReader ()
{
m_bit_count = 1;
}
byte GetNextBit ()
{
if (--m_bit_count <= 0)
{
m_bits = m_input.ReadUInt8();
m_bit_count = 8;
}
uint bit = m_bits & 1;
m_bits >>= 1;
return (byte)bit;
}
BitmapPalette ReadPalette ()
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
ushort rgb = m_input.ReadUInt16();
int b = (rgb & 0xF) * 0x11;
int r = ((rgb >> 4) & 0xF) * 0x11;
int g = ((rgb >> 8) & 0xF) * 0x11;
colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
}
}

221
Legacy/AyPio/PdtBitmap.cs Normal file
View File

@ -0,0 +1,221 @@
//! \file PdtBitmap.cs
//! \date 2023 Oct 19
//! \brief UK2 engine compressed bitmap.
//
// 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;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [971031][AyPio] Satyr 95
namespace GameRes.Formats.AyPio
{
[Export(typeof(ImageFormat))]
public class PdtBmpFormat : ImageFormat
{
public override string Tag => "PDT/BMP";
public override string Description => "UK2 engine compressed bitmap";
public override uint Signature => 0x544450; // 'PDT'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (8);
if (header.ToInt32 (4) != 0x118)
return null;
return new ImageMetaData { Width = 640, Height = 480, BPP = 32 };
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var decoder = new PdtBmpDecoder (file, info);
return decoder.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("PdtFormat.Write not implemented");
}
}
internal sealed class PdtBmpDecoder
{
IBinaryStream m_input;
ImageMetaData m_info;
public PdtBmpDecoder (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
int m_unpacked_size;
int m_packed_size;
public ImageData Unpack ()
{
long offset = 0;
var bitmap = UnpackBitmap (offset);
m_info.Width = (uint)bitmap.PixelWidth;
m_info.Height = (uint)bitmap.PixelHeight;
m_info.BPP = bitmap.Format.BitsPerPixel;
offset += m_packed_size;
var signature = m_input.ReadBytes (4);
if (signature.Length != 4 || !signature.AsciiEqual ("PDT\0"))
return new ImageData (bitmap, m_info);
var alpha = UnpackBitmap (offset);
if (alpha.Format != PixelFormats.Gray8)
alpha = new FormatConvertedBitmap (alpha, PixelFormats.Gray8, null, 0);
if (m_info.BPP != 32)
bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0);
int stride = m_info.iWidth * 4;
var pixels = new byte[stride * m_info.iHeight];
bitmap.CopyPixels (pixels, stride, 0);
var rect = new Int32Rect (0, 0, Math.Min (m_info.iWidth, alpha.PixelWidth),
Math.Min (m_info.iHeight, alpha.PixelHeight));
var a = new byte[m_info.iWidth * m_info.iHeight];
alpha.CopyPixels (rect, a, m_info.iWidth, 0);
int src = 0;
for (int dst = 3; dst < pixels.Length; dst += 4)
{
pixels[dst] = a[src++];
}
return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, stride);
}
byte[] m_bits;
byte[] m_output;
BitmapSource UnpackBitmap (long offset)
{
m_input.Position = offset+8;
m_unpacked_size = m_input.ReadInt32();
m_packed_size = m_input.ReadInt32();
long data_offset = m_input.ReadUInt32() + offset;
long bits_offset = m_input.ReadUInt32() + offset;
string name = m_input.ReadCString (0x100);
if (null == m_output || m_unpacked_size > m_output.Length)
m_output = new byte[m_unpacked_size];
int bits_length = (int)(data_offset - bits_offset);
if (null == m_bits || bits_length > m_bits.Length)
m_bits = new byte[bits_length+4];
m_input.Position = bits_offset;
m_input.Read (m_bits, 0, bits_length);
m_input.Position = data_offset;
UnpackBits();
using (var bmp_input = new BinMemoryStream (m_output, name))
{
var decoder = new BmpBitmapDecoder (bmp_input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
return decoder.Frames[0];
}
}
public void UnpackBits ()
{
InitBitReader();
int dst = 0;
byte last_byte = 0;
while (dst < m_unpacked_size)
{
int ctl = 0;
while (GetNextBit() != 0)
++ctl;
switch (ctl)
{
case 0:
last_byte = m_output[dst++] = m_input.ReadUInt8();
break;
case 1:
{
int off = GetInteger();
int count = GetInteger();
Binary.CopyOverlapped (m_output, dst - off, dst, count);
dst += count;
break;
}
case 2:
{
int count = GetInteger();
int step = GetInteger();
int pos = 0;
for (int i = 0; i < step; i += count)
{
Binary.CopyOverlapped (m_output, dst - count, dst + pos, count);
pos += count * count;
}
dst += count * step;
break;
}
case 3:
m_output[dst++] = last_byte;
break;
}
}
}
int GetInteger ()
{
int i = 0;
while (GetNextBit() != 0)
++i;
int n = 0;
for (int j = i; j > 0; --j)
{
n = n << 1 | GetNextBit();
}
return n + (1 << i);
}
uint m_current_bits;
int m_bit_count;
int m_bit_pos;
void InitBitReader ()
{
m_bit_pos = 0;
m_bit_count = 0;
}
byte GetNextBit ()
{
if (0 == m_bit_count--)
{
m_current_bits = m_bits.ToUInt32 (m_bit_pos);
m_bit_pos += 4;
m_bit_count = 31;
}
uint bit = m_current_bits >> 31;
m_current_bits <<= 1;
return (byte)bit;
}
}
}

41
Legacy/Blucky/Aliases.cs Normal file
View File

@ -0,0 +1,41 @@
//! \file Aliases.cs
//! \date 2023 Sep 17
//! \brief Blucky formats aliases.
//
// 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;
// [970627][Blucky] Rekiai
namespace GameRes.Formats.Blucky
{
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "OSA")]
[ExportMetadata("Target", "BMP")]
public class OsaFormat : ResourceAlias { }
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "WF")]
[ExportMetadata("Target", "WAV")]
public class WfFormat : ResourceAlias { }
}

89
Legacy/DMotion/ArcDM.cs Normal file
View File

@ -0,0 +1,89 @@
//! \file ArcDM.cs
//! \date 2023 Oct 24
//! \brief D-Motion 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;
namespace GameRes.Formats.DMotion
{
internal class ExtEntry : Entry
{
public int Count;
}
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag => "256/DMOTION";
public override string Description => "D-Motion engine resource archive";
public override uint Signature => 0x4B434150; // 'PACK'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "FILE100DATA"))
return null;
if (!file.View.AsciiEqual (0x10, @".\\\"))
return null;
int ext_count = file.View.ReadUInt16 (0x16);
long index_pos = file.View.ReadUInt32 (0x18);
int total_count = 0;
var ext_dir = new List<ExtEntry> (ext_count);
for (int i = 0; i < ext_count; ++i)
{
var ext = new ExtEntry {
Name = file.View.ReadString (index_pos, 4),
Count = file.View.ReadUInt16 (index_pos+6),
Offset = file.View.ReadUInt32 (index_pos+8),
Size = file.View.ReadUInt32 (index_pos+12),
};
ext_dir.Add (ext);
total_count += ext.Count;
index_pos += 0x10;
}
if (!IsSaneCount (total_count))
return null;
var dir = new List<Entry> (total_count);
foreach (var ext in ext_dir)
{
index_pos = ext.Offset;
for (int i = 0; i < ext.Count; ++i)
{
var name = file.View.ReadString (index_pos, 8).TrimEnd() + ext.Name;
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos+8);
entry.Size = file.View.ReadUInt32 (index_pos+12);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 0x10;
}
}
return new ArcFile (file, this, dir);
}
}
}

85
Legacy/Desire/ArcDSV.cs Normal file
View File

@ -0,0 +1,85 @@
//! \file ArcDSV.cs
//! \date 2023 Oct 15
//! \brief Desire resource archive (PC-98).
//
// 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;
namespace GameRes.Formats.Desire
{
[Export(typeof(ArchiveFormat))]
public class D000Opener : ArchiveFormat
{
public override string Tag => "000/DESIRE";
public override string Description => "Desire resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasAnyOfExtensions (".000", ".001", ".002", ".003"))
return null;
if (!IsAscii (file.View.ReadByte (0)))
return null;
uint index_pos = 0;
var dir = new List<Entry>();
while (index_pos < file.MaxOffset)
{
byte b = file.View.ReadByte (index_pos);
if (0 == b)
break;
if (!IsAscii (b))
return null;
var name = file.View.ReadString (index_pos, 0xC);
var entry = Create<Entry> (name);
entry.Size = file.View.ReadUInt32 (index_pos+0xC);
if (entry.Size >= file.MaxOffset || 0 == entry.Size)
return null;
dir.Add (entry);
index_pos += 0x10;
}
if (index_pos >= file.MaxOffset || file.View.ReadUInt32 (index_pos+0xC) != 0)
return null;
uint offset = index_pos + 0x10;
foreach (var entry in dir)
{
entry.Offset = offset;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
offset += entry.Size;
}
if (offset != file.MaxOffset)
return null;
return new ArcFile (file, this, dir);
}
static internal bool IsAscii (byte b)
{
return b >= 0x20 && b < 0x7F;
}
}
}

74
Legacy/Desire/ImageDES.cs Normal file
View File

@ -0,0 +1,74 @@
//! \file ImageDES.cs
//! \date 2023 Oct 15
//! \brief DES98 engine image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
// [940720][Desire] H+
namespace GameRes.Formats.Desire
{
[Export(typeof(ImageFormat))]
public class DesFormat : ImageFormat
{
public override string Tag => "DES98";
public override string Description => "Des98 engine image format";
public override uint Signature => 0;
public DesFormat ()
{
Extensions = new[] { "" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
ushort width = Binary.BigEndian (file.ReadUInt16());
ushort height = Binary.BigEndian (file.ReadUInt16());
if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400)
return null;
return new ImageMetaData {
Width = width,
Height = height,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 4;
var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb);
var reader = new System98.GraBaseReader (file, info);
reader.UnpackBits();
return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("DesFormat.Write not implemented");
}
}
}

93
Legacy/Desire/ImageDPC.cs Normal file
View File

@ -0,0 +1,93 @@
//! \file ImageDPC.cs
//! \date 2023 Oct 15
//! \brief Desire image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [970801][Desire] Yuugiri ~Ningyoushi no Isan~
namespace GameRes.Formats.Desire
{
[Export(typeof(ImageFormat))]
public class DpcFormat : ImageFormat
{
public override string Tag => "DPC";
public override string Description => "Desire image format";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".DPC"))
return null;
file.Position = 0x20;
short left = file.ReadInt16();
short top = file.ReadInt16();
ushort width = file.ReadUInt16();
ushort height = file.ReadUInt16();
if (0 == width || 0 == height || left < 0 || left + width > 2048 || top < 0 || top + height > 2048)
return null;
return new ImageMetaData {
Width = width,
Height = height,
OffsetX = left,
OffsetY = top,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var palette = ReadPalette (file);
file.Position = 0x28;
var reader = new System98.GraBaseReader (file, info);
reader.UnpackBits();
return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
}
BitmapPalette ReadPalette (IBinaryStream input)
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
ushort w = input.ReadUInt16();
int alpha = (w & 1) == 0 ? 0xFF : 0;
int g = (w >> 12) * 0x11;
int r = ((w >> 7) & 0xF) * 0x11;
int b = ((w >> 2) & 0xF) * 0x11;
colors[i] = Color.FromArgb ((byte)alpha, (byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("DpcFormat.Write not implemented");
}
}
}

View File

@ -0,0 +1,80 @@
//! \file ImageAN1.cs
//! \date 2023 Oct 05
//! \brief Discovery animation resource (PC-98).
//
// 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.Discovery
{
//[Export(typeof(ImageFormat))]
public class An1Format : Pr1Format
{
public override string Tag => "AN1";
public override string Description => "Discovery animation resource";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".AN1"))
return null;
return base.ReadMetaData (file);
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new AnReader (file, (PrMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("An1Format.Write not implemented");
}
}
internal class AnReader : PrReader
{
public AnReader (IBinaryStream file, PrMetaData info) : base (file, info)
{
}
public new ImageData Unpack ()
{
UnpackPlanes();
int frame_count = m_planes[0].ToUInt16 (2);
int frame_width = 0x20;
int frame_height = frame_count * 0x20;
int output_stride = frame_width >> 1;
var output = new byte[output_stride * frame_height];
int src = frame_count * 0x16 + 6;
m_plane_size = (output_stride >> 2) * frame_height;
FlattenPlanes (src, output);
Info.Width = (uint)frame_width;
Info.Height = (uint)frame_height;
return ImageData.Create (Info, PixelFormats.Indexed4, m_palette, output, output_stride);
}
}
}

View File

@ -0,0 +1,343 @@
//! \file ImagePR1.cs
//! \date 2023 Oct 04
//! \brief Discovery image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Discovery
{
internal class PrMetaData : ImageMetaData
{
public byte Flags;
public byte Mask;
public bool IsLeftToRight => (Flags & 1) != 0;
}
[Export(typeof(ImageFormat))]
public class Pr1Format : ImageFormat
{
public override string Tag => "PR1";
public override string Description => "Discovery image format";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasAnyOfExtensions (".PR1", ".AN1"))
return null;
var header = file.ReadHeader (12);
return new PrMetaData {
Width = (uint)header.ToUInt16 (8) << 3,
Height = header.ToUInt16 (0xA),
OffsetX = header.ToUInt16 (2),
OffsetY = header.ToUInt16 (4),
Flags = header[0],
Mask = header[1],
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new PrReader (file, (PrMetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Pr1Format.Write not implemented");
}
}
internal class PrReader
{
IBinaryStream m_input;
PrMetaData m_info;
Action IncrementDest;
Func<bool> IsDone;
public PrMetaData Info => m_info;
public PrReader (IBinaryStream file, PrMetaData info)
{
m_input = file;
m_info = info;
if (m_info.IsLeftToRight)
{
IncrementDest = IncLeftToRight;
IsDone = () => m_dst >= m_plane_size;
}
else
{
IncrementDest = IncTopToBottom;
IsDone = () => m_x >= m_stride;
}
}
protected BitmapPalette m_palette;
protected int m_stride;
protected int m_plane_size;
protected byte[][] m_planes;
int m_dst;
int m_x;
protected void UnpackPlanes ()
{
const int buffer_slice = 0x410;
m_input.Position = 0xC;
m_palette = ReadPalette();
m_stride = m_info.iWidth >> 3;
m_plane_size = m_stride * m_info.iHeight;
m_planes = new byte[][] {
new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size],
};
var buffer = new byte[buffer_slice * 4];
var buf_count = new byte[4];
var offsets = new int[] { 0, buffer_slice, buffer_slice*2, buffer_slice*3 };
m_dst = 0;
m_x = 0;
while (!IsDone())
{
int ctl = m_input.ReadByte();
if (-1 == ctl)
break;
int count = (ctl & 0x1F) + 1;
bool bit = (ctl & 0x20) != 0;
ctl >>= 6;
if (!bit)
{
if (ctl != 0)
{
int src_pos = ctl;
int src_count2 = 1 << (ctl - 1);
int pos = offsets[ctl];
int count2 = src_count2;
do
{
byte p0 = m_input.ReadUInt8();
byte p1 = m_input.ReadUInt8();
byte p2 = m_input.ReadUInt8();
byte p3 = m_input.ReadUInt8();
PutPixels (p0, p1, p2, p3);
buffer[pos++] = p0;
buffer[pos++] = p1;
buffer[pos++] = p2;
buffer[pos++] = p3;
}
while (--count > 0 && --count2 > 0);
while (count > 0)
{
int si = offsets[src_pos];
for (int i = 0; i < src_count2; ++i)
{
byte p0 = buffer[si++];
byte p1 = buffer[si++];
byte p2 = buffer[si++];
byte p3 = buffer[si++];
PutPixels (p0, p1, p2, p3);
if (--count <= 0)
break;
}
}
offsets[src_pos] += src_count2 * 4;
buf_count[src_pos] += (byte)src_count2;
if (buf_count[src_pos] == 0)
offsets[src_pos] = src_pos * buffer_slice;
}
else
{
while (count --> 0)
{
byte p0 = m_input.ReadUInt8();
byte p1 = m_input.ReadUInt8();
byte p2 = m_input.ReadUInt8();
byte p3 = m_input.ReadUInt8();
PutPixels (p0, p1, p2, p3);
int pos = offsets[0];
buffer[pos++] = p0;
buffer[pos++] = p1;
buffer[pos++] = p2;
buffer[pos++] = p3;
offsets[0] += 4;
buf_count[0]++;
if (0 == buf_count[0])
offsets[0] = 0;
}
}
}
else if (ctl != 0)
{
int count2 = 1 << (ctl - 1);
int off_diff = count2 << 2;
int off_mask = off_diff - 1;
int off = m_input.ReadUInt8() << 2;;
int base_pos = ctl * buffer_slice;
off += base_pos;
int src = off;
while (count > 0)
{
off = src;
for (int i = 0; i < count2; ++i)
{
byte p0 = buffer[off];
byte p1 = buffer[off+1];
byte p2 = buffer[off+2];
byte p3 = buffer[off+3];
PutPixels (p0, p1, p2, p3);
off += 4;
int pos = off - base_pos;
if ((pos & off_mask) == 0)
off -= off_diff;
if (--count <= 0)
break;
}
}
}
else
{
while (count --> 0)
{
int off = m_input.ReadUInt8() << 2;
byte p0 = buffer[off];
byte p1 = buffer[off+1];
byte p2 = buffer[off+2];
byte p3 = buffer[off+3];
PutPixels (p0, p1, p2, p3);
}
}
}
}
public ImageData Unpack ()
{
UnpackPlanes();
int output_stride = m_info.iWidth >> 1;
var output = new byte[output_stride * m_info.iHeight];
FlattenPlanes (0, output);
return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, output, output_stride);
}
void PutPixels (byte p0, byte p1, byte p2, byte p3)
{
if (0xFF == m_info.Mask || true) // we don't do overlaying here, just single image decoding
{
m_planes[0][m_dst] = p0;
m_planes[1][m_dst] = p1;
m_planes[2][m_dst] = p2;
m_planes[3][m_dst] = p3;
}
else
{
byte v = m_info.Mask;
byte mask = p0;
if ((v & 1) != 0)
mask = (byte)~mask;
if ((v & 2) != 0)
mask |= (byte)~p1;
else
mask |= p1;
if ((v & 4) != 0)
mask |= (byte)~p2;
else
mask |= p2;
if ((v & 8) != 0)
mask |= (byte)~p3;
else
mask |= p3;
p0 &= mask;
p1 &= mask;
p2 &= mask;
p3 &= mask;
mask = (byte)~mask;
m_planes[0][m_dst] &= mask;
m_planes[0][m_dst] |= p0;
m_planes[1][m_dst] &= mask;
m_planes[1][m_dst] |= p1;
m_planes[2][m_dst] &= mask;
m_planes[2][m_dst] |= p2;
m_planes[3][m_dst] &= mask;
m_planes[3][m_dst] |= p3;
}
IncrementDest();
}
void IncLeftToRight ()
{
++m_dst;
++m_x;
if (m_x > m_info.iWidth)
m_x = 0;
}
void IncTopToBottom ()
{
m_dst += m_stride;
if (m_dst >= m_plane_size)
m_dst = ++m_x;
}
internal void FlattenPlanes (int src, byte[] output)
{
int m_dst = 0;
for (; src < m_plane_size; ++src)
{
int b0 = m_planes[0][src];
int b1 = m_planes[1][src];
int b2 = m_planes[2][src];
int b3 = m_planes[3][src];
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
output[m_dst++] = px;
}
}
}
BitmapPalette ReadPalette ()
{
const int count = 16;
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
byte g = m_input.ReadUInt8();
byte r = m_input.ReadUInt8();
byte b = m_input.ReadUInt8();
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
}
}

193
Legacy/Grocer/ImagePIC.cs Normal file
View File

@ -0,0 +1,193 @@
//! \file ImagePIC.cs
//! \date 2023 Sep 25
//! \brief Grocer image format (PC-98).
//
// 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;
// [941209][Grocer] Wedding Errantry -Gyakutama Ou-
namespace GameRes.Formats.Grocer
{
[Export(typeof(ImageFormat))]
public class PicFormat : ImageFormat
{
public override string Tag => "PIC/GROCER";
public override string Description => "Grocer image format";
public override uint Signature => 1;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x57);
if (!header.AsciiEqual (0x10, "Actor98"))
return null;
uint width = (uint)header.ToUInt16 (0x53) << 3;
if (width > 640)
return null;
return new ImageMetaData
{
Width = width,
Height = header.ToUInt16 (0x55),
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new PicReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("PicFormat.Write not implemented");
}
}
internal class PicReader
{
IBinaryStream m_input;
ImageMetaData m_info;
public PicReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
public ImageData Unpack ()
{
m_input.Position = 0x21;
var palette = ReadPalette();
m_input.Position = 0x57;
int stride = m_info.iWidth / 8;
var pixels = new byte[m_info.iWidth * m_info.iHeight];
var buffer = new byte[0x3C0];
int output_pos = 0;
for (int y = 0; y < m_info.iHeight; ++y)
{
int x;
for (int plane = 0; plane < 4; ++plane)
{
x = 0;
while (x < stride)
{
byte cur_byte = m_input.ReadUInt8();
if (cur_byte > 0 && cur_byte < 6)
{
int count = m_input.ReadUInt8();
switch (cur_byte)
{
case 1:
{
cur_byte = m_input.ReadUInt8();
int dst = plane * 0x50 + x + 0x280;
for (int i = 0; i < count; ++i)
{
buffer[dst+i] = cur_byte;
}
break;
}
case 2:
{
int src = plane * 0x50 + x;
int dst = src + 0x280;
Buffer.BlockCopy (buffer, src, buffer, dst, count);
break;
}
case 3:
{
int src = x + 0x280;
int dst = plane * 0x50 + src;
Buffer.BlockCopy (buffer, src, buffer, dst, count);
break;
}
case 4:
{
int src = x + 0x2D0;
int dst = plane * 0x50 + x + 0x280;
Buffer.BlockCopy (buffer, src, buffer, dst, count);
break;
}
case 5:
{
int src = x + 0x320;
int dst = plane * 0x50 + x + 0x280;
Buffer.BlockCopy (buffer, src, buffer, dst, count);
break;
}
}
x += count;
}
else
{
if (6 == cur_byte)
{
cur_byte = m_input.ReadUInt8();
}
int dst = plane * 0x50 + x + 0x280;
buffer[dst] = cur_byte;
++x;
}
}
}
for (x = 0; x < stride; ++x)
{
byte mask = 0x80;
for (int i = 0; i < 8; ++i)
{
byte px = 0;
if ((buffer[x + 0x280] & mask) != 0) px |= 0x01;
if ((buffer[x + 0x2D0] & mask) != 0) px |= 0x02;
if ((buffer[x + 0x320] & mask) != 0) px |= 0x04;
if ((buffer[x + 0x370] & mask) != 0) px |= 0x08;
pixels[output_pos + (x << 3) + i] = px;
mask >>= 1;
}
}
Buffer.BlockCopy (buffer, 0x140, buffer, 0, 0x280);
output_pos += m_info.iWidth;
}
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels);
}
BitmapPalette ReadPalette ()
{
const int count = 16;
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
byte g = m_input.ReadUInt8();
byte r = m_input.ReadUInt8();
byte b = m_input.ReadUInt8();
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
}
}

65
Legacy/Harvest/ArcDAT.cs Normal file
View File

@ -0,0 +1,65 @@
//! \file ArcDAT.cs
//! \date 2023 Sep 26
//! \brief MyHarvest 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.MyHarvest
{
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag => "DAT/UNA";
public override string Description => "MyHarvest resource archive";
public override uint Signature => 0x414E55; // 'UNA'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "001\0"))
return null;
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
return null;
uint index = 0x20;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index, 0x20);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index+0x20);
entry.Size = file.View.ReadUInt32 (index+0x24);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index += 0x30;
}
return new ArcFile (file, this, dir);
}
}
}

View File

@ -0,0 +1,56 @@
//! \file AudioBGM.cs
//! \date 2023 Sep 26
//! \brief MyHarvest audio 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.ComponentModel.Composition;
namespace GameRes.Formats.MyHarvest
{
[Export(typeof(AudioFormat))]
public class BgmAudio : AudioFormat
{
public override string Tag => "BGM/HARVEST";
public override string Description => "MyHarvest audio resource";
public override uint Signature => 0x304D4742; // 'BMG0'
public override bool CanWrite => false;
public override SoundInput TryOpen (IBinaryStream file)
{
var header = file.ReadHeader (0x1C);
if (!header.AsciiEqual (0x14, "dar\0"))
return null;
var format = new WaveFormat {
FormatTag = header.ToUInt16 (4),
Channels = header.ToUInt16 (6),
SamplesPerSecond = header.ToUInt32 (8),
AverageBytesPerSecond = header.ToUInt32 (0xC),
BlockAlign = header.ToUInt16 (0x10),
BitsPerSample = header.ToUInt16 (0x12),
};
uint pcm_size = header.ToUInt32 (0x18);
var region = new StreamRegion (file.AsStream, 0x1C, pcm_size);
return new RawPcmInput (region, format);
}
}
}

View File

@ -0,0 +1,61 @@
//! \file AudioSED.cs
//! \date 2023 Sep 26
//! \brief MyHarvest audio 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.ComponentModel.Composition;
namespace GameRes.Formats.MyHarvest
{
[Export(typeof(AudioFormat))]
public class SedAudio : AudioFormat
{
public override string Tag => "SED/HARVEST";
public override string Description => "MyHarvest audio resource";
public override uint Signature => 0x14553; // 'SE'
public override bool CanWrite => false;
public SedAudio ()
{
Signatures = new[] { 0x14553u, 0u };
}
public override SoundInput TryOpen (IBinaryStream file)
{
var header = file.ReadHeader (0x18);
if (!header.AsciiEqual (0, "SE") || !header.AsciiEqual (0x12, "da"))
return null;
var format = new WaveFormat {
FormatTag = header.ToUInt16 (2),
Channels = header.ToUInt16 (4),
SamplesPerSecond = header.ToUInt32 (6),
AverageBytesPerSecond = header.ToUInt32 (0xA),
BlockAlign = header.ToUInt16 (0xE),
BitsPerSample = header.ToUInt16 (0x10),
};
uint pcm_size = header.ToUInt32 (0x14);
var region = new StreamRegion (file.AsStream, 0x18, pcm_size);
return new RawPcmInput (region, format);
}
}
}

View File

@ -0,0 +1,96 @@
//! \file ImageUNH.cs
//! \date 2023 Sep 26
//! \brief MyHarvest 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 System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
// [021206][MyHarvest] Idol Mahjong Final Romance 4
namespace GameRes.Formats.MyHarvest
{
[Export(typeof(ImageFormat))]
public class UnhFormat : ImageFormat
{
public override string Tag => "UNH";
public override string Description => "MyHarvest image format";
public override uint Signature => 0x30484E55; // 'UNH0'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x18);
if (header.ToInt32 (4) != 1)
return null;
return new ImageMetaData {
Width = header.ToUInt32 (0x10),
Height = header.ToUInt32 (0x14),
BPP = 16,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 0x44;
var pixels = new ushort[info.iWidth * info.iHeight];
var frame = new ushort[0x1000];
int frame_pos = 0;
int dst = 0;
byte mask = 0;
int ctl = 0;
while (dst < pixels.Length)
{
mask <<= 1;
if (0 == mask)
{
ctl = file.ReadByte();
if (-1 == ctl)
break;
mask = 1;
}
ushort word = file.ReadUInt16();
if ((ctl & mask) == 0)
{
pixels[dst++] = frame[frame_pos++ & 0xFFF] = word;
}
else
{
int offset = word >> 4;
int count = (word & 0xF) + 2;
while (count --> 0)
{
ushort u = frame[offset++ & 0xFFF];
pixels[dst++] = frame[frame_pos++ & 0xFFF] = u;
}
}
}
return ImageData.Create (info, PixelFormats.Bgr565, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("UnhFormat.Write not implemented");
}
}
}

436
Legacy/Izumi/ImageMAI2.cs Normal file
View File

@ -0,0 +1,436 @@
//! \file ImageMAI2.cs
//! \date 2023 Oct 24
//! \brief Izumi engine image format (PC-98).
//
// 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;
namespace GameRes.Formats.Izumi
{
internal class Mai2MetaData : ImageMetaData
{
public byte Flags;
public ushort Plane0Size;
public ushort Plane1Size;
public ushort Plane2Size;
public ushort Plane3Size;
public bool HasPalette => (Flags & 0x80) != 0;
}
[Export(typeof(ImageFormat))]
public class Mai2Format : ImageFormat
{
public override string Tag => "MAI/IZUMI";
public override string Description => "Izumi engine image format";
public override uint Signature => 0x3249414D; // 'MAI2'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x14);
ushort xy = header.ToUInt16 (4);
return new Mai2MetaData {
Width = (uint)(header.ToUInt16 (6) << 3),
Height = header.ToUInt16 (8),
OffsetX = xy % 0x50,
OffsetY = xy / 0x50,
BPP = 4,
Flags = header[0xA],
Plane0Size = header.ToUInt16 (0x0C),
Plane1Size = header.ToUInt16 (0x0E),
Plane2Size = header.ToUInt16 (0x10),
Plane3Size = header.ToUInt16 (0x12),
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Mai2Reader (file, (Mai2MetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Mai2Format.Write not implemented");
}
}
internal class Mai2Reader
{
IBinaryStream m_input;
Mai2MetaData m_info;
public Mai2Reader (IBinaryStream input, Mai2MetaData info)
{
m_input = input;
m_info = info;
}
byte[][] m_planes;
int m_stride;
int m_height;
public ImageData Unpack ()
{
m_input.Position = 0x14;
BitmapPalette palette = null;
if (m_info.HasPalette)
palette = ReadPalette();
else
palette = BitmapPalettes.Gray16;
m_height = m_info.iHeight;
m_stride = m_info.iWidth >> 3;
int plane_size = m_stride * m_info.iHeight;
m_planes = new byte[][] {
new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size],
};
long next_pos = m_input.Position + m_info.Plane0Size;
if ((m_info.Flags & 1) != 0)
UnpackPlane (m_planes[0]);
m_input.Position = next_pos;
next_pos += m_info.Plane1Size;
if ((m_info.Flags & 2) != 0)
UnpackPlane (m_planes[1]);
m_input.Position = next_pos;
next_pos += m_info.Plane2Size;
if ((m_info.Flags & 4) != 0)
UnpackPlane (m_planes[2]);
m_input.Position = next_pos;
if ((m_info.Flags & 8) != 0)
UnpackPlane (m_planes[3]);
int output_stride = m_info.iWidth >> 1;
var output = new byte[output_stride * m_info.iHeight];
FlattenPlanes (output, output_stride);
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, output_stride);
}
void UnpackPlane (byte[] plane)
{
int dst_row = 0;
for (int x = 0; x < m_stride; ++x)
{
int dst = dst_row;
int remaining = m_height;
while (remaining > 0)
{
int count = 1;
int ctl = m_input.ReadUInt8();
if (ctl < 0x90)
{
count = ctl & 0x1F;
if (0 == count)
count = m_input.ReadUInt8();
switch (ctl >> 5)
{
case 0:
for (int i = 0; i < count; ++i)
plane[dst++] = 0;
break;
case 1:
for (int i = 0; i < count; ++i)
plane[dst++] = 0xFF;
break;
case 2:
Buffer.BlockCopy (m_planes[0], dst, plane, dst, count);
dst += count;
break;
case 3:
Buffer.BlockCopy (m_planes[1], dst, plane, dst, count);
dst += count;
break;
case 4:
Buffer.BlockCopy (m_planes[2], dst, plane, dst, count);
dst += count;
break;
}
}
else if (ctl < 0xF0)
{
count = ctl & 0xF;
if (0 == count)
count = m_input.ReadUInt8();
int off = 0;
switch (ctl >> 4)
{
case 0x9: off = 0x10; break;
case 0xA: off = 8; break;
case 0xB: off = 4; break;
case 0xC: off = 2; break;
case 0xD: off = m_height << 1; break;
case 0xE: off = m_height; break;
}
Binary.CopyOverlapped (plane, dst - off, dst, count);
dst += count;
}
else if (ctl < 0xF9)
{
count = ctl & 0xF;
if (0 == count)
count = m_input.ReadUInt8();
m_input.Read (plane, dst, count);
dst += count;
}
else
{
count = m_input.ReadUInt8();
switch (ctl)
{
case 0xF9:
dst += count;
break;
case 0xFA:
{
byte b = m_input.ReadUInt8();
for (int i = 0; i < count; ++i)
plane[dst++] = b;
break;
}
case 0xFB:
{
int src = 0;
if ((count & 0x80) != 0)
{
count &= 0x7F;
src = 1;
}
if (0 == count)
count = m_input.ReadUInt8();
for (int i = 0; i < count; ++i)
{
plane[dst] = (byte)~m_planes[src][dst];
dst++;
}
break;
}
case 0xFC:
if ((count & 0x80) != 0)
{
count &= 0x7F;
if (0 == count)
count = m_input.ReadUInt8();
byte al, ah;
al = m_input.ReadUInt8();
ah = (byte)(al << 4 | al & 0x0F);
al = (byte)(al >> 4 | al & 0xF0);
for (int i = 0; i < count; ++i)
{
plane[dst++] = al;
plane[dst++] = ah;
}
count <<= 1;
}
else
{
if (0 == count)
count = m_input.ReadUInt8();
for (int i = 0; i < count; ++i)
{
plane[dst] = (byte)~m_planes[2][dst];
dst++;
}
}
break;
case 0xFD:
if ((count & 0x80) != 0)
{
ctl = count;
count = ctl & 0x3F;
if (0 == count)
count = m_input.ReadUInt8();
byte al, ah, bl, bh;
al = m_input.ReadUInt8();
bl = (byte)(al & 0xF0 | al >> 4);
bh = (byte)(al & 0x0F | al << 4);
if (ctl < 0xC0)
{
al = Binary.RotByteR (bl, 2);
ah = Binary.RotByteR (bh, 2);
}
else
{
ah = m_input.ReadUInt8();
al = (byte)(ah & 0xF0 | ah >> 4);
ah = (byte)(ah & 0x0F | ah << 4);
}
for (int i = 0; i < count; ++i)
{
plane[dst++] = bl;
plane[dst++] = bh;
plane[dst++] = al;
plane[dst++] = ah;
}
count <<= 2;
}
else
{
if (0 == count)
count = m_input.ReadUInt8();
byte al = m_input.ReadUInt8();
byte ah = m_input.ReadUInt8();
for (int i = 0; i < count; ++i)
{
plane[dst++] = al;
plane[dst++] = ah;
}
count <<= 1;
}
break;
case 0xFE:
ctl = count;
count &= 0x3F;
if (0 == count)
count = m_input.ReadUInt8();
if (ctl < 0x40)
{
m_input.Read (plane, dst, 4);
count <<= 2;
Binary.CopyOverlapped (plane, dst, dst + 4, count - 4);
dst += count;
}
else
{
int psrc, pmask;
if ((ctl & 0x80) == 0)
{
psrc = 0;
pmask = 1;
}
else if (ctl < 0xC0)
{
psrc = 0;
pmask = 2;
}
else
{
psrc = 1;
pmask = 2;
}
for (int i = 0; i < count; ++i)
{
byte b = m_planes[psrc][dst];
b &= m_planes[pmask][dst];
plane[dst++] = b;
}
}
break;
case 0xFF:
{
Func<int, byte> op;
if (count < 0x40)
{
op = src => (byte)(m_planes[0][src] | m_planes[1][src]);
}
else if (count < 0x80)
{
op = src => (byte)(m_planes[0][src] ^ m_planes[1][src]);
count &= 0x3F;
}
else
{
if (count < 0xA0)
op = src => (byte)(m_planes[0][src] | m_planes[2][src]);
else if (count < 0xC0)
op = src => (byte)(m_planes[1][src] | m_planes[2][src]);
else if (count < 0xE0)
op = src => (byte)(m_planes[0][src] ^ m_planes[2][src]);
else
op = src => (byte)(m_planes[1][src] ^ m_planes[2][src]);
count &= 0x1F;
}
if (0 == count)
count = m_input.ReadUInt8();
for (int i = 0; i < count; ++i)
{
plane[dst] = op (dst);
dst++;
}
break;
}
}
}
remaining -= count;
}
dst_row += m_height;
}
}
void FlattenPlanes (byte[] output, int output_stride)
{
int plane_size = m_planes[0].Length;
int src = 0;
for (int x = 0; x < output_stride; x += 4)
{
int dst = x;
for (int y = 0; y < m_info.iHeight; ++y)
{
byte b0 = m_planes[0][src];
byte b1 = m_planes[1][src];
byte b2 = m_planes[2][src];
byte b3 = m_planes[3][src];
++src;
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) >> 0));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
output[dst+j/2] = px;
}
dst += output_stride;
}
}
}
BitmapPalette ReadPalette ()
{
using (var bits = new MsbBitStream (m_input.AsStream, true))
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
int r = bits.GetBits (4) * 0x11;
int g = bits.GetBits (4) * 0x11;
int b = bits.GetBits (4) * 0x11;
colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
}
}
}

332
Legacy/Izumi/ImageMAI3.cs Normal file
View File

@ -0,0 +1,332 @@
//! \file ImageMAI3.cs
//! \date 2023 Oct 23
//! \brief Izumi engine image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Izumi
{
internal class Mai3MetaData : ImageMetaData
{
public int DataOffset;
public bool HasPalette;
}
[Export(typeof(ImageFormat))]
public class Mai3Format : ImageFormat
{
public override string Tag => "MI3";
public override string Description => "Izumi engine image format";
public override uint Signature => 0x3049414D; // 'MAI03'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (14);
if (!header.AsciiEqual ("MAI03\x1A"))
return null;
return new Mai3MetaData {
Width = (uint)(header.ToUInt16 (8) << 3),
Height = header.ToUInt16 (0xA),
BPP = 4,
DataOffset = header.ToUInt16 (0xC) & 0x7FFF,
HasPalette = (header[0xD] & 0x80) != 0,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Mai3Reader (file, (Mai3MetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Mai3Format.Write not implemented");
}
}
internal class Mai3Reader
{
IBinaryStream m_input;
Mai3MetaData m_info;
public Mai3Reader (IBinaryStream input, Mai3MetaData info)
{
m_input = input;
m_info = info;
}
ushort[] m_buffer;
int m_output_stride;
byte[] m_output;
public ImageData Unpack ()
{
m_input.Position = m_info.DataOffset;
BitmapPalette palette = null;
if (m_info.HasPalette)
palette = ReadPalette();
else
palette = BitmapPalettes.Gray16;
m_buffer = new ushort[0x6D0];
m_output_stride = m_info.iWidth >> 1;
m_output = new byte[m_output_stride * m_info.iHeight];
InitPixels();
InitBitReader();
int output_dst = 0;
int stride = m_info.iWidth >> 3;
int x = stride >> 1;
while (x --> 0)
{
MoveBuffer();
UnpackLine (0x1C0);
UnpackLine (0x10);
MoveBuffer();
UnpackLine (0x1C0);
UnpackLine (0x10);
CopyOutput (output_dst);
output_dst += 8;
}
if ((stride & 1) != 0)
{
MoveBuffer();
UnpackLine (0x1C0);
UnpackLine (0x10);
CopyOutput (output_dst, 1);
}
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_output, m_output_stride);
}
void UnpackLine (int dst)
{
int height = m_info.iHeight;
while (height > 0)
{
if (GetNextBit() == 0)
{
int offset;
if (GetNextBit() != 0)
offset = 0;
else if (GetNextBit() != 0)
offset = 0x1B0;
else
offset = 0x360;
if (0 == offset || GetNextBit() != 0)
{
if (GetNextBit() != 0)
offset = -1;
else if (GetNextBit() != 0)
offset -= 2;
else if (GetNextBit() != 0)
offset -= 4;
else if (GetNextBit() != 0)
offset -= 8;
else
offset -= 0x10;
}
else if (GetNextBit() == 0)
{
if (GetNextBit() != 0)
offset += 2;
else if (GetNextBit() != 0)
offset += 4;
else if (GetNextBit() != 0)
offset += 8;
else
offset += 0x10;
}
int length = GetCount (8);
int count = 1;
for (int j = 0; j < length; ++j)
count = count << 1 | GetNextBit();
count += 1;
int src = dst + offset;
height -= count;
while (count --> 0)
m_buffer[dst++] = m_buffer[src++];
}
else
{
ushort px = m_buffer[dst + 0x1B0];
int prev = (px >> 8) & 1;
prev <<= 1;
prev |= (px >> 12) & 1;
prev <<= 1;
prev |= px & 1;
prev <<= 1;
prev |= (px >> 4) & 1;
byte n0 = GetPixel ((byte)prev);
byte n1 = GetPixel (n0);
byte n2 = GetPixel (n1);
byte n3 = GetPixel (n2);
px = m_patterns[0,n3];
px |= m_patterns[1,n2];
px |= m_patterns[2,n1];
px |= m_patterns[3,n0];
m_buffer[dst++] = px;
--height;
}
}
}
static readonly ushort[,] m_patterns = {
{ 0, 0x10, 1, 0x11, 0x1000, 0x1010, 0x1001, 0x1011, 0x100, 0x110, 0x101, 0x111, 0x1100, 0x1110, 0x1101, 0x1111 },
{ 0, 0x20, 2, 0x22, 0x2000, 0x2020, 0x2002, 0x2022, 0x200, 0x220, 0x202, 0x222, 0x2200, 0x2220, 0x2202, 0x2222 },
{ 0, 0x40, 4, 0x44, 0x4000, 0x4040, 0x4004, 0x4044, 0x400, 0x440, 0x404, 0x444, 0x4400, 0x4440, 0x4404, 0x4444 },
{ 0, 0x80, 8, 0x88, 0x8000, 0x8080, 0x8008, 0x8088, 0x800, 0x880, 0x808, 0x888, 0x8800, 0x8880, 0x8808, 0x8888 },
};
byte GetPixel (byte prev)
{
int count = GetCount (15);
prev <<= 4;
prev += 0xF;
int src = prev - count;
int dst = src;
byte al = m_pixels[src++];
if (count > 0)
{
while (count --> 0)
m_pixels[dst++] = m_pixels[src++];
m_pixels[dst] = al;
}
return al;
}
int GetCount (int limit)
{
int count = 0;
while (count < limit && GetNextBit() == 0)
++count;
return count;
}
void MoveBuffer ()
{
Buffer.BlockCopy (m_buffer, 0x20, m_buffer, 0x6E0, 0x360 << 1);
}
void CopyOutput (int dst_line, int rows = 2)
{
int src = 0x10;
int height = m_info.iHeight;
for (int y = 0; y < height; ++y)
{
ushort cx = m_buffer[src + 0x510];
ushort dx = m_buffer[src + 0x360];
ushort bx = m_buffer[src + 0x1B0];
ushort ax = m_buffer[src++];
int b0 = bx << 8 & 0xF000 | ax << 4 & 0x0F00 | cx & 0x00F0 | dx >> 4 & 0xF;
int b1 = bx << 12 & 0xF000 | ax << 8 & 0x0F00 | cx << 4 & 0x00F0 | dx & 0xF;
int b2 = bx & 0xF000 | ax >> 4 & 0x0F00 | cx >> 8 & 0x00F0 | dx >> 12;
int b3 = bx << 4 & 0xF000 | ax & 0x0F00 | cx >> 4 & 0x00F0 | dx >> 8 & 0xF;
int dst = dst_line;
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
m_output[dst++] = px;
}
b0 >>= 8;
b1 >>= 8;
b2 >>= 8;
b3 >>= 8;
}
dst_line += m_output_stride;
}
}
byte[] m_pixels = new byte[0x100];
void InitPixels ()
{
int dst = m_pixels.Length - 1;
for (int i = 0x0F; i >= 0; --i)
{
byte n = (byte)i;
for (int j = 0; j < 0x10; ++j)
{
m_pixels[dst--] = (byte)(n-- & 0xF);
}
}
}
int m_bits;
int m_bit_count;
void InitBitReader ()
{
m_bits = m_input.ReadUInt16();
m_bit_count = 16;
}
byte GetNextBit ()
{
int bit = m_bits & 1;
m_bits >>= 1;
if (--m_bit_count <= 0)
{
if (m_input.PeekByte() != -1)
m_bits = m_input.ReadUInt16();
else
m_bits = 0;
m_bit_count = 16;
}
return (byte)bit;
}
BitmapPalette ReadPalette ()
{
using (var bits = new MsbBitStream (m_input.AsStream, true))
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
int r = bits.GetBits (4) * 0x11;
int g = bits.GetBits (4) * 0x11;
int b = bits.GetBits (4) * 0x11;
colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
}
}
}

69
Legacy/Jam/ImageHTF.cs Normal file
View File

@ -0,0 +1,69 @@
//! \file ImageHTF.cs
//! \date 2023 Oct 07
//! \brief Huffman-compressed bitmap.
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Jam
{
[Export(typeof(ImageFormat))]
public class HtfFormat : ImageFormat
{
public override string Tag => "HTF";
public override string Description => "Huffman-compressed bitmap";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".HTF"))
return null;
int unpacked_size = file.ReadInt32();
if (unpacked_size <= 0 || unpacked_size > 0x1000000)
return null;
using (var huff = new HuffmanStream (file.AsStream, true))
using (var input = new BinaryStream (huff, file.Name))
{
return Bmp.ReadMetaData (input);
}
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 4;
using (var input = new HuffmanStream (file.AsStream, true))
{
var decoder = new BmpBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
return new ImageData (decoder.Frames[0], info);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("HtfFormat.Write not implemented");
}
}
}

View File

@ -78,6 +78,9 @@
<Compile Include="AlphaSystem\ArcPAK.cs" />
<Compile Include="AlphaSystem\ImageSFG.cs" />
<Compile Include="Alterna\ArcBIN.cs" />
<Compile Include="Adviz\ImageBIZ.cs" />
<Compile Include="Adviz\ImageGIZ.cs" />
<Compile Include="Adviz\ImageGIZ2.cs" />
<Compile Include="Aos\ArcDAT.cs" />
<Compile Include="Aquarium\ArcAAP.cs" />
<Compile Include="Aquarium\ArcCPA.cs" />
@ -88,14 +91,33 @@
<Compile Include="Artel\ImageMRL.cs" />
<Compile Include="Asura\ArcPAK.cs" />
<Compile Include="Asura\ImageMTG.cs" />
<Compile Include="AyPio\ArcDLB.cs" />
<Compile Include="AyPio\PdtBitmap.cs" />
<Compile Include="AyPio\AudioVOC.cs" />
<Compile Include="AyPio\ImagePDT.cs" />
<Compile Include="AyPio\ImagePDT5.cs" />
<Compile Include="BlackButterfly\ArcDAT.cs" />
<Compile Include="Blucky\Aliases.cs" />
<Compile Include="Bom\ImageGRP.cs" />
<Compile Include="CottonClub\ImageLMG.cs" />
<Compile Include="Desire\ArcDSV.cs" />
<Compile Include="Desire\ImageDES.cs" />
<Compile Include="Desire\ImageDPC.cs" />
<Compile Include="Discovery\ImageAN1.cs" />
<Compile Include="Discovery\ImagePR1.cs" />
<Compile Include="DMotion\ArcDM.cs" />
<Compile Include="Grocer\ImagePIC.cs" />
<Compile Include="Gsx\ArcK5.cs" />
<Compile Include="Gsx\ImageK4.cs" />
<Compile Include="Harvest\ArcDAT.cs" />
<Compile Include="Harvest\AudioBGM.cs" />
<Compile Include="Harvest\AudioSED.cs" />
<Compile Include="Harvest\ImageUNH.cs" />
<Compile Include="Herb\ArcPAK.cs" />
<Compile Include="Herb\ImageGRP.cs" />
<Compile Include="HyperWorks\ImageG.cs" />
<Compile Include="Izumi\ImageMAI2.cs" />
<Compile Include="Izumi\ImageMAI3.cs" />
<Compile Include="James\ImageJMG.cs" />
<Compile Include="BRoom\ArcCPC.cs" />
<Compile Include="BRoom\ArcPK.cs" />
@ -116,6 +138,7 @@
<Compile Include="hmp\ImageCBF.cs" />
<Compile Include="HyperWorks\ArcPAK.cs" />
<Compile Include="HyperWorks\ImageI24.cs" />
<Compile Include="Jam\ImageHTF.cs" />
<Compile Include="KApp\ArcASD.cs" />
<Compile Include="KApp\ArcCGD.cs" />
<Compile Include="KApp\ImageCGD.cs" />
@ -133,17 +156,28 @@
<Compile Include="Liddell\ImageBPA.cs" />
<Compile Include="Logg\ArcARF.cs" />
<Compile Include="Logg\ImageFRM.cs" />
<Compile Include="Mapl\ImageMI2.cs" />
<Compile Include="Melonpan\ArcTTD.cs" />
<Compile Include="Mermaid\AudioPWV.cs" />
<Compile Include="Mermaid\ImageGP1.cs" />
<Compile Include="Miami\ImageMIA.cs" />
<Compile Include="Mina\ArcPAK.cs" />
<Compile Include="Mink\ImageFC.cs" />
<Compile Include="Mink\ImageFD.cs" />
<Compile Include="Mmfass\ArcSDA.cs" />
<Compile Include="Nekotaro\ImageNCG.cs" />
<Compile Include="Neon\ArcAR2.cs" />
<Compile Include="Pias\EncryptedGraphDat.cs" />
<Compile Include="Ponytail\ArcBND.cs" />
<Compile Include="Ponytail\ImageTCZ.cs" />
<Compile Include="Ponytail\ImageTSZ.cs" />
<Compile Include="Nug\ArcDAT.cs" />
<Compile Include="Nyoken\ArcZLK.cs" />
<Compile Include="Omi\ArcDAT.cs" />
<Compile Include="Paprika\ArcPKDAT.cs" />
<Compile Include="Paprika\ImageNP.cs" />
<Compile Include="Pearl\ArcARY.cs" />
<Compile Include="Pearl\ImagePL4.cs" />
<Compile Include="PenguinWorks\ArcPAC.cs" />
<Compile Include="Pias\ArcDAT.cs" />
<Compile Include="PineSoft\ArcCMB.cs" />
@ -156,6 +190,9 @@
<Compile Include="ProjectMyu\ImageGAM.cs" />
<Compile Include="Ransel\ArcBCD.cs" />
<Compile Include="Rare\ArcX.cs" />
<Compile Include="RedZone\ArcPAK.cs" />
<Compile Include="Adv98\ImageGPC.cs" />
<Compile Include="RedZone\ScriptQDO.cs" />
<Compile Include="Regrips\AudioWRG.cs" />
<Compile Include="Regrips\ImagePRG.cs" />
<Compile Include="Rhss\ArcCRG.cs" />
@ -202,18 +239,26 @@
<Compile Include="Rune\ArcYK.cs" />
<Compile Include="Sarang\ImageABC.cs" />
<Compile Include="Sogna\ArcSGS.cs" />
<Compile Include="Sophia\ArcNOR.cs" />
<Compile Include="SplushWave\ArcDAT.cs" />
<Compile Include="SplushWave\ImageSWG.cs" />
<Compile Include="SquadraD\ArcPLA.cs" />
<Compile Include="SquadraD\ArcSDA.cs" />
<Compile Include="StudioEbisu\ArcEP1.cs" />
<Compile Include="StudioFoma\ArcARC.cs" />
<Compile Include="System21\ImageTEX.cs" />
<Compile Include="System98\ArcLIB.cs" />
<Compile Include="System98\ImageG.cs" />
<Compile Include="Tako\ArcMPK.cs" />
<Compile Include="Tetratech\ArcBND.cs" />
<Compile Include="Tiare\ImageGRA.cs" />
<Compile Include="Tigerman\ArcCHR.cs" />
<Compile Include="Tigerman\ArcPAC.cs" />
<Compile Include="Tigerman\ImageCHR.cs" />
<Compile Include="Tobe\ImageWBI.cs" />
<Compile Include="Types\ArcARC.cs" />
<Compile Include="Types\ImageTPGF.cs" />
<Compile Include="Ucom\ImageUG.cs" />
<Compile Include="Uma\ArcCDT.cs" />
<Compile Include="Uma\ArcSDT.cs" />
<Compile Include="UMeSoft\AudioIKE.cs" />
@ -244,7 +289,7 @@
<Compile Include="Mink\ArcMINK.cs" />
<Compile Include="Mutation\ArcDPF.cs" />
<Compile Include="Pinpai\ArcARC.cs" />
<Compile Include="Sorciere\ImageBIZ.cs" />
<Compile Include="Adviz\ImageBIZ2.cs" />
<Compile Include="UMeSoft\ArcBIN.cs" />
<Compile Include="Uncanny\AudioCWV.cs" />
<Compile Include="Uncanny\ImageCII.cs" />

438
Legacy/Mapl/ImageMI2.cs Normal file
View File

@ -0,0 +1,438 @@
//! \file ImageMI2.cs
//! \date 2023 Oct 19
//! \brief Mapl engine 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;
// [980130][Pias] Rashin
namespace GameRes.Formats.Mapl
{
internal class Mi2MetaData : ImageMetaData
{
public int Colors;
}
[Export(typeof(ImageFormat))]
public class Mi2Format : ImageFormat
{
public override string Tag => "MI2";
public override string Description => "Mapl engine image format";
public override uint Signature => 0x28;
public Mi2Format ()
{
Extensions = new[] { "mi2", "fcg" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x28); // BITMAPINFOHEADER
int bpp = header.ToUInt16 (0xE);
if (bpp != 8 && bpp != 24)
return null;
uint width = header.ToUInt32 (4);
uint height = header.ToUInt32 (8);
int colors = header.ToInt32 (0x20);
if (8 == bpp)
{
if (colors < 0 || colors > 0x100)
return null;
if (0 == colors)
colors = 0x100;
}
return new Mi2MetaData {
Width = width,
Height = height,
BPP = bpp,
Colors = colors,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Mi2Reader (file, (Mi2MetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Mi2Format.Write not implemented");
}
}
internal class Mi2Reader
{
IBinaryStream m_input;
Mi2MetaData m_info;
public Mi2Reader (IBinaryStream file, Mi2MetaData info)
{
m_input = file;
m_info = info;
}
byte[] m_block = new byte[64];
byte[] m_buffer = new byte[32];
int stride;
int block_stride;
int blocksW;
int blocksH;
public ImageData Unpack ()
{
stride = (m_info.iWidth + 7) & ~7;
block_stride = stride * 8;
blocksW = m_info.iWidth >> 3;
if ((m_info.iWidth & 7) != 0)
blocksW++;
blocksH = m_info.iHeight >> 3;
if ((m_info.iHeight & 7) != 0)
blocksH++;
var channel = new byte[stride * m_info.iHeight];
m_input.Position = 0x28;
if (8 == m_info.BPP)
{
var palette = ImageFormat.ReadPalette (m_input.AsStream, m_info.Colors);
UnpackChannel (channel);
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, channel, stride);
}
else
{
int bgr_stride = m_info.iWidth * 3;
var bgr = new byte[bgr_stride * m_info.iHeight];
for (int c = 0; c < 3; ++c)
{
UnpackChannel (channel);
int src = 0;
int dst = c;
for (int y = 0; y < m_info.iHeight; ++y)
{
for (int x = 0; x < m_info.iWidth; ++x)
{
bgr[dst] = channel[src+x];
dst += 3;
}
src += stride;
}
}
return ImageData.Create (m_info, PixelFormats.Bgr24, null, bgr, bgr_stride);
}
}
void UnpackChannel (byte[] output)
{
int dst_row = output.Length - stride;
for (int y = 0; y < blocksH; ++y)
{
int dst_pos = dst_row;
for (int x = 0; x < blocksW; ++x)
{
byte ctl = m_input.ReadUInt8();
switch (ctl)
{
case 0:
m_input.Read (m_block, 0, m_block.Length);
break;
case 1:
{
byte b = m_input.ReadUInt8();
for (int i = 0; i < m_block.Length; ++i)
m_block[i] = b;
break;
}
case 2:
Op2();
break;
case 3:
Op3();
break;
case 4:
Op4();
break;
case 5:
Op5();
break;
case 6:
Op6();
break;
case 7:
Op7();
break;
case 8:
Op8();
AdjustBlock();
break;
case 9:
Op9();
AdjustBlock();
break;
case 10:
Op4();
AdjustBlock();
break;
case 11:
Op5();
AdjustBlock();
break;
case 12:
Op6();
AdjustBlock();
break;
case 13:
Op7();
AdjustBlock();
break;
default:
throw new InvalidFormatException();
}
int dst = dst_pos;
int src = 0;
while (src < m_block.Length && dst >= 0)
{
Buffer.BlockCopy (m_block, src, output, dst, 8);
src += 8;
dst -= stride;
}
dst_pos += 8;
}
dst_row -= block_stride;
}
}
void Op2 ()
{
byte b = m_input.ReadUInt8();
m_input.Read (m_buffer, 0, 8);
int pos = 0;
for (int i = 0; i < 8; ++i)
{
byte mask = 0x80;
for (int j = 0; j < 8; ++j)
{
if ((mask & m_buffer[i]) != 0)
m_block[pos++] = b;
else
m_block[pos++] = m_input.ReadUInt8();
mask >>= 1;
}
}
}
void Op3 ()
{
int pos = 0;
byte b1 = m_input.ReadUInt8();
byte b2 = m_input.ReadUInt8();
for (int i = 0; i < 8; ++i)
{
byte mask = 0x80;
byte b = m_input.ReadUInt8();
for (int j = 0; j < 8; ++j)
{
if ((mask & b) != 0)
m_block[pos++] = b2;
else
m_block[pos++] = b1;
mask >>= 1;
}
}
}
void Op4()
{
byte b1 = m_input.ReadUInt8();
byte b2 = m_input.ReadUInt8();
byte b3 = m_input.ReadUInt8();
int dst = 0;
m_input.Read (m_buffer, 0, 16);
for (int i = 0; i < 16; ++i)
{
byte bits = m_buffer[i];
for (int j = 0; j < 4; ++j)
{
switch (bits >> 6)
{
case 0: m_block[dst] = m_input.ReadUInt8(); break;
case 1: m_block[dst] = b1; break;
case 2: m_block[dst] = b2; break;
case 3: m_block[dst] = b3; break;
}
dst++;
bits <<= 2;
}
}
}
void Op5()
{
byte b0 = m_input.ReadUInt8();
byte b1 = m_input.ReadUInt8();
byte b2 = m_input.ReadUInt8();
byte b3 = m_input.ReadUInt8();
m_input.Read (m_buffer, 0, 16);
int dst = 0;
for (int i = 0; i < 16; ++i)
{
byte bits = m_buffer[i];
for (int j = 0; j < 4; ++j)
{
switch (bits >> 6)
{
case 0: m_block[dst] = b0; break;
case 1: m_block[dst] = b1; break;
case 2: m_block[dst] = b2; break;
case 3: m_block[dst] = b3; break;
}
dst++;
bits <<= 2;
}
}
}
byte[] m_buffer2 = new byte[0x100];
byte[] m_buffer6 = new byte[0x40];
void Op6()
{
int count = m_input.ReadUInt8();
m_input.Read (m_buffer2, 0, count);
m_input.Read (m_buffer, 0, 24);
for (int i = 0; i < m_buffer6.Length; ++i)
m_buffer6[i] = 0;
int buf_pos = 0;
int bits = 0;
int bit_count = 0;
int bit_src = 0;
byte mask = 0x80;
for (int i = 0; i < 64; ++i)
{
byte n0 = 0;
byte n1 = 4;
for (int j = 0; j < 3; ++j)
{
if (0 == bit_count)
{
bits = m_buffer[bit_src++];
bit_count = 8;
}
if ((bits & mask) != 0)
n0 |= n1;
--bit_count;
n1 >>= 1;
mask = Binary.RotByteR (mask, 1);
}
m_buffer6[buf_pos++] = n0;
}
for (int i = 0; i < 64; ++i)
{
byte b = m_buffer6[i];
if (b > 0)
m_block[i] = m_buffer2[b-1];
else
m_block[i] = m_input.ReadUInt8();
}
}
void Op7()
{
int count = m_input.ReadUInt8();
m_input.Read (m_buffer2, 0, count);
m_input.Read (m_buffer, 0, 32);
int dst = 0;
for (int i = 0; i < 32; ++i)
{
byte b = m_buffer[i];
for (int j = 0; j < 2; ++j)
{
int bits = b >> 4;
b <<= 4;
if (bits != 0)
m_block[dst++] = m_buffer2[bits-1];
else
m_block[dst++] = m_input.ReadUInt8();
}
}
}
void Op8()
{
byte b0 = m_input.ReadUInt8();
m_input.Read (m_buffer, 0, 8);
int src = 0;
int dst = 0;
for (int i = 0; i < 8; ++i)
{
byte mask = 0x80;
byte b = m_buffer[src++];
for (int j = 0; j < 8; ++j)
{
if ((mask & b) != 0)
m_block[dst++] = b0;
else
m_block[dst++] = m_input.ReadUInt8();
mask >>= 1;
}
}
}
void Op9()
{
byte b0 = m_input.ReadUInt8();
byte b1 = m_input.ReadUInt8();
int dst = 0;
for (int i = 0; i < 8; ++i)
{
byte mask = 0x80;
byte b = m_input.ReadUInt8();
for (int j = 0; j < 8; ++j)
{
if ((mask & b) != 0)
m_block[dst++] = b1;
else
m_block[dst++] = b0;
mask >>= 1;
}
}
}
void AdjustBlock()
{
int dst = 0;
for (int i = 0; i < 8; ++i)
{
m_input.ReadByte();
for (int j = 0; j < 8; ++j)
{
m_block[dst++] <<= 1;
}
}
}
}
}

318
Legacy/Miami/ImageMIA.cs Normal file
View File

@ -0,0 +1,318 @@
//! \file ImageMIA.cs
//! \date 2023 Oct 21
//! \brief Miamisoft image format (PC-98).
//
// 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;
// [950630][Miamisoft] Kotohigaoka Monogatari
namespace GameRes.Formats.Miami
{
[Export(typeof(ImageFormat))]
public class MiaFormat : ImageFormat
{
public override string Tag => "MIA";
public override string Description => "Miamisoft image format";
public override uint Signature => 0;
public MiaFormat ()
{
Signatures = new[] { 0x40u, 0u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
if (!header.AsciiEqual (0xA, "CoB42"))
return null;
return new ImageMetaData {
Width = header.ToUInt16 (6),
Height = header.ToUInt16 (8),
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new MiaReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("MiaFormat.Write not implemented");
}
}
internal class MiaReader
{
IBinaryStream m_input;
ImageMetaData m_info;
public MiaReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
byte[] m_buffer;
int m_buf_dst;
byte[] m_order;
byte[] m_output;
int m_output_dst;
int m_output_stride;
public ImageData Unpack ()
{
m_input.Position = 0x10;
var palette = ReadPalette (m_input);
try
{
UnpackInternal();
}
catch (EndOfStreamException)
{
FlushBuffer();
}
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_output, m_output_stride);
}
void UnpackInternal () // 1374:7A54
{
m_output_stride = m_info.iWidth >> 1;
m_output = new byte[m_output_stride * m_info.iHeight];
int buffer_size = m_info.iHeight * 0x10;
m_buffer = new byte[buffer_size];
SetupPattern();
m_order = m_input.ReadBytes (6);
byte prev_pixel = 0x10;
m_buf_dst = 0;
m_output_dst = 0;
while (m_output_dst < m_output_stride)
{
int ctl = GetInt() - 1;
if (ctl < 0) // @1@
{
m_buffer[m_buf_dst ] = 0;
m_buffer[m_buf_dst+1] = 0;
m_buffer[m_buf_dst+2] = 0;
m_buffer[m_buf_dst+3] = 0;
for (int i = 0; i < 4; ++i)
{
int count = GetInt();
int dst = count + (prev_pixel << 4);
byte al = m_pattern[dst];
int src = dst - 1;
while (count --> 0)
m_pattern[dst--] = m_pattern[src--];
m_pattern[dst] = al;
prev_pixel = al;
for (int j = 0; j < 4; ++j)
{
m_buffer[m_buf_dst+j] <<= 1;
m_buffer[m_buf_dst+j] |= (byte)(al & 1);
al >>= 1;
}
}
ushort ax = LittleEndian.ToUInt16 (m_buffer, m_buf_dst);
ax <<= 4;
LittleEndian.Pack (ax, m_buffer, m_buf_dst+4);
ax = LittleEndian.ToUInt16 (m_buffer, m_buf_dst+2);
ax <<= 4;
LittleEndian.Pack (ax, m_buffer, m_buf_dst+6);
m_buf_dst += 8;
}
else if (ctl < 5) // @2@
{
int count = 1 + GetInt();
switch (m_order[ctl])
{
case 1: CopyOp01 (count, 8); break;
case 2: CopyOp01 (count, 0x10); break;
case 3: CopyOp01 (count, 0x20); break;
case 4: CopyOp01 (count, m_info.iHeight << 3); break;
case 5: CopyOp05 (count); break;
default: throw new InvalidFormatException();
}
}
else // ctl >= 5
{
throw new InvalidFormatException();
}
if (buffer_size == m_buf_dst)
FlushBuffer();
}
}
int GetInt ()
{
int count = 0;
while (GetNextBit() == 0)
++count;
return count;
}
void CopyOp01 (int count, int offset)
{
int bx = count;
while (count > 0)
{
int dst = m_buf_dst;
if (dst < offset)
{
int src = dst;
dst = -(dst - offset) >> 3;
if (count > dst)
count = dst;
src += m_info.iHeight << 4;
src -= offset;
bx -= count;
count <<= 3;
Binary.CopyOverlapped (m_buffer, src, m_buf_dst, count);
m_buf_dst += count;
count = bx;
if (0 == count)
break;
}
int remaining = m_buffer.Length - m_buf_dst;
remaining >>= 3;
if (count > remaining)
count = remaining;
bx -= count;
count <<= 3;
Binary.CopyOverlapped (m_buffer, m_buf_dst - offset, m_buf_dst, count);
m_buf_dst += count;
count = bx;
if (m_buffer.Length == m_buf_dst)
FlushBuffer();
}
}
void CopyOp05 (int count)
{
int src = m_buf_dst - 8;
if (src < 0)
src = m_buffer.Length - 8;
ushort ax = LittleEndian.ToUInt16 (m_buffer, src);
ax = (ushort)((ax << 1) & 0x0A0A | (ax >> 1) & 0x0505);
LittleEndian.Pack (ax, m_buffer, m_buf_dst);
ax <<= 4;
LittleEndian.Pack (ax, m_buffer, m_buf_dst+4);
ax = LittleEndian.ToUInt16 (m_buffer, src+2);
ax = (ushort)((ax << 1) & 0x0A0A | (ax >> 1) & 0x0505);
LittleEndian.Pack (ax, m_buffer, m_buf_dst+2);
ax <<= 4;
LittleEndian.Pack (ax, m_buffer, m_buf_dst+6);
m_buf_dst += 8;
if (m_buf_dst == m_buffer.Length)
FlushBuffer();
if (--count != 0)
CopyOp01 (count, 0x10);
}
void FlushBuffer ()
{
int height = m_info.iHeight;
int hi = height << 3;
int src = 0;
int dst = m_output_dst;
for (int y = 0; y < height; ++y)
{
int b0 = m_buffer[src+4] | m_buffer[src+hi ];
int b1 = m_buffer[src+5] | m_buffer[src+hi+1];
int b2 = m_buffer[src+6] | m_buffer[src+hi+2];
int b3 = m_buffer[src+7] | m_buffer[src+hi+3];
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
m_output[dst+(j>>1)] = px;
}
src += 8;
dst += m_output_stride;
}
m_output_dst += 4;
m_buf_dst = 0;
}
byte[] m_pattern = new byte[0x110];
void SetupPattern ()
{
int dst = 0;
byte h = 0;
for (int i = 0; i < 0x11; ++i)
{
byte l = h;
for (int j = 0; j < 0x10; ++j)
m_pattern[dst++] = (byte)(l++ & 0xF);
h++;
}
}
int m_bit_count = 0;
int m_bits;
byte GetNextBit ()
{
if (--m_bit_count <= 0)
{
m_bits = m_input.ReadUInt8();
m_bit_count = 8;
}
int bit = m_bits & 1;
m_bits >>= 1;
return (byte)bit;
}
BitmapPalette ReadPalette (IBinaryStream input)
{
const int count = 16;
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
byte g = m_input.ReadUInt8();
byte r = m_input.ReadUInt8();
byte b = m_input.ReadUInt8();
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
}
}

299
Legacy/Mina/ArcPAK.cs Normal file
View File

@ -0,0 +1,299 @@
//! \file ArcPAK.cs
//! \date 2023 Oct 09
//! \brief Mina 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;
using System.Windows.Media;
// [010223][Mina] Storia ~Ouma no Mori no Himegimi-tachi~
namespace GameRes.Formats.Mina
{
[Export(typeof(ArchiveFormat))]
public class BmpPakOpener : ArchiveFormat
{
public override string Tag => "PAK/MINA/BMP";
public override string Description => "Mina bitmap archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasExtension (".PAK"))
return null;
int pos;
for (pos = 0; pos < 0x10; ++pos)
{
if (0 == file.View.ReadByte (pos))
break;
}
if (pos >= 0x10 || pos <= 4 || !file.View.AsciiEqual (pos-4, ".BMP"))
return null;
using (var input = file.CreateStream())
{
var dir = new List<Entry>();
while (input.PeekByte() != -1)
{
var name = input.ReadCString();
if (name.Length > 0x10)
return null;
var entry = Create<Entry> (name);
entry.Offset = input.Position;
input.Seek (5, SeekOrigin.Current);
uint size = input.ReadUInt32();
entry.Size = size + 9;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
input.Seek (size, SeekOrigin.Current);
}
return new ArcFile (file, this, dir);
}
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new BitmapDecoder (input);
}
}
[Export(typeof(ArchiveFormat))]
public class WavPakOpener : ArchiveFormat
{
public override string Tag => "PAK/MINA/WAV";
public override string Description => "Mina audio archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasExtension (".PAK"))
return null;
int pos;
for (pos = 4; pos < 0x14; ++pos)
{
if (0 == file.View.ReadByte (pos))
break;
}
if (pos >= 0x14 || pos <= 8 || !file.View.AsciiEqual (pos-4, ".WAV"))
return null;
using (var input = file.CreateStream())
{
var dir = new List<Entry>();
while (input.PeekByte() != -1)
{
uint data_size = input.ReadUInt32();
var name = input.ReadCString();
if (name.Length > 0x10)
return null;
var entry = Create<Entry> (name);
entry.Offset = input.Position;
uint fmt_size = input.ReadUInt32();
if (fmt_size < 0x10)
return null;
entry.Size = data_size + fmt_size + 4;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
input.Seek (data_size + fmt_size, SeekOrigin.Current);
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
uint fmt_size = arc.File.View.ReadUInt32 (entry.Offset);
uint pcm_size = entry.Size - 4 - fmt_size;
using (var mem = new MemoryStream ((int)fmt_size))
{
using (var buffer = new BinaryWriter (mem, Encoding.ASCII, true))
{
buffer.Write (AudioFormat.Wav.Signature);
buffer.Write (entry.Size+0x10);
buffer.Write (0x45564157); // 'WAVE'
buffer.Write (0x20746d66); // 'fmt '
buffer.Write (fmt_size);
var fmt = arc.File.View.ReadBytes (entry.Offset+4, fmt_size);
buffer.Write (fmt, 0, fmt.Length);
buffer.Write (0x61746164); // 'data'
buffer.Write (pcm_size);
}
var header = mem.ToArray();
var data = arc.File.CreateStream (entry.Offset+4+fmt_size, pcm_size);
return new PrefixStream (header, data);
}
}
}
[Export(typeof(ArchiveFormat))]
public class ScriptPakOpener : ArchiveFormat
{
public override string Tag => "PAK/MINA/SPT";
public override string Description => "Mina scripts archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public ScriptPakOpener ()
{
ContainedFormats = new[] { "SCR" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!VFS.IsPathEqualsToFileName (file.Name, "SCRIPT.PAK"))
return null;
using (var input = file.CreateStream())
{
var dir = new List<Entry>();
while (input.PeekByte() != -1)
{
var name = input.ReadCString();
if (name.Length > 0x10)
return null;
var entry = Create<Entry> (name);
entry.Size = input.ReadUInt32();
entry.Offset = input.Position;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
input.Seek (entry.Size, SeekOrigin.Current);
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
var mem = new MemoryStream (data.Length);
int pos = 0;
while (pos < data.Length)
{
int len = data[pos]+1;
int num = data.ToUInt16 (1);
pos += 3;
for (int j = 0; j < len; ++j)
data[pos+j] = Binary.RotByteR (data[pos+j], 4);
mem.Write (data, pos, len);
mem.WriteByte (0xD);
mem.WriteByte (0xA);
pos += len;
}
mem.Position = 0;
return mem;
}
}
internal class BmpMetaData : ImageMetaData
{
public byte Flags;
public bool IsCompressed => (Flags & 1) != 0;
}
internal class BitmapDecoder : IImageDecoder
{
IBinaryStream m_input;
BmpMetaData m_info;
ImageData m_image;
public Stream Source => m_input.AsStream;
public ImageFormat SourceFormat => null;
public ImageMetaData Info => m_info;
public ImageData Image => m_image ?? (m_image = Unpack());
public BitmapDecoder (IBinaryStream input)
{
m_input = input;
m_info = new BmpMetaData {
Width = input.ReadUInt16(),
Height = input.ReadUInt16(),
Flags = input.ReadUInt8(),
};
m_info.BPP = m_info.IsCompressed ? 32 : 24;
}
ImageData Unpack ()
{
m_input.Position = 9;
if (m_info.IsCompressed)
{
return RleUnpack();
}
else
{
int bitmap_size = m_info.iWidth * 3 * m_info.iHeight;
var pixels = m_input.ReadBytes (bitmap_size);
return ImageData.Create (m_info, PixelFormats.Rgb24, null, pixels);
}
}
ImageData RleUnpack ()
{
int stride = m_info.iWidth * 4;
var output = new byte[stride * m_info.iHeight];
byte alpha = 0;
int count = 0;
int dst = 0;
while (dst < output.Length)
{
if (--count <= 0)
{
alpha = m_input.ReadUInt8();
count = m_input.ReadUInt8();
}
if (alpha != 0)
{
output[dst+2] = m_input.ReadUInt8();
output[dst+1] = m_input.ReadUInt8();
output[dst ] = m_input.ReadUInt8();
output[dst+3] = alpha;
}
dst += 4;
}
return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride);
}
#region IDisposable members
bool m_disposed = false;
public void Dispose ()
{
if (!m_disposed)
{
m_input.Dispose();
m_disposed = true;
}
}
#endregion
}
}

View File

@ -27,6 +27,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
// [991231][Jam] Kakuyuugou Shoujo Ripple-chan
// [000331][Jam] Zetsumetsu King
// [000630][STONE HEADS] Sei Cosplay Gakuen ~Game Bunkou~

View File

@ -24,8 +24,10 @@
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
@ -44,6 +46,11 @@ namespace GameRes.Formats.Nekotaro
public override string Description { get { return "Nekotaro Game System image format"; } }
public override uint Signature { get { return 0x706D4347; } } // 'GCmp'
public GCmpFormat ()
{
Extensions = new[] { "GCMP", "AIG" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
@ -98,6 +105,8 @@ namespace GameRes.Formats.Nekotaro
Stride = (info.iWidth + 7) / 8;
}
static BitmapPalette LastUsedPalette = null;
public ImageData Unpack ()
{
m_input.Position = 0x10;
@ -106,6 +115,8 @@ namespace GameRes.Formats.Nekotaro
pixels = Unpack24bpp();
else
pixels = Unpack8bpp();
if (8 == Info.BPP)
Palette = LastUsedPalette ?? (LastUsedPalette = RetrievePalette() ?? DefaultPalette);
return ImageData.CreateFlipped (Info, Format, Palette, pixels, Stride);
}
@ -190,14 +201,11 @@ namespace GameRes.Formats.Nekotaro
byte[] Unpack8bpp ()
{
if (8 == Info.BPP)
{
Format = PixelFormats.Indexed8;
Palette = DefaultPalette;
}
else
Format = PixelFormats.BlackWhite;
int pixel_count = Info.iHeight * Stride;
if (m_info.IsCompressed)
if (!m_info.IsCompressed)
return m_input.ReadBytes (pixel_count);
var output = new byte[pixel_count];
@ -272,268 +280,125 @@ namespace GameRes.Formats.Nekotaro
return output;
}
static readonly BitmapPalette DefaultPalette = new BitmapPalette (
/*
new Color[] {
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0x22, 0x22, 0x22),
Color.FromRgb (0x44, 0x44, 0x44),
Color.FromRgb (0x55, 0x55, 0x55),
Color.FromRgb (0x66, 0x66, 0x66),
Color.FromRgb (0x77, 0x77, 0x77),
Color.FromRgb (0x88, 0x88, 0x88),
Color.FromRgb (0x99, 0x99, 0x99),
Color.FromRgb (0xAA, 0xAA, 0xAA),
Color.FromRgb (0xBB, 0xBB, 0xBB),
Color.FromRgb (0xCC, 0xCC, 0xCC),
Color.FromRgb (0xDD, 0xDD, 0xDD),
Color.FromRgb (0xEE, 0xEE, 0xEE),
Color.FromRgb (0x00, 0xFF, 0x00),
Color.FromRgb (0x1C, 0x09, 0x05),
Color.FromRgb (0x2F, 0x0A, 0x05),
Color.FromRgb (0x4E, 0x04, 0x02),
Color.FromRgb (0x41, 0x0C, 0x05),
Color.FromRgb (0x29, 0x15, 0x36),
Color.FromRgb (0x24, 0x22, 0x21),
Color.FromRgb (0x6C, 0x07, 0x0D),
Color.FromRgb (0x1F, 0x2D, 0x36),
Color.FromRgb (0x4B, 0x21, 0x18),
Color.FromRgb (0x5D, 0x1B, 0x0D),
Color.FromRgb (0x8B, 0x00, 0x36),
Color.FromRgb (0x8E, 0x06, 0x16),
Color.FromRgb (0x7E, 0x11, 0x0F),
Color.FromRgb (0x09, 0x44, 0x64),
Color.FromRgb (0x48, 0x2C, 0x4B),
Color.FromRgb (0x38, 0x37, 0x3A),
Color.FromRgb (0x3A, 0x24, 0x88),
Color.FromRgb (0x74, 0x23, 0x12),
Color.FromRgb (0x0D, 0x53, 0x29),
Color.FromRgb (0x22, 0x34, 0x86),
Color.FromRgb (0xB1, 0x03, 0x2A),
Color.FromRgb (0x4B, 0x37, 0x28),
Color.FromRgb (0x64, 0x30, 0x28),
Color.FromRgb (0x32, 0x4A, 0x2D),
Color.FromRgb (0x9B, 0x17, 0x20),
Color.FromRgb (0xB0, 0x10, 0x10),
Color.FromRgb (0x3D, 0x19, 0xCC),
Color.FromRgb (0x1B, 0x38, 0xB2),
Color.FromRgb (0x97, 0x25, 0x13),
Color.FromRgb (0x30, 0x4C, 0x5E),
Color.FromRgb (0x77, 0x38, 0x22),
Color.FromRgb (0xD3, 0x0B, 0x1F),
Color.FromRgb (0x01, 0x69, 0x65),
Color.FromRgb (0x5F, 0x46, 0x33),
Color.FromRgb (0x4B, 0x4D, 0x4F),
Color.FromRgb (0xB6, 0x1B, 0x34),
Color.FromRgb (0x0A, 0x74, 0x34),
Color.FromRgb (0xBB, 0x26, 0x11),
Color.FromRgb (0xED, 0x0B, 0x26),
Color.FromRgb (0x2F, 0x52, 0x97),
Color.FromRgb (0x49, 0x20, 0xFB),
Color.FromRgb (0x89, 0x44, 0x15),
Color.FromRgb (0x67, 0x46, 0x65),
Color.FromRgb (0x06, 0x76, 0x72),
Color.FromRgb (0x93, 0x3F, 0x2D),
Color.FromRgb (0x3F, 0x65, 0x49),
Color.FromRgb (0x6D, 0x52, 0x3B),
Color.FromRgb (0x88, 0x4C, 0x38),
Color.FromRgb (0xE5, 0x26, 0x17),
Color.FromRgb (0xA6, 0x47, 0x1D),
Color.FromRgb (0x43, 0x68, 0x7D),
Color.FromRgb (0x23, 0x50, 0xE8),
Color.FromRgb (0xE3, 0x24, 0x43),
Color.FromRgb (0x94, 0x56, 0x1C),
Color.FromRgb (0x60, 0x63, 0x64),
Color.FromRgb (0xBC, 0x3E, 0x49),
Color.FromRgb (0x06, 0x9C, 0x45),
Color.FromRgb (0xC4, 0x44, 0x24),
Color.FromRgb (0xB1, 0x55, 0x2B),
Color.FromRgb (0x8D, 0x60, 0x53),
Color.FromRgb (0x63, 0x46, 0xFB),
Color.FromRgb (0x7B, 0x6C, 0x61),
Color.FromRgb (0x91, 0x57, 0x97),
Color.FromRgb (0xAA, 0x5A, 0x4C),
Color.FromRgb (0x49, 0x7E, 0xA0),
Color.FromRgb (0xF8, 0x3C, 0x29),
Color.FromRgb (0xA9, 0x67, 0x20),
Color.FromRgb (0xC9, 0x56, 0x36),
Color.FromRgb (0xA2, 0x6A, 0x3E),
Color.FromRgb (0xBF, 0x56, 0x6C),
Color.FromRgb (0x77, 0x7A, 0x7B),
Color.FromRgb (0x5D, 0x79, 0xD2),
Color.FromRgb (0xCC, 0x62, 0x44),
Color.FromRgb (0xA3, 0x75, 0x63),
Color.FromRgb (0xDE, 0x60, 0x31),
Color.FromRgb (0xB5, 0x79, 0x23),
Color.FromRgb (0x45, 0x80, 0xF5),
Color.FromRgb (0xFD, 0x56, 0x29),
Color.FromRgb (0xEE, 0x52, 0x64),
Color.FromRgb (0x8C, 0x83, 0x6E),
Color.FromRgb (0xCD, 0x70, 0x2A),
Color.FromRgb (0xC4, 0x6E, 0x53),
Color.FromRgb (0x86, 0x87, 0x87),
Color.FromRgb (0x5E, 0x95, 0xAC),
Color.FromRgb (0x7D, 0x6C, 0xFD),
Color.FromRgb (0x36, 0xC5, 0x22),
Color.FromRgb (0xAC, 0x6F, 0xB2),
Color.FromRgb (0xD9, 0x6E, 0x4E),
Color.FromRgb (0xC1, 0x84, 0x2D),
Color.FromRgb (0xDB, 0x6C, 0x72),
Color.FromRgb (0xEB, 0x6F, 0x42),
Color.FromRgb (0x9F, 0x8B, 0x81),
Color.FromRgb (0x92, 0x94, 0x93),
Color.FromRgb (0x76, 0x90, 0xDB),
Color.FromRgb (0x85, 0x9A, 0x99),
Color.FromRgb (0xE0, 0x79, 0x58),
Color.FromRgb (0xBE, 0x87, 0x6D),
Color.FromRgb (0xD5, 0x7E, 0x62),
Color.FromRgb (0x5B, 0xA7, 0xDF),
Color.FromRgb (0xCC, 0x91, 0x2A),
Color.FromRgb (0xF5, 0x6F, 0x76),
Color.FromRgb (0x7B, 0xA7, 0xA7),
Color.FromRgb (0xF1, 0x7C, 0x54),
Color.FromRgb (0xA1, 0x9C, 0x87),
Color.FromRgb (0xE5, 0x81, 0x61),
Color.FromRgb (0xF2, 0x8A, 0x47),
Color.FromRgb (0xEE, 0x88, 0x67),
Color.FromRgb (0xA1, 0xA3, 0xA3),
Color.FromRgb (0x8A, 0xA0, 0xE5),
Color.FromRgb (0xC4, 0x9A, 0x7F),
Color.FromRgb (0xD9, 0x9F, 0x36),
Color.FromRgb (0x95, 0xAC, 0xAA),
Color.FromRgb (0xEC, 0x88, 0x8B),
Color.FromRgb (0xAE, 0xA7, 0x92),
Color.FromRgb (0xE8, 0x90, 0x70),
Color.FromRgb (0xF5, 0x8F, 0x6F),
Color.FromRgb (0xD5, 0x8B, 0xDC),
Color.FromRgb (0x6A, 0xC2, 0xF7),
Color.FromRgb (0xEE, 0x9A, 0x7A),
Color.FromRgb (0xF7, 0x98, 0x74),
Color.FromRgb (0x8D, 0xBA, 0xDB),
Color.FromRgb (0xBA, 0xB1, 0x9C),
Color.FromRgb (0xB2, 0xB3, 0xB1),
Color.FromRgb (0xD2, 0xA8, 0x9B),
Color.FromRgb (0xA6, 0xBA, 0xBD),
Color.FromRgb (0xEC, 0xB4, 0x3A),
Color.FromRgb (0xFC, 0x98, 0x9F),
Color.FromRgb (0xF7, 0xA1, 0x80),
Color.FromRgb (0xED, 0xA7, 0x85),
Color.FromRgb (0xFA, 0xA9, 0x83),
Color.FromRgb (0xDD, 0xB3, 0xAF),
Color.FromRgb (0xFA, 0xA6, 0xA7),
Color.FromRgb (0xC8, 0xC0, 0xAD),
Color.FromRgb (0xFA, 0xB0, 0x8F),
Color.FromRgb (0x89, 0xD9, 0xFC),
Color.FromRgb (0xA9, 0xCF, 0xE8),
Color.FromRgb (0xBB, 0xCC, 0xCB),
Color.FromRgb (0xFB, 0xB2, 0xB2),
Color.FromRgb (0xFB, 0xB9, 0x97),
Color.FromRgb (0xE2, 0xC2, 0xAF),
Color.FromRgb (0xFC, 0xCA, 0x40),
Color.FromRgb (0xFA, 0xBF, 0x82),
Color.FromRgb (0xC9, 0xCA, 0xC9),
Color.FromRgb (0xF8, 0xAC, 0xF8),
Color.FromRgb (0xD4, 0xCD, 0xC2),
Color.FromRgb (0xFC, 0xC2, 0x9D),
Color.FromRgb (0xFC, 0xBE, 0xBA),
Color.FromRgb (0xD2, 0xD3, 0xD0),
Color.FromRgb (0xEC, 0xC9, 0xC6),
Color.FromRgb (0xCA, 0xD9, 0xD7),
Color.FromRgb (0xFD, 0xCA, 0xA5),
Color.FromRgb (0xFE, 0xDB, 0x5B),
Color.FromRgb (0xD8, 0xD8, 0xD4),
Color.FromRgb (0xFD, 0xCA, 0xC9),
Color.FromRgb (0xC3, 0xDF, 0xF1),
Color.FromRgb (0xFE, 0xD2, 0xB1),
Color.FromRgb (0xFD, 0xD6, 0xA1),
Color.FromRgb (0xEE, 0xD7, 0xCA),
Color.FromRgb (0xFB, 0xCB, 0xF7),
Color.FromRgb (0xFE, 0xDB, 0xB6),
Color.FromRgb (0xFE, 0xF5, 0x2C),
Color.FromRgb (0xFD, 0xD6, 0xD4),
Color.FromRgb (0xE2, 0xE2, 0xDC),
Color.FromRgb (0xFE, 0xEC, 0x74),
Color.FromRgb (0xFE, 0xE1, 0xBE),
Color.FromRgb (0xED, 0xE5, 0xDC),
Color.FromRgb (0xD9, 0xEC, 0xF8),
Color.FromRgb (0xFB, 0xE3, 0xD4),
Color.FromRgb (0xFD, 0xDD, 0xFA),
Color.FromRgb (0xFE, 0xE7, 0xC6),
Color.FromRgb (0xFE, 0xFA, 0x91),
Color.FromRgb (0xFE, 0xEF, 0xCD),
Color.FromRgb (0xFC, 0xEB, 0xEA),
Color.FromRgb (0xFE, 0xF6, 0xDC),
Color.FromRgb (0xFE, 0xFD, 0xE4),
Color.FromRgb (0x35, 0x29, 0x24),
Color.FromRgb (0x1A, 0x43, 0x25),
Color.FromRgb (0x01, 0x49, 0x96),
Color.FromRgb (0x86, 0x27, 0x16),
Color.FromRgb (0x4D, 0x52, 0x3F),
Color.FromRgb (0xEB, 0x0E, 0x0A),
Color.FromRgb (0x00, 0x6A, 0xCC),
Color.FromRgb (0x80, 0x34, 0xC1),
Color.FromRgb (0xFD, 0x00, 0xFF),
Color.FromRgb (0x08, 0x87, 0xEF),
Color.FromRgb (0x76, 0x70, 0x56),
Color.FromRgb (0xB8, 0x55, 0x3F),
Color.FromRgb (0x35, 0x9F, 0xE1),
Color.FromRgb (0xAA, 0x7E, 0x60),
Color.FromRgb (0x01, 0xFD, 0x00),
Color.FromRgb (0xAB, 0x93, 0x8A),
Color.FromRgb (0xD3, 0x8C, 0x56),
Color.FromRgb (0x77, 0xC0, 0xAC),
Color.FromRgb (0xB9, 0xA6, 0x9E),
Color.FromRgb (0xE6, 0xAB, 0x63),
Color.FromRgb (0x9D, 0xCC, 0xA5),
Color.FromRgb (0xD1, 0xB6, 0x91),
Color.FromRgb (0xA6, 0xD9, 0xCF),
Color.FromRgb (0xEA, 0xC9, 0x9D),
Color.FromRgb (0xDF, 0xE2, 0xBC),
Color.FromRgb (0xFC, 0xE8, 0xA2),
Color.FromRgb (0xF9, 0xF2, 0xDE),
Color.FromRgb (0x23, 0x0D, 0x1A),
Color.FromRgb (0x02, 0x58, 0x1A),
Color.FromRgb (0x66, 0x39, 0x13),
Color.FromRgb (0x36, 0x6D, 0x66),
Color.FromRgb (0x90, 0x5F, 0x2A),
Color.FromRgb (0x51, 0x9E, 0x7E),
Color.FromRgb (0xC0, 0x91, 0x52),
Color.FromRgb (0x7F, 0xC3, 0xAE),
Color.FromRgb (0xE0, 0xBF, 0x78),
Color.FromRgb (0xDC, 0xE8, 0xD4),
Color.FromRgb (0x65, 0x39, 0x12),
Color.FromRgb (0x22, 0x69, 0x49),
Color.FromRgb (0x90, 0x5E, 0x2B),
Color.FromRgb (0x36, 0x87, 0x5F),
Color.FromRgb (0x53, 0x9F, 0x81),
Color.FromRgb (0xBB, 0x85, 0x4C),
Color.FromRgb (0xD9, 0xB9, 0x6F),
Color.FromRgb (0x9A, 0xCE, 0xC2),
Color.FromRgb (0xDF, 0xEF, 0xDE),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0xFF),
static void LzssUnpack (IBinaryStream input, byte[] output)
{
int dst = 0;
int mask = 0;
int ctl = 0;
while (dst < output.Length)
{
mask >>= 1;
if (0 == mask)
{
ctl = input.ReadUInt8();
mask = 0x80;
}
if ((ctl & mask) != 0)
{
int off = input.ReadUInt16();
int count = (off & 0xF) + 3;
off >>= 4;
int src = dst - off - 1;
Binary.CopyOverlapped (output, src, dst, count);
dst += count;
}
else
{
output[dst++] = input.ReadUInt8();
}
}
*/
}
BitmapPalette RetrievePalette ()
{
// find SYSTEM.LZS file, decompress and read it as text file
// find 'P' line that denotes archive name and entry number
// if entry number is zero, then it's just a file (possibly compressed)
// open referenced file and retrieve palette
try
{
string system_name = "SYSTEM.LZS";
if (!File.Exists (system_name))
{
system_name = @"..\SYSTEM.LZS";
if (!File.Exists (system_name))
return null;
}
byte[] system_bin;
using (var input = BinaryStream.FromFile (system_name))
{
int unpacked_size = input.ReadUInt16();
input.ReadUInt16();
system_bin = new byte[unpacked_size];
LzssUnpack (input, system_bin);
}
string line;
using (var mem = new MemoryStream (system_bin))
using (var text = new StreamReader (mem, Encodings.cp932))
{
while ((line = text.ReadLine()) != null)
{
if (line.Length > 3 && line.StartsWith ("P:"))
break;
}
if (null == line)
return null;
}
var match = PLineRe.Match (line);
if (!match.Success)
return null;
int id;
if (!Int32.TryParse (match.Groups[2].Value, out id))
return null;
var arc_name = Path.Combine (Path.GetDirectoryName (system_name), match.Groups[1].Value);
if (0 == id)
{
using (var file = BinaryStream.FromFile (arc_name))
{
Stream pal_stream;
int unpacked_size = file.ReadUInt16();
int packed_size = file.ReadUInt16();
if (packed_size + 4 == file.Length)
{
var pal_data = new byte[unpacked_size];
LzssUnpack (file, pal_data);
pal_stream = new MemoryStream (pal_data);
}
else
{
file.Position = 0;
pal_stream = file.AsStream;
}
int colors = (int)pal_stream.Length / 3;
using (pal_stream)
return ImageFormat.ReadPalette (pal_stream, colors, PaletteFormat.Rgb);
}
}
else
{
using (var file = new ArcView (arc_name))
{
var arc = Nsc.Value.TryOpen (file);
if (null == arc)
return null;
var entry = ((List<Entry>)arc.Dir)[id-1];
using (var input = arc.OpenEntry (entry))
return ImageFormat.ReadPalette (input, 0x100, PaletteFormat.Rgb);
}
}
}
catch
{
return null;
}
}
static readonly Regex PLineRe = new Regex (@"^P:([^,]+),(\d+),(\d+)", RegexOptions.Compiled);
static readonly ResourceInstance<ArchiveFormat> Nsc = new ResourceInstance<ArchiveFormat> ("NSC");
static readonly BitmapPalette DefaultPalette = new BitmapPalette (
// [000317][PIL] Seek -remasters-
#region colors
new Color[] {
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0xFF, 0xFF, 0xFF),
@ -792,6 +657,7 @@ namespace GameRes.Formats.Nekotaro
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0x00, 0x00, 0x00),
}
#endregion
);
bool m_disposed = false;

293
Legacy/Nekotaro/ImageNCG.cs Normal file
View File

@ -0,0 +1,293 @@
//! \file ImageNCG.cs
//! \date 2023 Oct 10
//! \brief Nekotaro Game System image format (PC-98).
//
// 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;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Nekotaro
{
[Export(typeof(ImageFormat))]
public class NcgFormat : ImageFormat
{
public override string Tag => "NCG";
public override string Description => "Nekotaro Game System image format";
public override uint Signature => 0;
public NcgFormat ()
{
Signatures = new[] { 0xC8500000u, 0u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (4);
int left = header[0] << 3;
int top = header[1] << 1;
int width = header[2] << 3;
int height = header[3] << 1;
int right = left + width;
int bottom = top + height;
if (right > 640 || bottom > 400 || 0 == width || 0 == height)
return null;
return new ImageMetaData {
Width = (uint)width,
Height = (uint)height,
OffsetX = left,
OffsetY = top,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new NcgReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("NcgFormat.Write not implemented");
}
}
internal class NcgReader
{
IBinaryStream m_input;
ImageMetaData m_info;
public NcgReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
public ImageData Unpack ()
{
m_input.Position = 4;
var palette = ReadPalette();
int width = m_info.iWidth;
int height = m_info.iHeight;
int output_stride = width;
var pixels = new byte[output_stride * height];
int quart_width = width / 4;
int half_height = height / 2;
var blockmap = new bool[quart_width * half_height];
var bits1 = new byte[8];
var bits2 = new byte[8];
byte ctl;
int dst, pblk;
do
{
for (int i = 0; i < 8; ++i)
bits1[i] = bits2[i] = 0;
for (int shift = 0; shift < 4; ++shift)
{
byte bit = (byte)(1 << shift);
FillBits (bits1, bit);
FillBits (bits2, bit);
}
for (;;)
{
ctl = m_input.ReadUInt8();
if (0xFF == ctl || 0x7F == ctl)
break;
int pos = (ctl & 0x3F) << 8 | m_input.ReadUInt8();
int x = (pos % 80) << 3;
int y = (pos / 80) << 1;
dst = width * y + x;
pblk = x / 4 + quart_width * (y / 2);
switch (ctl >> 6)
{
case 0:
{
int w_count = m_input.ReadUInt8();
int h_count = m_input.ReadUInt8();
int gap = quart_width - 2 * w_count;
while (h_count --> 0)
{
for (int i = 0; i < w_count; ++i)
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk++] = true;
blockmap[pblk++] = true;
}
pblk += gap;
dst += 2 * width - 8 * w_count;
}
break;
}
case 1:
{
int count = m_input.ReadUInt8();
while (count --> 0)
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk++] = true;
blockmap[pblk++] = true;
}
break;
}
case 2:
{
int count = m_input.ReadUInt8();
while (count --> 0)
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk ] = true;
blockmap[pblk+1] = true;
dst += 2 * width - 8;
pblk += quart_width;
}
break;
}
case 3:
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk ] = true;
blockmap[pblk+1] = true;
break;
}
}
}
}
while (ctl != 0xFF);
do
{
for (int i = 0; i < 8; ++i)
bits1[i] = 0;
for (int shift = 0; shift < 4; ++shift)
FillBits (bits1, (byte)(1 << shift));
for (;;)
{
ctl = m_input.ReadUInt8();
if (0xFF == ctl || 0xFE == ctl)
break;
int pos = (ctl & 0x7F) << 8 | m_input.ReadUInt8();
dst = 4 * (pos % 160) + width * 2 * (pos / 160);
pblk = (pos % 160) + quart_width * (pos / 160);
if ((ctl & 0x80) == 0)
{
int count = m_input.ReadUInt8();
while (count --> 0)
{
for (int j = 0; j < 4; ++j)
{
pixels[dst+width] = bits1[j+4];
pixels[dst++] = bits1[j];
}
blockmap[pblk] = true;
pblk += quart_width;
dst += 2 * width - 4;
}
}
else
{
for (int j = 0; j < 4; ++j)
{
pixels[dst+width] = bits1[j+4];
pixels[dst++] = bits1[j];
}
blockmap[pblk] = true;
}
}
}
while (ctl != 0xFF);
dst = 0;
pblk = 0;
for (int y = 0; y < half_height; ++y)
{
for (int x = 0; x < quart_width; ++x)
{
if (blockmap[pblk++])
{
dst += 4;
}
else
{
for (int i = 0; i < 8; ++i)
bits1[i] = 0;
for (int shift = 0; shift < 4; ++shift)
FillBits (bits1, (byte)(1 << shift));
for (int j = 0; j < 4; ++j)
{
pixels[dst+width] = bits1[j+4];
pixels[dst++] = bits1[j];
}
}
}
dst += width;
}
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride);
}
void FillBits (byte[] bits, byte bit)
{
sbyte s = m_input.ReadInt8();
for (int i = 0; i < 8; ++i)
{
if (s < 0)
bits[i] |= bit;
s <<= 1;
}
}
static readonly string PaletteKey = "NEKOTARO";
BitmapPalette ReadPalette ()
{
int k = 0;
var colors = new Color[16];
for (int c = 0; c < 16; ++c)
{
int g = m_input.ReadUInt8();
int r = m_input.ReadUInt8();
int b = m_input.ReadUInt8();
b = (~b - PaletteKey[k++ & 7]) & 0xFF;
r = (~r - PaletteKey[k++ & 7]) & 0xFF;
g = (~g - PaletteKey[k++ & 7]) & 0xFF;
colors[c] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
}
}

90
Legacy/Neon/ArcAR2.cs Normal file
View File

@ -0,0 +1,90 @@
//! \file ArcAR2.cs
//! \date 2023 Oct 17
//! \brief Neon 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
// [990527][Neon] Onegai! Maid☆Roid
namespace GameRes.Formats.Neon
{
[Export(typeof(ArchiveFormat))]
public class Ar2Opener : ArchiveFormat
{
public override string Tag => "AR2/NEON";
public override string Description => "Neon resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
const byte DefaultKey = 0x55;
public override ArcFile TryOpen (ArcView file)
{
uint uKey = DefaultKey | DefaultKey << 16;
uKey |= uKey << 8;
if (file.MaxOffset <= 0x10
|| file.View.ReadUInt32 (8) != uKey
|| file.View.ReadUInt32 (0) != file.View.ReadUInt32 (4)
|| (file.View.ReadUInt32 (0xC) ^ uKey) > 0x100)
return null;
using (var stream = file.CreateStream())
using (var input = new XoredStream (stream, DefaultKey))
{
var buffer = new byte[0x100];
var dir = new List<Entry>();
while (0x10 == input.Read (buffer, 0, 0x10))
{
uint size = buffer.ToUInt32 (0);
// uint orig_size = buffer.ToUInt32 (4); // original size?
// uint extra = buffer.ToUInt32 (8); // header size/compression?
int name_length = buffer.ToInt32 (0xC);
if (0 == size && 0 == name_length)
continue;
if (name_length <= 0 || name_length > buffer.Length)
return null;
input.Read (buffer, 0, name_length);
var name = Encodings.cp932.GetString (buffer, 0, name_length);
var entry = Create<Entry> (name);
entry.Offset = input.Position;
entry.Size = size;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
input.Seek (size, SeekOrigin.Current);
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new XoredStream (input, DefaultKey);
}
}
}

83
Legacy/Pearl/ArcARY.cs Normal file
View File

@ -0,0 +1,83 @@
//! \file ArcARY.cs
//! \date 2023 Sep 23
//! \brief Pearl Soft 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Pearl
{
// implementation based on BMX/TRIANGLE
// exact same layout, but doesn't have compressed entries.
[Export(typeof(ArchiveFormat))]
public class AryOpener : ArchiveFormat
{
public override string Tag => "ARY";
public override string Description => "Pearl Soft resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
uint index_size = (uint)count * 4 + 8;
if (index_size > file.View.Reserve (0, index_size))
return null;
uint index_offset = 4;
uint offset = file.View.ReadUInt32 (index_offset);
if (offset != index_size)
return null;
uint last_offset = file.View.ReadUInt32 (index_size - 4);
if (last_offset != file.MaxOffset)
return null;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
index_offset += 4;
var entry = new Entry {
Name = string.Format ("{0}#{1:D4}", base_name, i),
Offset = offset,
};
offset = file.View.ReadUInt32 (index_offset);
entry.Size = (uint)(offset - entry.Offset);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
foreach (var entry in dir)
{
uint signature = file.View.ReadUInt32 (entry.Offset);
entry.ChangeType (AutoEntry.DetectFileType (signature));
}
return new ArcFile (file, this, dir);
}
}
}

382
Legacy/Pearl/ImagePL4.cs Normal file
View File

@ -0,0 +1,382 @@
//! \file ImagePL4.cs
//! \date 2023 Sep 23
//! \brief Pearl Soft 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;
// [980424][Pearl Soft] Watashi
namespace GameRes.Formats.Pearl
{
internal class Pl4MetaData : ImageMetaData
{
public ushort CompressionMethod;
}
[Export(typeof(ImageFormat))]
public class Pl4Format : ImageFormat
{
public override string Tag => "PL4";
public override string Description => "Pearl Soft image format";
public override uint Signature => 0x20344C50; // 'PL4 '
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
int version = header.ToUInt16 (4);
if (version != 1)
return null;
var info = new Pl4MetaData {
Width = header.ToUInt16 (0xC) * 8u,
Height = header.ToUInt16 (0xE),
CompressionMethod = header.ToUInt16 (6),
BPP = 8,
};
if (info.CompressionMethod > 1)
return null;
return info;
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new Pl4Reader (file, (Pl4MetaData)info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Pl4Format.Write not implemented");
}
}
internal class Pl4Reader
{
IBinaryStream m_input;
Pl4MetaData m_info;
int m_stride;
public Pl4Reader (IBinaryStream input, Pl4MetaData info)
{
m_input = input;
m_info = info;
m_stride = m_info.iWidth;
}
public ImageData Unpack ()
{
m_input.Position = 0x10;
var palette = ReadPalette (16);
var pixels = new byte[m_stride * m_info.iHeight];
m_input.Position = 0x40;
if (m_info.CompressionMethod == 0)
UnpackV0 (pixels);
else if (m_info.CompressionMethod == 1)
UnpackV1 (pixels);
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, m_stride);
}
BitmapPalette ReadPalette (int colors)
{
var color_data = m_input.ReadBytes (colors * 3);
var color_map = new Color[colors];
int src = 0;
for (int i = 0; i < colors; ++i)
{
color_map[i] = Color.FromRgb ((byte)(color_data[src ] * 0x11),
(byte)(color_data[src+1] * 0x11),
(byte)(color_data[src+2] * 0x11));
src += 3;
}
return new BitmapPalette (color_map);
}
void UnpackV0 (byte[] output)
{
int height = m_info.iHeight;
int x = m_info.iWidth / 4;
int output_size = height * m_stride;
int row = 0;
int dst = 0;
int ctl, word;
while ((ctl = m_input.ReadByte()) != -1)
{
byte next = m_input.ReadUInt8();
if (0x98 == ctl)
{
ctl = m_input.ReadUInt8();
if (0 == next)
{
next = m_input.ReadUInt8();
}
else
{
word = ctl << 8 | next;
int count = ((word >> 1) & 0x1F) + 2;
int src_y;
int src_x = Math.DivRem ((word >> 6) + 1, height, out src_y);
int src = dst - src_y * m_stride - 4 * src_x;
src_y = row - src_y;
if (src_y < 0)
{
src_y += height;
src += output_size - 4;
}
while (count --> 0)
{
output[dst ] = output[src ];
output[dst+1] = output[src+1];
output[dst+2] = output[src+2];
output[dst+3] = output[src+3];
dst += m_stride;
if (++row >= height)
{
row = 0;
dst -= output_size - 4;
if (--x <= 0)
return;
}
src += m_stride;
if (++src_y >= height)
{
src_y = 0;
src -= output_size - 4;
}
}
continue;
}
}
word = next << 8 | ctl;
int px = 0;
if ((word & 0x1000) != 0) px = 0x01000000;
if ((word & 0x2000) != 0) px |= 0x00010000;
if ((word & 0x4000) != 0) px |= 0x00000100;
if ((word & 0x8000) != 0) px |= 0x00000001;
if ((word & 0x0100) != 0) px |= 0x02000000;
if ((word & 0x0200) != 0) px |= 0x00020000;
if ((word & 0x0400) != 0) px |= 0x00000200;
if ((word & 0x0800) != 0) px |= 0x00000002;
if ((word & 0x0010) != 0) px |= 0x04000000;
if ((word & 0x0020) != 0) px |= 0x00040000;
if ((word & 0x0040) != 0) px |= 0x00000400;
if ((word & 0x0080) != 0) px |= 0x00000004;
if ((word & 0x0001) != 0) px |= 0x08000000;
if ((word & 0x0002) != 0) px |= 0x00080000;
if ((word & 0x0004) != 0) px |= 0x00000800;
if ((word & 0x0008) != 0) px |= 0x00000008;
LittleEndian.Pack (px, output, dst);
dst += m_stride;
if (++row >= height)
{
row = 0;
dst -= output_size - 4;
if (--x <= 0)
break;
}
}
}
byte[] m_pixelBuffer;
MsbBitStream m_bits;
void UnpackV1 (byte[] output)
{
m_pixelBuffer = InitLineBuffer();
int height = m_info.iHeight;
int dst = 0;
int output_size = m_stride * height;
int x = m_info.iWidth / 8;
int y = 0;
using (m_bits = new MsbBitStream (m_input.AsStream, true))
{
int p1 = 0, p2 = 0, p3 = 0, p4 = 0;
int ctl_bit;
while ((ctl_bit = m_bits.GetNextBit()) != -1)
{
if (ctl_bit != 0)
{
int src = dst;
int src_y = y;
switch (m_bits.GetBits (2))
{
case 0:
src_y = y - 2;
src = dst - 2 * m_stride;
break;
case 1:
src_y = y - 1;
src = dst - m_stride;
break;
case 2:
src_y = y - 4;
src = dst - 4 * m_stride;
break;
case 3:
src = dst - 8;
break;
}
if (src_y < 0)
{
src_y += height;
src += output_size - 8;
}
int count_length = 0;
while (m_bits.GetNextBit() == 0)
++count_length;
int count = 1;
if (count_length != 0)
{
count = m_bits.GetBits (count_length) | 1 << count_length;
}
while (count --> 0)
{
Buffer.BlockCopy (output, src, output, dst, 8);
dst += m_stride;
if (++y >= height)
{
y = 0;
dst -= output_size - 8;
if (--x <= 0)
return;
}
src += m_stride;
if (++src_y >= height)
{
src_y = 0;
src -= output_size - 8;
}
}
}
else
{
int px1 = 0;
int px2 = 0;
p1 = UpdatePixel (p1);
p2 = UpdatePixel (p2);
p3 = UpdatePixel (p3);
p4 = UpdatePixel (p4);
if ((p1 & 0x80) != 0) px1 = 0x00000001;
if ((p1 & 0x40) != 0) px1 |= 0x00000100;
if ((p1 & 0x20) != 0) px1 |= 0x00010000;
if ((p1 & 0x10) != 0) px1 |= 0x01000000;
if ((p1 & 0x08) != 0) px2 = 0x00000001;
if ((p1 & 0x04) != 0) px2 |= 0x00000100;
if ((p1 & 0x02) != 0) px2 |= 0x00010000;
if ((p1 & 0x01) != 0) px2 |= 0x01000000;
if ((p2 & 0x80) != 0) px1 |= 0x00000002;
if ((p2 & 0x40) != 0) px1 |= 0x00000200;
if ((p2 & 0x20) != 0) px1 |= 0x00020000;
if ((p2 & 0x10) != 0) px1 |= 0x02000000;
if ((p2 & 0x08) != 0) px2 |= 0x00000002;
if ((p2 & 0x04) != 0) px2 |= 0x00000200;
if ((p2 & 0x02) != 0) px2 |= 0x00020000;
if ((p2 & 0x01) != 0) px2 |= 0x02000000;
if ((p3 & 0x80) != 0) px1 |= 0x00000004;
if ((p3 & 0x40) != 0) px1 |= 0x00000400;
if ((p3 & 0x20) != 0) px1 |= 0x00040000;
if ((p3 & 0x10) != 0) px1 |= 0x04000000;
if ((p3 & 0x08) != 0) px2 |= 0x00000004;
if ((p3 & 0x04) != 0) px2 |= 0x00000400;
if ((p3 & 0x02) != 0) px2 |= 0x00040000;
if ((p3 & 0x01) != 0) px2 |= 0x04000000;
if ((p4 & 0x80) != 0) px1 |= 0x00000008;
if ((p4 & 0x40) != 0) px1 |= 0x00000800;
if ((p4 & 0x20) != 0) px1 |= 0x00080000;
if ((p4 & 0x10) != 0) px1 |= 0x08000000;
if ((p4 & 0x08) != 0) px2 |= 0x00000008;
if ((p4 & 0x04) != 0) px2 |= 0x00000800;
if ((p4 & 0x02) != 0) px2 |= 0x00080000;
if ((p4 & 0x01) != 0) px2 |= 0x08000000;
LittleEndian.Pack (px1, output, dst);
LittleEndian.Pack (px2, output, dst+4);
dst += m_stride;
if (++y >= height)
{
y = 0;
dst -= output_size - 8;
if (--x <= 0)
break;
}
}
}
}
}
int UpdatePixel (int pixel)
{
byte nibble = GetNextPixel (pixel);
return GetNextPixel (nibble) | nibble << 4;
}
byte GetNextPixel (int pixel)
{
int bits = GetPixelBits();
int prior = (pixel & 0xF) << 4;
byte next = m_pixelBuffer[prior+bits];
int pos = prior + bits;
if (bits == 0)
return next;
while (bits --> 0)
{
m_pixelBuffer[pos] = m_pixelBuffer[pos - 1];
--pos;
}
return m_pixelBuffer[prior] = next;
}
int GetPixelBits ()
{
if (m_bits.GetNextBit() != 0)
{
return m_bits.GetBits (1);
}
else if (m_bits.GetNextBit() != 0)
{
return m_bits.GetBits (1) + 2;
}
else if (m_bits.GetNextBit() != 0)
{
return m_bits.GetBits (2) + 4;
}
else
{
return m_bits.GetBits (3) + 8;
}
}
static byte[] InitLineBuffer ()
{
var buffer = new byte[256];
for (int i = 0; i < 256; ++i)
{
buffer[i] = (byte)((i + (i >> 4)) & 0xF);
}
return buffer;
}
}
}

View File

@ -2,7 +2,7 @@
//! \date 2022 May 24
//! \brief Pias resource archive.
//
// Copyright (C) 2022 by morkt
// Copyright (C) 2022-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
@ -36,77 +36,141 @@ using System.Windows.Media;
namespace GameRes.Formats.Pias
{
enum ResourceType
{
Undefined = -1,
Graphics = 1,
Sound = 2,
}
internal class IndexReader
{
internal const bool UseOffsetAsName = true;
protected ArcView m_arc;
protected ResourceType m_res;
protected List<Entry> m_dir;
public bool IsEncrypted { get; protected set; }
public IndexReader (ArcView arc, ResourceType res)
{
m_arc = arc;
m_res = res;
m_dir = null;
}
public List<Entry> GetIndex ()
{
if (m_res > 0)
{
var text_name = VFS.ChangeFileName (m_arc.Name, "text.dat");
if (!VFS.FileExists (text_name))
return null;
IBinaryStream input = VFS.OpenBinaryStream (text_name);
try
{
if (DatOpener.EncryptedSignatures.Contains (input.Signature))
return null;
var reader = new TextReader (input);
m_dir = reader.GetResourceList ((int)m_res);
}
finally
{
input.Dispose();
}
}
if (null == m_dir)
m_dir = new List<Entry>();
if (!FillEntries())
return null;
return m_dir;
}
protected bool FillEntries ()
{
uint header_size = 4;
string entry_type = "audio";
if (ResourceType.Graphics == m_res)
{
header_size = 8;
entry_type = "image";
}
for (int i = m_dir.Count - 1; i >= 0; --i)
{
var entry = m_dir[i];
entry.Size = m_arc.View.ReadUInt32 (entry.Offset) + header_size;
entry.Name = i.ToString("D4");
entry.Type = entry_type;
}
var known_offsets = new HashSet<long> (m_dir.Select (e => e.Offset));
long offset = 0;
while (offset < m_arc.MaxOffset)
{
uint entry_size = m_arc.View.ReadUInt32(offset);
if (uint.MaxValue == entry_size)
{
entry_size = 4;
}
else
{
entry_size += header_size;
if (!known_offsets.Contains (offset))
{
var entry = new Entry {
Name = GetName (offset, m_dir.Count),
Type = entry_type,
Offset = offset,
Size = entry_size,
};
if (!entry.CheckPlacement (m_arc.MaxOffset))
return false;
m_dir.Add (entry);
}
}
offset += entry_size;
}
return true;
}
internal string GetName (long offset, int num)
{
return UseOffsetAsName ? offset.ToString ("D8") : num.ToString("D4");
}
}
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag { get { return "DAT/PIAS"; } }
public override string Description { get { return "Pias resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override string Tag => "DAT/PIAS";
public override string Description => "Pias resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public DatOpener ()
{
Signatures = new[] { 0x0002C026u, 0u };
}
internal static readonly HashSet<uint> EncryptedSignatures = new HashSet<uint> { 0x03184767u };
public override ArcFile TryOpen (ArcView file)
{
var arc_name = Path.GetFileName (file.Name).ToLowerInvariant();
string entry_type = null;
if ("voice.dat" == arc_name || "music.dat" == arc_name || "sound.dat" == arc_name)
entry_type = "audio";
ResourceType resource_type = ResourceType.Undefined;
if ("sound.dat" == arc_name)
resource_type = ResourceType.Sound;
else if ("graph.dat" == arc_name)
entry_type = "image";
else
resource_type = ResourceType.Graphics;
else if ("voice.dat" != arc_name && "music.dat" != arc_name)
return null;
int resource_type = -1;
if ("graph.dat" == arc_name)
resource_type = 1;
else if ("sound.dat" == arc_name)
resource_type = 2;
uint header_size = 1 == resource_type ? 8u : 4u;
List<Entry> dir = null;
if (resource_type > 0)
{
var text_name = VFS.ChangeFileName (file.Name, "text.dat");
if (!VFS.FileExists (text_name))
return null;
using (var text_dat = VFS.OpenBinaryStream (text_name))
{
var reader = new TextReader (text_dat);
dir = reader.GetResourceList (resource_type);
if (dir != null)
{
for (int i = dir.Count - 1; i >= 0; --i)
{
var entry = dir[i];
entry.Size = file.View.ReadUInt32 (entry.Offset) + header_size;
entry.Name = i.ToString("D4");
entry.Type = entry_type;
}
}
}
}
var index = new IndexReader (file, resource_type);
var dir = index.GetIndex();
if (null == dir)
dir = new List<Entry>();
var known_offsets = new HashSet<long> (dir.Select (e => e.Offset));
long offset = 0;
while (offset < file.MaxOffset)
{
uint entry_size = file.View.ReadUInt32(offset) + header_size;
if (!known_offsets.Contains (offset))
{
var entry = new Entry {
Name = dir.Count.ToString("D4"),
Type = entry_type,
Offset = offset,
Size = entry_size,
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
offset += entry_size;
}
return null;
return new ArcFile (file, this, dir);
}
@ -120,7 +184,6 @@ namespace GameRes.Formats.Pias
{
if (entry.Type != "audio")
return base.OpenEntry (arc, entry);
uint size = arc.File.View.ReadUInt32 (entry.Offset);
var format = new WaveFormat
{
FormatTag = 1,
@ -138,6 +201,12 @@ namespace GameRes.Formats.Pias
format.BlockAlign = 1;
}
format.SetBPS();
return OpenAudioEntry (arc, entry, format);
}
public Stream OpenAudioEntry (ArcFile arc, Entry entry, WaveFormat format)
{
uint size = arc.File.View.ReadUInt32 (entry.Offset);
byte[] header;
using (var buffer = new MemoryStream())
{

View File

@ -0,0 +1,377 @@
//! \file EncryptedGraphDat.cs
//! \date 2023 Oct 20
//! \brief Pias encrypted 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Windows.Media;
// [000526][Pias] Ningyou no Hako
namespace GameRes.Formats.Pias
{
internal class PiasEncryptedArchive : ArcFile
{
public PiasEncryptedArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir)
: base (arc, impl, dir)
{
}
}
internal class EncryptedIndexReader : IndexReader
{
public EncryptedIndexReader (ArcView arc, ResourceType res) : base (arc, res)
{
}
new public List<Entry> GetIndex ()
{
if (m_res > 0)
{
var text_name = VFS.ChangeFileName (m_arc.Name, "text.dat");
if (!VFS.FileExists (text_name))
return null;
IBinaryStream input = VFS.OpenBinaryStream (text_name);
try
{
if (!DatOpener.EncryptedSignatures.Contains (input.Signature))
return null;
input.Position = 4;
var rnd = new KeyGenerator (1);
rnd.Seed (input.Signature);
var crypto = new InputCryptoStream (input.AsStream, new PiasTransform (rnd));
input = new BinaryStream (crypto, text_name);
var reader = new TextReader (input);
m_dir = reader.GetResourceList ((int)m_res);
}
finally
{
input.Dispose();
}
}
IsEncrypted = ResourceType.Graphics == m_res;
if (null == m_dir)
{
m_dir = new List<Entry>();
}
if (!IsEncrypted)
{
if (!FillEntries())
return null;
return m_dir;
}
var buffer = new byte[4];
var key = new KeyGenerator (0);
for (int i = m_dir.Count - 1; i >= 0; --i)
{
var entry = m_dir[i];
uint seed = m_arc.View.ReadUInt32 (entry.Offset);
m_arc.View.Read (entry.Offset+4, buffer, 0, 4);
key.Seed (seed);
Decrypt (buffer, 0, 4, key);
entry.Size = (buffer.ToUInt32 (0) & 0xFFFFFu) + 8u;
entry.Name = GetName (entry.Offset, i);
entry.Type = "image";
}
var known_offsets = new HashSet<long> (m_dir.Select (e => e.Offset));
long offset = 0;
while (offset < m_arc.MaxOffset)
{
uint seed = m_arc.View.ReadUInt32 (offset);
m_arc.View.Read (offset+4, buffer, 0, 4);
key.Seed (seed);
Decrypt (buffer, 0, 4, key);
uint entry_size = (buffer.ToUInt32 (0) & 0xFFFFFu) + 8u;
if (!known_offsets.Contains (offset))
{
var entry = new Entry {
Name = GetName (offset, m_dir.Count) + "_",
Type = "image",
Offset = offset,
Size = entry_size,
};
if (!entry.CheckPlacement (m_arc.MaxOffset))
return null;
m_dir.Add (entry);
}
offset += entry_size + 4;
}
return m_dir;
}
internal static void Decrypt (byte[] data, int pos, int length, KeyGenerator key)
{
for (int i = 0; i < length; ++i)
{
data[pos+i] ^= (byte)key.Next();
}
}
}
[Export(typeof(ArchiveFormat))]
public class EncryptedDatOpener : DatOpener
{
public override string Tag => "DAT/PIAS/ENC";
public override string Description => "Pias encrypted resource archive";
public override uint Signature => 0;
public override bool CanWrite => false;
public EncryptedDatOpener ()
{
Signatures = new[] { 0x02F3A62Bu, 0u };
}
public override ArcFile TryOpen (ArcView file)
{
var arc_name = Path.GetFileName (file.Name).ToLowerInvariant();
ResourceType resource_type = ResourceType.Undefined;
if ("sound.dat" == arc_name)
resource_type = ResourceType.Sound;
else if ("graph.dat" == arc_name)
resource_type = ResourceType.Graphics;
else
return null;
var index = new EncryptedIndexReader (file, resource_type);
var dir = index.GetIndex();
if (null == dir)
return null;
if (index.IsEncrypted)
return new PiasEncryptedArchive (file, this, dir);
else
return new ArcFile (file, this, dir);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var input = arc.OpenBinaryEntry (entry);
return new EncryptedGraphDecoder (input);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (entry.Type != "audio")
return OpenEncrypted (arc, entry);
var format = new WaveFormat
{
FormatTag = 1,
Channels = 2,
SamplesPerSecond = 22050,
AverageBytesPerSecond = 88200,
BitsPerSample = 16,
BlockAlign = 4,
};
return OpenAudioEntry (arc, entry, format);
}
public Stream OpenEncrypted (ArcFile arc, Entry entry)
{
uint seed = arc.File.View.ReadUInt32 (entry.Offset);
var stream = arc.File.CreateStream (entry.Offset+4, entry.Size);
var key = new KeyGenerator (0);
key.Seed (seed);
return new InputCryptoStream (stream, new PiasTransform (key));
}
}
internal class KeyGenerator
{
int m_type;
uint m_seed;
// 0 -> graph.dat
// 1 -> text.dat
// 2 -> save.dat
public KeyGenerator (int type)
{
m_type = type;
m_seed = 0;
}
public void Seed (uint seed)
{
m_seed = seed;
}
public uint Next ()
{
uint y, x;
if (0 == m_type)
{
x = 0xD22;
y = 0x849;
}
else if (1 == m_type)
{
x = 0xF43;
y = 0x356B;
}
else if (2 == m_type)
{
x = 0x292;
y = 0x57A7;
}
else
{
x = 0;
y = 0;
}
uint a = x + m_seed * y;
uint b = 0;
if ((a & 0x400000) != 0)
b = 1;
if ((a & 0x400) != 0)
b ^= 1;
if ((a & 1) != 0)
b ^= 1;
m_seed = (a >> 1) | (b != 0 ? 0x80000000u : 0u);
return m_seed;
}
}
internal sealed class PiasTransform : ByteTransform
{
KeyGenerator m_key;
public PiasTransform (KeyGenerator key)
{
m_key = key;
}
public override int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount,
byte[] outputBuffer, int outputOffset)
{
for (int i = 0; i < inputCount; ++i)
{
outputBuffer[outputOffset++] = (byte)(m_key.Next() ^ inputBuffer[inputOffset+i]);
}
return inputCount;
}
}
internal class EncryptedGraphDecoder : BinaryImageDecoder
{
public EncryptedGraphDecoder (IBinaryStream input) : base (input, new ImageMetaData { BPP = 16 })
{
m_input.ReadInt32(); // skip size
Info.Width = m_input.ReadUInt16() & 0x3FFu;
Info.Height = m_input.ReadUInt16() & 0x3FFu;
}
protected override ImageData GetImageData ()
{
m_input.Position = 8;
int width = Info.iWidth;
int output_size = width * Info.iHeight;
var pixels = new ushort[output_size];
var prev = new int[8];
int dst = 0;
while (dst < output_size)
{
int count;
ushort w = m_input.ReadUInt16();
if ((w & 0x2000) != 0)
{
count = (((w >> 1) & 0x6000 | w & 0x1000) >> 12) + 1;
int idx = prev[count - 1]++ % 19;
int off = w & 0xFFF;
bool step_back = (off & StepBackMask[idx]) != 0;
bool step_vertical = (off & StepVerticalMask[idx]) != 0;
int m = (off & OffsetMask0[idx]) | (off >> 1) & (OffsetMask1[idx] >> 1) | (off >> 2) & (OffsetMask2[idx] >> 2);
int n = 16 - width * ((m + 16) / 32);
int p = m + 16;
int hidword = p >> 31;
p = (p & ~0xFF) | ((hidword & 0xFF) ^ (m + 16));
int src = dst + n - (hidword ^ ((p - hidword) & 0x1F) - hidword);
count = Math.Min (count, output_size - dst);
if (step_vertical)
{
if (step_back)
{
for (int i = 0; i < count; ++i)
{
pixels[dst+i] = pixels[src];
src -= width;
}
}
else
{
int step = width;
for (int i = 0; i < count; ++i)
{
pixels[dst+i] = pixels[src];
src += width;
}
}
}
else if (step_back)
{
for (int i = 0; i < count; ++i)
{
pixels[dst+i] = pixels[src--];
}
}
else
{
for (int i = 0; i < count; ++i)
{
pixels[dst+i] = pixels[src++];
}
}
}
else
{
pixels[dst] = (ushort)((w >> 1) & 0x6000 | w & 0x1FFF);
count = 1;
}
dst += count;
}
int stride = width * 2;
return ImageData.Create (Info, PixelFormats.Bgr555, null, pixels, stride);
}
static readonly ushort[] OffsetMask2 = {
0, 0x800, 0x0C00, 0x0E00, 0x800, 0x0FC0, 0, 0x0F00, 0x0FF0, 0x0FF0, 0x0C00, 0x0F00, 0x800, 0x0E00, 0x0F00, 0x0C00, 0x0C00, 0x0F80, 0,
};
static readonly ushort[] OffsetMask1 = {
0, 0, 0, 0x0F0, 0x200, 0x18, 0x7C0, 0x7E, 0, 0, 0x1FE, 0x7E, 0x3E0, 0x0C0, 0x78, 0x1F0, 0, 0x30, 0x7E0,
};
static readonly ushort[] OffsetMask0 = {
0x3FF, 0x1FF, 0x0FF, 7, 0x0FF, 3, 0x1F, 0, 3, 3, 0, 0, 0x0F, 0x1F, 3, 7, 0x0FF, 7, 0x0F,
};
static readonly ushort[] StepBackMask = {
0x800, 0x400, 0x100, 8, 0x400, 0x20, 0x20, 0x80, 4, 8, 0x200, 1, 0x10, 0x100, 4, 0x200, 0x100, 0x40, 0x800,
};
static readonly ushort[] StepVerticalMask = {
0x400, 0x200, 0x200, 0x100, 0x100, 4, 0x800, 1, 8, 4, 1, 0x80, 0x400, 0x20, 0x80, 8, 0x200, 8, 0x10,
};
}
}

View File

@ -29,9 +29,7 @@ using System.IO;
namespace GameRes.Formats.PlanTech
{
#if DEBUG
[Export(typeof(ArchiveFormat))]
#endif
public class PacOpener : ArchiveFormat
{
public override string Tag { get { return "PAC/PLANTECH"; } }

View File

@ -29,9 +29,7 @@ using System.Windows.Media;
namespace GameRes.Formats.PlanTech
{
#if DEBUG
[Export(typeof(ImageFormat))]
#endif
public class PacFormat : ImageFormat
{
public override string Tag { get { return "PAC/PLANTECH"; } }

125
Legacy/Ponytail/ArcBND.cs Normal file
View File

@ -0,0 +1,125 @@
//! \file ArcBND.cs
//! \date 2023 Sep 28
//! \brief Ponytail Adventure 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.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
// [951115][Ponytail Soft] Masuzume Yume
namespace GameRes.Formats.Ponytail
{
[Export(typeof(ArchiveFormat))]
public class BndOpener : ArchiveFormat
{
public override string Tag => "BND/NMI";
public override string Description => "Ponytail Soft resource archive";
public override uint Signature => 0x646E6942; // 'Bind'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, " ver.0"))
return null;
int count = file.View.ReadInt16 (0xD);
if (!IsSaneCount (count))
return null;
uint index_offset = file.View.ReadUInt32 (0xF);
if (index_offset >= file.MaxOffset)
return null;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 8).Trim();
var ext = file.View.ReadString (index_offset+8, 3);
name = name + '.' + ext;
var entry = Create<PackedEntry> (name);
entry.Size = file.View.ReadUInt32 (index_offset+0x0C);
entry.Offset = file.View.ReadUInt32 (index_offset+0x14);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 0x18;
}
foreach (PackedEntry entry in dir.Where (e => e.Name.EndsWith ("Z") && e.Type != "image"))
{
if (file.View.AsciiEqual (entry.Offset, "lz1_"))
{
entry.IsPacked = true;
char last_chr =(char)file.View.ReadByte (entry.Offset+4);
entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset+5);
string name = entry.Name.Remove (entry.Name.Length-1);
entry.Name = name + char.ToUpperInvariant (last_chr);
}
}
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 output = new byte[pent.UnpackedSize];
using (var input = arc.File.CreateStream (pent.Offset+9, pent.Size-9))
Lz1Unpack (input, output);
return new BinMemoryStream (output, pent.Name);
}
internal static void Lz1Unpack (IBinaryStream input, byte[] output)
{
byte mask = 0;
int ctl = 0;
int dst = 0;
while (dst < output.Length)
{
mask <<= 1;
if (0 == mask)
{
ctl = input.ReadUInt8();
if (ctl < 0)
break;
mask = 1;
}
if ((ctl & mask) != 0)
{
output[dst++] = input.ReadUInt8();
}
else
{
int code = input.ReadUInt16();
int offset = (code >> 5) + 1;
int count = Math.Min (3 + (code & 0x1F), output.Length - dst);
Binary.CopyOverlapped (output, dst - offset, dst, count);
dst += count;
}
}
}
}
}

212
Legacy/Ponytail/ImageTCZ.cs Normal file
View File

@ -0,0 +1,212 @@
//! \file ImageTCZ.cs
//! \date 2023 Sep 28
//! \brief Ponytail NMI 2.5 image format (PC-98).
//
// 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;
// Graphics driver version 0.3 95/10/30
// by Y.Nakamura / NARIMI.A
namespace GameRes.Formats.Ponytail
{
[Export(typeof(ImageFormat))]
public class TczFormat : ImageFormat
{
public override string Tag => "TCZ";
public override string Description => "Ponytail Soft NMI image format";
public override uint Signature => 0x20494D4E; // 'NMI '
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
if (!header.AsciiEqual (4, "2.5\0"))
return null;
var info = new ImageMetaData {
Width = header.ToUInt16 (0xC),
Height = header.ToUInt16 (0xE),
OffsetX = header.ToInt16 (0x8),
OffsetY = header.ToInt16 (0xA),
BPP = 4,
};
if (info.Height > TczReader.MaxHeight)
return null;
return info;
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new TczReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("TczFormat.Write not implemented");
}
}
internal class TczReader : TszReader
{
internal const int MaxHeight = 0x190;
const int BufferSize = MaxHeight * 0x10;
public TczReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
m_stride = m_info.iWidth >> 1;
}
byte[] m_buffer;
static short[] s_offTable0 = { -4, -3, -2, -1, 0, 1, 2, 3 };
static short[] s_offTable1 = { -16, -8, -6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6, 8, 10, 16 };
public new ImageData Unpack ()
{
m_input.Position = 0x10;
var palette = ReadPalette();
var output = new byte[m_stride * m_info.iHeight];
FillBuffers();
ResetBitReader();
int dst = 2;
int output_pos = 0;
int x = 0;
while (x < m_stride)
{
int y = 0;
while (y < m_info.iHeight)
{
if (GetNextBit()) // @1@
{
int src = dst;
if (!GetNextBit()) // @2@
{
src += m_lineOffsets1[1];
int off = GetBits (4);
src += s_offTable1[off];
}
else if (!GetNextBit()) // @3@
{
src += GetBits (2) - 4;
}
else
{
if (!GetNextBit()) // @4@
{
src += m_lineOffsets1[2];
}
else if (!GetNextBit()) // @5@
{
src += m_lineOffsets1[4];
}
else
{
src += m_lineOffsets1[8];
}
int off = GetBits (3);
src += s_offTable0[off];
}
src &= 0xFFFF;
int count = GetBitLength() + 1;
Binary.CopyOverlapped (m_buffer, src, dst, count);
y += count;
dst += count;
}
else
{
int bx = m_buffer[dst-2];
bx = (bx << 8 | bx) & 0xF00F;
if (GetNextBit()) // @8@
{
int ax = GetBits (4);
bx = (bx & 0xFF) | ax << 12;
}
if (GetNextBit()) // @9@
{
int ax = GetBits (4);
bx = (bx & 0xFF00) | ax;
}
bx = (bx & 0xFF) | (bx >> 8);
m_buffer[dst++] = (byte)bx;
++y;
}
}
++x;
if ((x & 3) == 0)
{
CopyScanline (m_lineOffsets0[(x - 1) & 0xC], output, output_pos);
output_pos += 4;
}
int z = x & 0xF;
dst = m_lineOffsets0[z];
if (z != 0)
{
m_lineOffsets1[z] -= BufferSize;
}
else
{
for (int i = 1; i < 16; ++i)
{
m_lineOffsets1[i] += BufferSize;
}
}
}
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride);
}
ushort[] m_lineOffsets0 = new ushort[16];
ushort[] m_lineOffsets1 = new ushort[16];
void FillBuffers ()
{
m_buffer = new byte[BufferSize + MaxHeight];
m_buffer[0] = m_buffer[1] = 3;
ushort p = 2;
for (int i = 0; i < 16; ++i)
{
m_lineOffsets1[i] = (ushort)(((16 - i) & 0xF) * MaxHeight);
m_lineOffsets0[i] = p;
p += MaxHeight;
}
}
void CopyScanline (int src, byte[] output, int dst)
{
for (int i = 0; i < m_info.iHeight; ++i)
{
output[dst ] = m_buffer[src + i ];
output[dst+1] = m_buffer[src + i + MaxHeight ];
output[dst+2] = m_buffer[src + i + MaxHeight*2];
output[dst+3] = m_buffer[src + i + MaxHeight*3];
dst += m_stride;
}
}
}
}

330
Legacy/Ponytail/ImageTSZ.cs Normal file
View File

@ -0,0 +1,330 @@
//! \file ImageTSZ.cs
//! \date 2023 Sep 27
//! \brief Ponytail NMI 2.05 image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [930413][Ponytail Soft] Yougen Doumu
namespace GameRes.Formats.Ponytail
{
[Export(typeof(ImageFormat))]
public class TszFormat : ImageFormat
{
public override string Tag => "TSZ";
public override string Description => "Ponytail Soft NMI image format";
public override uint Signature => 0x20494D4E; // 'NMI '
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
if (!header.AsciiEqual (4, "2.05"))
return null;
return new ImageMetaData {
Width = (uint)header.ToUInt16 (0xC) << 2,
Height = header.ToUInt16 (0xE),
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new TszReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("TszFormat.Write not implemented");
}
}
internal class TszReader
{
protected IBinaryStream m_input;
protected ImageMetaData m_info;
protected int m_stride;
protected TszReader () { }
public TszReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
m_stride = m_info.iWidth >> 1;
}
private int m_previous_row;
private ushort[] m_linebuffer;
private int m_dst;
public ImageData Unpack ()
{
m_input.Position = 0x10;
var palette = ReadPalette();
var output = new byte[m_stride * m_info.iHeight];
m_linebuffer = new ushort[m_info.iHeight * 2];
m_previous_row = m_info.iHeight;
int width = m_info.iWidth >> 2;
int dst_x = 0;
int x = 0;
ResetBitReader();
while (x < width)
{
int y = 0;
m_dst = 0;
if ((x & 1) != 0)
m_dst += m_info.iHeight;
while (y < m_info.iHeight)
{
int ctl = 0;
while (GetNextBit())
{
++ctl;
}
int count; // bx
switch (ctl)
{
case 0: count = CopyMethod0(); break;
case 1: count = CopyMethod1(); break;
case 2:
m_linebuffer[m_dst] = m_input.ReadUInt16();
count = 1;
break;
case 3: count = CopyMethod3(); break;
case 4: count = CopyMethod4(); break;
default: throw new InvalidFormatException();
}
m_dst += count;
y += count;
}
if ((x & 1) != 0)
{
CopyScanline (output, dst_x);
dst_x += 4;
}
m_previous_row = -m_previous_row;
++x;
}
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride);
}
void CopyScanline (byte[] output, int dst)
{
for (int i = 0; i < m_info.iHeight; ++i)
{
ushort px1 = m_linebuffer[i];
ushort px2 = m_linebuffer[i + m_info.iHeight];
// these bytes contain 8 pixels that are being put into 4 planes
int b0 = (px1 << 4) & 0xF0 | (px2 ) & 0x0F;
int b1 = (px1 ) & 0xF0 | (px2 >> 4) & 0x0F;
int b2 = (px1 >> 4) & 0xF0 | (px2 >> 8) & 0x0F;
int b3 = (px1 >> 8) & 0xF0 | (px2 >> 12) & 0x0F;
// repack pixels into flat surface, 2 pixels per byte
for (int j = 0; j < 8; j += 2)
{
byte px = (byte)((((b0 << j) & 0x80) >> 3)
| (((b1 << j) & 0x80) >> 2)
| (((b2 << j) & 0x80) >> 1)
| (((b3 << j) & 0x80) ));
px |= (byte)((((b0 << j) & 0x40) >> 6)
| (((b1 << j) & 0x40) >> 5)
| (((b2 << j) & 0x40) >> 4)
| (((b3 << j) & 0x40) >> 3));
output[dst+j/2] = px;
}
dst += m_stride;
}
}
int CopyMethod0 ()
{
int count = GetBitLength();
int offset = GetBits (4);
offset += m_previous_row - 8;
CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count);
return count;
}
int CopyMethod1 ()
{
int count = GetBitLength();
int offset = m_input.ReadUInt8();
offset += m_previous_row - 0x80;
CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count);
return count;
}
int CopyMethod3 ()
{
int count = GetBitLength();
int offset = GetBits (4);
offset -= 0x10;
CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count);
return count;
}
int CopyMethod4 ()
{
byte al = m_input.ReadUInt8();
int nibble = al >> 4;
ushort mask1 = s_pattern1[al >> 4];
ushort mask2 = s_pattern2[al & 0xF];
ushort pixel = m_linebuffer[m_dst + m_previous_row];
pixel &= mask1;
for (int i = 0; i < 4; ++i)
{
short carry = (short)(nibble & 1);
nibble >>= 1;
pixel |= (ushort)(-carry & mask2);
pixel = RotU16R (pixel, 1);
}
pixel = RotU16L (pixel, 4);
m_linebuffer[m_dst] = pixel;
return 1;
}
static readonly ushort[] s_pattern1 = new ushort[] {
0xFFFF, 0xEEEE, 0xDDDD, 0xCCCC, 0xBBBB, 0xAAAA, 0x9999, 0x8888,
0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000,
};
static readonly ushort[] s_pattern2 = new ushort[] {
0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111,
0x1000, 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111,
};
ushort m_bits;
int m_bit_count;
protected void ResetBitReader ()
{
m_bits = 0;
m_bit_count = 0;
}
protected int GetBitLength ()
{
if (!GetNextBit())
return 1;
int count = 1;
while (GetNextBit())
{
++count;
}
return GetBits (count) | 1 << count;
}
protected bool GetNextBit ()
{
if (--m_bit_count < 0)
{
m_bits = m_input.ReadUInt16();
m_bit_count = 15;
}
bool bit = (m_bits & 0x8000) != 0;
m_bits <<= 1;
return bit;
}
static readonly ushort[] s_bit_mask = new ushort[] {
0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
};
protected int GetBits (int count)
{
m_bit_count -= count;
if (m_bit_count < 0)
{
m_bits = RotU16L (m_bits, count);
int cl = -m_bit_count;
ushort bits = m_input.ReadUInt16();
bits = RotU16L (bits, cl);
ushort mask = s_bit_mask[cl];
ushort new_bits = (ushort)(bits & ~mask);
bits &= mask;
bits |= m_bits;
m_bits = new_bits;
m_bit_count = 16 - cl;
return bits;
}
else
{
m_bits = RotU16L (m_bits, count);
ushort mask = s_bit_mask[count];
int bits = m_bits & mask;
m_bits &= (ushort)~mask;
return bits;
}
}
protected BitmapPalette ReadPalette ()
{
const int count = 16;
var colors = new Color[count];
for (int i = 0; i < count; ++i)
{
byte r = m_input.ReadUInt8();
byte g = m_input.ReadUInt8();
byte b = m_input.ReadUInt8();
colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
static internal ushort RotU16L (ushort val, int count)
{
return (ushort)(val << count | val >> (16 - count));
}
static internal ushort RotU16R (ushort val, int count)
{
return (ushort)(val >> count | val << (16 - count));
}
static internal void CopyOverlapped (ushort[] data, int src, int dst, int count)
{
src <<= 1;
dst <<= 1;
count <<= 1;
if (dst > src)
{
while (count > 0)
{
int preceding = Math.Min (dst - src, count);
Buffer.BlockCopy (data, src, data, dst, preceding);
dst += preceding;
count -= preceding;
}
}
else
{
Buffer.BlockCopy (data, src, data, dst, count);
}
}
}
}

View File

@ -28,6 +28,7 @@ using System.ComponentModel.Composition;
using System.IO;
using GameRes.Compression;
// [031219][Project-μ] Gin no Hebi Kuro no Tsuki
// [040528][Lakshmi] Mabuta Tojireba Soko ni...
namespace GameRes.Formats.ProjectMu

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.0.10.208")]
[assembly: AssemblyFileVersion ("1.0.10.208")]
[assembly: AssemblyVersion ("1.0.10.212")]
[assembly: AssemblyFileVersion ("1.0.10.212")]

68
Legacy/RedZone/ArcPAK.cs Normal file
View File

@ -0,0 +1,68 @@
//! \file ArcPAK.cs
//! \date 2023 Sep 18
//! \brief RED-ZONE 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;
// [010706][RED-ZONE] Kenkyuu Nisshi
namespace GameRes.Formats.RedZone
{
[Export(typeof(ArchiveFormat))]
public class PakOpener : ArchiveFormat
{
public override string Tag => "PAK/REDZONE";
public override string Description => "RED-ZONE resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
uint index_offset = 4;
const uint index_entry_size = 0x54;
long min_offset = index_offset + count * index_entry_size;
if (min_offset >= file.MaxOffset)
return null;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 0x44);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0x44);
entry.Size = file.View.ReadUInt32 (index_offset+0x48);
if (entry.Offset < min_offset || !entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += index_entry_size;
}
return new ArcFile (file, this, dir);
}
}
}

View File

@ -0,0 +1,74 @@
//! \file ScriptQDO.cs
//! \date 2023 Sep 21
//! \brief RED-ZONE binary script.
//
// 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;
namespace GameRes.Formats.RedZone
{
[Export(typeof(ScriptFormat))]
public class QdoOpener : GenericScriptFormat
{
public override string Tag => "QDO";
public override string Description => "Red-Zone script file";
public override uint Signature => 0x5F4F4451; // 'QDO_SHO'
public override bool IsScript (IBinaryStream file)
{
var header = file.ReadHeader (8);
return header.AsciiEqual ("QDO_SHO");
}
const int ScriptDataPos = 0x0E;
public override Stream ConvertFrom (IBinaryStream file)
{
var data = file.ReadBytes ((int)file.Length);
if (data[0xC] != 0)
{
for (int i = ScriptDataPos; i < data.Length; ++i)
{
data[i] = (byte)~(data[i] - 13);
}
data[0xC] = 0;
}
return new BinMemoryStream (data, file.Name);
}
public override Stream ConvertBack (IBinaryStream file)
{
var data = file.ReadBytes ((int)file.Length);
if (data[0xC] == 0)
{
for (int i = ScriptDataPos; i < data.Length; ++i)
{
data[i] = (byte)(~data[i] + 13);
}
data[0xC] = 1;
}
return new BinMemoryStream (data, file.Name);
}
}
}

146
Legacy/Sophia/ArcNOR.cs Normal file
View File

@ -0,0 +1,146 @@
//! \file ArcNOR.cs
//! \date 2023 Sep 26
//! \brief Sophia 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;
// [991210][Sophia] Film Noir
namespace GameRes.Formats.Sophia
{
internal class NorEntry : PackedEntry
{
public int Method;
}
[Export(typeof(ArchiveFormat))]
public class NorOpener : ArchiveFormat
{
public override string Tag => "NOR";
public override string Description => "Sophia resource archive";
public override uint Signature => 0;
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!file.View.AsciiEqual (4, "NRCOMB01\0") || !IsSaneCount (count))
return null;
var dir = new List<Entry> (count);
using (var index = file.CreateStream())
{
index.Position = 0x10;
for (int i = 0; i < count; ++i)
{
uint offset = index.ReadUInt32();
uint size = index.ReadUInt32();
string name = index.ReadCString();
var entry = Create<NorEntry> (name);
entry.Offset = offset;
entry.Size = size;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var nent = (NorEntry)entry;
if (!nent.IsPacked)
{
if (nent.Method == 0x1F4 || nent.Method == 0x67
|| !arc.File.View.AsciiEqual (nent.Offset, "NCMB01"))
return base.OpenEntry (arc, nent);
nent.Method = arc.File.View.ReadInt32 (nent.Offset+0x28);
if (nent.Method == 0x1F4 || nent.Method == 0x67)
{
nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x24);
nent.Offset += 0x2C;
return base.OpenEntry (arc, nent);
}
nent.IsPacked = true;
nent.UnpackedSize = arc.File.View.ReadUInt32 (nent.Offset+0x24);
nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x10);
nent.Offset += 0x2C;
}
using (var input = arc.File.CreateStream (nent.Offset, nent.Size))
{
var output = new byte[nent.UnpackedSize];
NcmbDecompress (input, output);
return new BinMemoryStream (output, nent.Name);
}
}
internal static void NcmbDecompress (IBinaryStream input, byte[] output)
{
var dict = new int[0xC00];
int root = input.ReadInt32();
int tree_size = input.ReadInt32();
int unpacked_size = input.ReadInt32();
int count = root + tree_size - 0xFF;
while (count --> 0)
{
int token = 6 * input.ReadInt32();
dict[token ] = input.ReadInt32();
dict[token + 1] = input.ReadInt32();
}
if (unpacked_size > 0)
{
int cur_byte = 0;
int mask = 0;
for (int dst = 0; dst < unpacked_size; ++dst)
{
int token = root;
do
{
if (0 == mask)
{
cur_byte = input.ReadUInt8();
mask = 0x80;
}
if ((cur_byte & mask) != 0)
token = dict[6 * token + 1];
else
token = dict[6 * token];
mask >>= 1;
}
while (dict[6 * token] != -1);
output[dst] = (byte)token;
}
}
}
}
[Export(typeof(ResourceAlias))]
[ExportMetadata("Extension", "M")]
[ExportMetadata("Target", "MP3")]
[ExportMetadata("Type", "audio")]
public class MFormat : ResourceAlias { }
}

114
Legacy/SquadraD/ArcPLA.cs Normal file
View File

@ -0,0 +1,114 @@
//! \file ArcPLA.cs
//! \date 2023 Sep 26
//! \brief Squadra D audio 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;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.SquadraD
{
internal class PlaEntry : PackedEntry
{
public int Id;
public int n1;
public uint SampleRate;
public int Channels;
public byte n2;
public byte n3;
public int[] Data;
}
[Export(typeof(ArchiveFormat))]
public class PlaOpener : ArchiveFormat
{
public override string Tag => "PLA";
public override string Description => "Squadra D audio archive";
public override uint Signature => 0x2E616C50; // 'Pla.'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
uint arc_size = file.View.ReadUInt32 (4);
if (arc_size != file.MaxOffset || file.View.ReadUInt32 (0x10) != 2)
return null;
uint check = (arc_size & 0xD5555555u) << 1 | arc_size & 0xAAAAAAAAu;
if (check != file.View.ReadUInt32 (8))
return null;
int count = file.View.ReadUInt16 (0xE);
if (!IsSaneCount (count))
return null;
var dir = new List<Entry> (count);
using (var index = file.CreateStream())
{
index.Position = 0x14;
for (int i = 0; i < count; ++i)
{
var entry = new PlaEntry {
Id = index.ReadInt32()
};
entry.Name = entry.Id.ToString ("D5");
dir.Add (entry);
}
foreach (PlaEntry entry in dir)
{
entry.n1 = index.ReadInt32();
entry.SampleRate = index.ReadUInt32();
entry.Channels = index.ReadInt32();
entry.n2 = index.ReadUInt8();
entry.n3 = index.ReadUInt8();
index.ReadInt16();
}
foreach (PlaEntry entry in dir)
{
entry.Offset = index.ReadUInt32();
}
foreach (PlaEntry entry in dir)
{
int n = entry.Channels * 2;
entry.Data = new int[n];
for (int j = 0; j < n; ++j)
entry.Data[j] = index.ReadInt32();
}
}
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);
}
/*
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
return base.OpenEntry (arc, entry);
}
*/
}
}

121
Legacy/SquadraD/ArcSDA.cs Normal file
View File

@ -0,0 +1,121 @@
//! \file ArcSDA.cs
//! \date 2023 Sep 26
//! \brief Squadra D resource 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;
namespace GameRes.Formats.SquadraD
{
[Export(typeof(ArchiveFormat))]
public class SdaOpener : ArchiveFormat
{
public override string Tag => "SDA/SD";
public override string Description => "Squadra D resource archive";
public override uint Signature => 0x4153; // 'SA'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public SdaOpener ()
{
Signatures = new[] { 0x4153u, 0xCC004153u, 0u };
}
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (0, "SA\0"))
return null;
int data_offset = file.View.ReadInt32 (4);
if (data_offset <= 8 || data_offset >= file.MaxOffset)
return null;
int count = (data_offset - 8) / 0x14;
if (!IsSaneCount (count))
return null;
string arc_name = Path.GetFileNameWithoutExtension (file.Name);
bool is_cg = arc_name == "g";
uint index = 8;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index, 0x10).Trim();;
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index+0xC) + data_offset;
entry.Size = file.View.ReadUInt32 (index+0x10);
if (is_cg)
entry.Type = "image";
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index += 0x14;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
{
var data = LzssDecompress (input);
return new BinMemoryStream (data, entry.Name);
}
}
internal static byte[] LzssDecompress (IBinaryStream input)
{
int unpacked_size = input.ReadInt32();
var output = new byte[unpacked_size];
using (var bits = new LsbBitStream (input.AsStream, true))
{
var frame = new byte[0x1000];
int frame_pos = 0xFC0;
int dst = 0;
while (dst < unpacked_size)
{
if (bits.GetNextBit() == 0)
{
byte b = (byte)bits.GetBits (8);
output[dst++] = frame[frame_pos++ & 0xFFF] = b;
}
else
{
int count_len = 4;
if (bits.GetNextBit() != 0)
count_len = 6;
int offset = bits.GetBits (12);
int count = bits.GetBits (count_len);
count = Math.Min (count + 3, unpacked_size - dst);
while (count --> 0)
{
byte b = frame[offset++ & 0xFFF];
output[dst++] = frame[frame_pos++ & 0xFFF] = b;
}
}
}
return output;
}
}
}
}

141
Legacy/System98/ArcLIB.cs Normal file
View File

@ -0,0 +1,141 @@
//! \file ArcLIB.cs
//! \date 2023 Oct 15
//! \brief System-98 resource archive (PC-98).
//
// 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;
namespace GameRes.Formats.System98
{
[Export(typeof(ArchiveFormat))]
public class LibOpener : ArchiveFormat
{
public override string Tag => "LIB/SYSTEM98";
public override string Description => "System-98 engine resource archive";
public override uint Signature => 0x3062694C; // 'Lib0'
public override bool IsHierarchic => false;
public override bool CanWrite => false;
public override ArcFile TryOpen (ArcView file)
{
var cat_name = Path.ChangeExtension (file.Name, ".CAT");
if (!VFS.FileExists (cat_name))
return null;
int count;
byte[] index;
using (var cat = VFS.OpenView (cat_name))
{
count = cat.View.ReadInt16 (4);
if (!IsSaneCount (count))
return null;
int index_size = count * 0x16;
if (cat.View.AsciiEqual (0, "Cat0"))
{
index = file.View.ReadBytes (6, (uint)index_size);
}
else if (cat.View.AsciiEqual (0, "Cat1"))
{
index = new byte[index_size];
using (var input = cat.CreateStream (6))
LzssUnpack (input, index);
}
else
return null;
}
int pos = 0;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = Binary.GetCString (index, pos, 0xC).TrimEnd();
var entry = Create<PackedEntry> (name);
entry.Size = index.ToUInt32 (pos+0xE);
entry.Offset = index.ToUInt32 (pos+0x12);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.IsPacked = index[pos+0xC] != 0;
dir.Add (entry);
pos += 0x16;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = entry as PackedEntry;
if (null == pent || !pent.IsPacked)
return base.OpenEntry (arc, entry);
if (pent.UnpackedSize == 0)
pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset+6);
var data = new byte[pent.UnpackedSize];
using (var input = arc.File.CreateStream (entry.Offset+10, entry.Size-10))
{
int length = LzssUnpack (input, data);
return new BinMemoryStream (data, 0, length, entry.Name);
}
}
internal static int LzssUnpack (IBinaryStream input, byte[] output)
{
var frame = new byte[0x1000];
int frame_pos = 1;
int ctl = 0;
byte mask = 0;
int dst = 0;
while (dst < output.Length)
{
mask <<= 1;
if (0 == mask)
{
ctl = input.ReadByte();
if (-1 == ctl)
break;
mask = 1;
}
if (input.PeekByte() == -1)
break;
if ((ctl & mask) != 0)
{
output[dst++] = frame[frame_pos++ & 0xFFF] = input.ReadUInt8();
}
else
{
int lo = input.ReadByte();
int hi = input.ReadByte();
if (-1 == hi)
break;
int count = (lo & 0xF) + 3;
int off = hi << 4 | lo >> 4;
while (count --> 0)
{
byte b = frame[off++ & 0xFFF];
output[dst++] = frame[frame_pos++ & 0xFFF] = b;
}
}
}
return dst;
}
}
}

337
Legacy/System98/ImageG.cs Normal file
View File

@ -0,0 +1,337 @@
//! \file ImageG.cs
//! \date 2023 Oct 15
//! \brief System-98 engine image format (PC-98).
//
// 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;
// [951216][Four-Nine] Lilith
namespace GameRes.Formats.System98
{
[Export(typeof(ImageFormat))]
public class GFormat : ImageFormat
{
public override string Tag => "G/SYSTEM98";
public override string Description => "System-98 engine image format";
public override uint Signature => 0;
public GFormat ()
{
Extensions = new[] { "g", "" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (file.Length < 61)
return null;
var header = file.ReadHeader (0xA);
ushort width = Binary.BigEndian (header.ToUInt16 (6));
ushort height = Binary.BigEndian (header.ToUInt16 (8));
if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400)
return null;
return new ImageMetaData {
Width = width,
Height = height,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 0xA;
var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb);
var reader = new GraBaseReader (file, info);
reader.UnpackBits();
return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GFormat.Write not implemented");
}
}
/// <summary>
/// This compression format is used in several PC-98 game engines.
/// </summary>
internal class GraBaseReader
{
protected IBinaryStream m_input;
protected ImageMetaData m_info;
protected int m_output_stride;
protected byte[] m_pixels;
protected int m_dst;
public byte[] Pixels => m_pixels;
public int Stride => m_output_stride;
public GraBaseReader (IBinaryStream file, ImageMetaData info)
{
m_input = file;
m_info = info;
m_output_stride = m_info.iWidth >> 1;
m_pixels = new byte[m_output_stride * m_info.iHeight];
}
protected ushort[] m_buffer;
public void UnpackBits ()
{
try
{
UnpackBitsInternal();
}
catch (EndOfStreamException)
{
FlushBuffer();
}
}
void UnpackBitsInternal ()
{
int width = m_info.iWidth;
int wTimes2 = width << 1;
int wTimes4 = width << 2;
int buffer_size = wTimes4 + wTimes2;
m_buffer = new ushort[buffer_size >> 1];
m_dst = 0;
InitFrame();
InitBitReader();
ushort p = ReadPair (0);
for (int i = 0; i < width; ++i)
m_buffer[i] = p;
int dst = wTimes2;
int prev_src = 0;
while (m_dst < m_pixels.Length)
{
bool same_line = false;
int src = -width;
if (GetNextBit() != 0)
{
if (GetNextBit() == 0)
src <<= 1;
else if (GetNextBit() == 0)
src += 1;
else
src -= 1;
}
else if (GetNextBit() == 0)
{
src = -4;
p = m_buffer[dst/2-1];
if ((p & 0xFF) == (p >> 8))
same_line = src != prev_src;
}
if (src != prev_src)
{
prev_src = src;
if (!same_line)
src += dst;
else
src = dst - 2;
if (GetNextBit() != 0)
{
int bitlength = 0;
do
{
++bitlength;
}
while (GetNextBit() != 0);
int count = 1;
while (bitlength --> 0)
count = count << 1 | GetNextBit();
int remaining = (buffer_size - dst) >> 1;
while (count > remaining)
{
count -= remaining;
MovePixels (m_buffer, src, dst, remaining);
src += remaining << 1;
if (FlushBuffer())
return;
dst = wTimes2;
src -= wTimes4;
remaining = wTimes4 >> 1;
}
MovePixels (m_buffer, src, dst, count);
dst += count << 1;
if (dst == buffer_size)
{
if (FlushBuffer())
return;
dst = wTimes2;
}
}
else
{
MovePixels (m_buffer, src, dst, 1);
dst += 2;
if (dst == buffer_size)
{
if (FlushBuffer())
return;
dst = wTimes2;
}
}
}
else
{
p = m_buffer[dst/2-1];
do
{
byte prev = (byte)(p >> 8);
p = ReadPair (prev);
m_buffer[dst >> 1] = p;
dst += 2;
if (dst == buffer_size)
{
if (FlushBuffer())
return;
dst = wTimes2;
}
}
while (GetNextBit() != 0);
prev_src = 0;
}
}
}
bool FlushBuffer ()
{
MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth);
int src = m_info.iWidth;
int count = Math.Min (m_info.iWidth << 1, m_pixels.Length - m_dst);
while (count --> 0)
{
ushort p = m_buffer[src++];
m_pixels[m_dst++] = (byte)((p & 0xF0) | p >> 12);
}
return m_dst == m_pixels.Length;
}
protected ushort ReadPair (int pos)
{
byte al = ReadPixel (pos);
byte ah = ReadPixel (al);
return (ushort)(al | ah << 8);
}
protected byte ReadPixel (int pos)
{
byte px = 0;
if (GetNextBit() == 0)
{
int count = 1;
if (GetNextBit() != 0)
{
if (GetNextBit() != 0)
{
count = count << 1 | GetNextBit();
}
count = count << 1 | GetNextBit();
}
count = count << 1 | GetNextBit();
pos += count;
px = m_frame[pos--];
while (count --> 0)
{
m_frame[pos+1] = m_frame[pos];
--pos;
}
m_frame[pos+1] = px;
}
else if (GetNextBit() == 0)
{
px = m_frame[pos];
}
else
{
px = m_frame[pos+1];
m_frame[pos+1] = m_frame[pos];
m_frame[pos] = px;
}
return px;
}
byte[] m_frame;
protected void InitFrame ()
{
m_frame = new byte[0x100];
int p = 0;
byte a = 0;
for (int j = 0; j < 0x10; ++j)
{
for (int i = 0; i < 0x10; ++i)
{
m_frame[p++] = a;
a -= 0x10;
}
a += 0x10;
}
}
protected void MovePixels (ushort[] pixels, int src, int dst, int count)
{
count <<= 1;
if (dst > src)
{
while (count > 0)
{
int preceding = Math.Min (dst - src, count);
Buffer.BlockCopy (pixels, src, pixels, dst, preceding);
dst += preceding;
count -= preceding;
}
}
else
{
Buffer.BlockCopy (pixels, src, pixels, dst, count);
}
}
int m_bits;
int m_bit_count;
protected void InitBitReader ()
{
m_bit_count = 1;
}
protected byte GetNextBit ()
{
if (--m_bit_count <= 0)
{
m_bits = m_input.ReadUInt8();
m_bit_count = 8;
}
int bit = (m_bits >> 7) & 1;
m_bits <<= 1;
return (byte)bit;
}
}
}

122
Legacy/Tiare/ImageGRA.cs Normal file
View File

@ -0,0 +1,122 @@
//! \file ImageGRA.cs
//! \date 2023 Oct 11
//! \brief Tiare image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [950616][JAST] Tenshi-tachi no Gogo ~Tenkousei~
// [950922][Tiare] Vanishing Point -Tenshi no Kieta Machi-
namespace GameRes.Formats.Tiare
{
internal class GraMetaData : ImageMetaData
{
public byte Flags;
public long DataOffset;
public bool HasPalette => (Flags & 0x80) == 0;
}
[Export(typeof(ImageFormat))]
public class GraFormat : ImageFormat
{
public override string Tag => "GRA/TIARE";
public override string Description => "Tiare image format";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x30);
int pos = header.IndexOf (0x1A);
if (-1 == pos)
return null;
++pos;
while (pos < header.Length && header[pos++] != 0)
;
if (pos + 3 >= header.Length || header[pos+3] != 4)
return null;
byte flags = header[pos];
file.Position = pos + 8;
int skip = Binary.BigEndian (file.ReadUInt16());
if (skip != 0)
file.Seek (skip, SeekOrigin.Current);
uint width = Binary.BigEndian (file.ReadUInt16());
uint height = Binary.BigEndian (file.ReadUInt16());
if (width == 0 || height == 0)
return null;
return new GraMetaData
{
Width = width,
Height = height,
BPP = 4,
Flags = flags,
DataOffset = file.Position,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var gra = (GraMetaData)info;
file.Position = gra.DataOffset;
BitmapPalette palette;
if (gra.HasPalette)
palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb);
else
palette = DefaultPalette;
var reader = new System98.GraBaseReader (file, info);
reader.UnpackBits();
return ImageData.Create (gra, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GraFormat.Write not implemented");
}
static readonly BitmapPalette DefaultPalette = new BitmapPalette (new Color[] {
#region Default palette
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0x00, 0x00, 0x77),
Color.FromRgb (0x77, 0x00, 0x00),
Color.FromRgb (0x77, 0x00, 0x77),
Color.FromRgb (0x00, 0x77, 0x00),
Color.FromRgb (0x00, 0x77, 0x77),
Color.FromRgb (0x77, 0x77, 0x00),
Color.FromRgb (0x77, 0x77, 0x77),
Color.FromRgb (0x00, 0x00, 0x00),
Color.FromRgb (0x00, 0x00, 0xFF),
Color.FromRgb (0xFF, 0x00, 0x00),
Color.FromRgb (0xFF, 0x00, 0xFF),
Color.FromRgb (0x00, 0xFF, 0x00),
Color.FromRgb (0x00, 0xFF, 0xFF),
Color.FromRgb (0xFF, 0xFF, 0x00),
Color.FromRgb (0xFF, 0xFF, 0xFF),
#endregion
});
}
}

107
Legacy/Tobe/ImageWBI.cs Normal file
View File

@ -0,0 +1,107 @@
//! \file ImageWBI.cs
//! \date 2023 Oct 18
//! \brief TOBE 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 System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
// [000915][TOBE] One's Own [or very]
namespace GameRes.Formats.Tobe
{
internal class WbiMetaData : ImageMetaData
{
public byte RleCode;
}
[Export(typeof(ImageFormat))]
public class WbiFormat : ImageFormat
{
public override string Tag => "WBI";
public override string Description => "TOBE image format";
public override uint Signature => 0x2D494257; // 'WBI-'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x20);
if (!header.AsciiEqual (4, "V1.00\0"))
return null;
return new WbiMetaData
{
Width = header.ToUInt16 (0xE),
Height = header.ToUInt16 (0x10),
BPP = 24,
RleCode = header[0x1C],
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var wbi = (WbiMetaData)info;
file.Position = 0x20;
int stride = wbi.iWidth * 3;
var pixels = new byte[stride * wbi.iHeight];
int dst = 0;
bool skip = false;
byte r = 0, g = 0, b = 0;
int count = 0;
while (dst < pixels.Length)
{
if (count <= 0)
{
b = file.ReadUInt8();
if (skip)
{
file.ReadByte();
skip = false;
}
g = file.ReadUInt8();
r = file.ReadUInt8();
count = 1;
if (wbi.RleCode == file.PeekByte())
{
count = file.ReadUInt16() >> 8;
if (count <= 0)
{
count = 1;
file.Seek (-2, SeekOrigin.Current);
skip = true;
}
}
}
--count;
pixels[dst++] = b;
pixels[dst++] = g;
pixels[dst++] = r;
}
return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("WbiFormat.Write not implemented");
}
}
}

234
Legacy/Ucom/ImageUG.cs Normal file
View File

@ -0,0 +1,234 @@
//! \file ImageUG.cs
//! \date 2023 Oct 16
//! \brief Ucom image format (PC-98).
//
// 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [961220][Ucom] Bunkasai
namespace GameRes.Formats.Ucom
{
[Export(typeof(ImageFormat))]
public class UgFormat : ImageFormat
{
public override string Tag => "UG";
public override string Description => "Ucom image format";
public override uint Signature => 0;
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".UG"))
return null;
int left = file.ReadUInt16();
int top = file.ReadUInt16();
int right = file.ReadUInt16();
int bottom = file.ReadUInt16();
int width = (right - left + 1) << 3;
int height = bottom - top + 1;
if (width <= 0 || height <= 0 || width > 640 || height > 512)
return null;
return new ImageMetaData
{
Width = (uint)width,
Height = (uint)height,
OffsetX = left,
OffsetY = top,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new UgReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("UgFormat.Write not implemented");
}
}
/// <summary>
/// Same compression algorithm as the base, but scanlines are vertical
/// </summary>
internal class UgReader : System98.GraBaseReader
{
public UgReader (IBinaryStream input, ImageMetaData info) : base (input, info)
{
}
public ImageData Unpack ()
{
m_input.Position = 8;
var palette = ReadPalette();
m_input.Position = 0x28;
try
{
UnpackBitsInternal();
}
catch (EndOfStreamException)
{
FlushBuffer();
}
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, Pixels, Stride);
}
void UnpackBitsInternal ()
{
int height = m_info.iHeight;
int hTimes2 = height << 1;
int hTimes4 = height << 2;
int buffer_size = hTimes4 * 3;
m_buffer = new ushort[buffer_size >> 1];
m_dst = 0;
InitFrame();
InitBitReader();
ushort p = ReadPair (0);
for (int i = 0; i < hTimes2+1; ++i)
m_buffer[i] = p;
int dst = hTimes4;
int prev_src = 0;
while (m_dst < m_pixels.Length)
{
bool same_line = false;
int src = -hTimes2;
if (GetNextBit() != 0) // @1@
{
if (GetNextBit() == 0) // @4@
src += 4;
else if (GetNextBit() == 0) // @5@
src -= 4;
else
src <<= 1;
}
else if (GetNextBit() == 0) // @2@
{
src = -4;
p = m_buffer[dst/2-1];
if ((p & 0xFF) == (p >> 8))
same_line = src != prev_src;
}
if (src != prev_src) // @6@
{
prev_src = src;
if (!same_line)
src += dst;
else
src = dst - 2;
if (GetNextBit() != 0) // @3@
{
int bitlength = 0;
do
{
++bitlength;
}
while (GetNextBit() != 0);
int count = 1;
while (bitlength --> 0)
count = count << 1 | GetNextBit();
MovePixels (m_buffer, src, dst, count);
dst += count << 1;
if (dst == buffer_size)
{
if (FlushBuffer())
return;
dst = hTimes4;
}
}
else
{
MovePixels (m_buffer, src, dst, 1);
dst += 2;
if (dst == buffer_size)
{
if (FlushBuffer())
return;
dst = hTimes4;
}
}
}
else
{
p = m_buffer[dst/2-1];
do
{
byte prev = (byte)(p >> 8);
p = ReadPair (prev);
m_buffer[dst >> 1] = p;
dst += 2;
if (dst == buffer_size)
{
if (FlushBuffer())
return;
dst = hTimes4;
}
}
while (GetNextBit() != 0);
prev_src = 0;
}
}
}
bool FlushBuffer ()
{
int height = m_info.iHeight;
int src_line = height << 1;
int dst = m_dst;
for (int i = 0; i < height; ++i)
{
int src = src_line;
for (int j = 0; j < 4; ++j)
{
ushort p = m_buffer[src];
m_pixels[dst+j] = (byte)((p & 0xF0) | p >> 12);
src += height;
}
src_line++;
dst += m_output_stride;
}
m_dst += 4;
MovePixels (m_buffer, height << 3, 0, height << 1);
return m_dst >= m_output_stride;
}
BitmapPalette ReadPalette ()
{
var colors = new Color[16];
for (int i = 0; i < 16; ++i)
{
ushort rgb = m_input.ReadUInt16();
int b = (rgb & 0xF) * 0x11;
int r = ((rgb >> 4) & 0xF) * 0x11;
int g = ((rgb >> 8) & 0xF) * 0x11;
colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b);
}
return new BitmapPalette (colors);
}
}
}

View File

@ -74,13 +74,15 @@ namespace GameRes.Formats.WestGate
uint next_offset = file.View.ReadUInt32 (index_offset+0xC);
if (next_offset < data_offset)
return null;
string last_name = null;
var invalid_chars = Path.GetInvalidFileNameChars();
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 0xC);
if (string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1)
if (last_name == name || string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1)
return null;
last_name = name;
index_offset += 0x10;
var entry = new Entry { Name = name, Type = entry_type };
entry.Offset = next_offset;