(Legacy): added formats, mostly PC-98.

This commit is contained in:
morkt 2023-10-20 18:21:55 +04:00
parent 67811651d4
commit 1aac79f7b5
19 changed files with 2705 additions and 328 deletions

View File

@ -44,20 +44,56 @@ namespace GameRes.Formats.AyPio
int count = file.View.ReadInt16 (0x16);
if (!IsSaneCount (count))
return null;
uint index_pos = 0x18;
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 = file.View.ReadString (index_pos, 0xD);
var name = index.ReadCString (0xD);
var entry = Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_pos+0x0D);
entry.Size = file.View.ReadUInt32 (index_pos+0x11);
if (!entry.CheckPlacement (file.MaxOffset))
entry.Offset = index.ReadUInt32();
entry.Size = index.ReadUInt32();
if (!entry.CheckPlacement (max_offset))
return null;
dir.Add (entry);
index_pos += 0x15;
}
return new ArcFile (file, this, dir);
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 };
}
}

View File

@ -79,7 +79,7 @@ namespace GameRes.Formats.AyPio
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new PdtReader (file, (PdtMetaData)info);
var reader = new Pdt4Reader (file, (PdtMetaData)info);
return reader.Unpack();
}
@ -89,12 +89,12 @@ namespace GameRes.Formats.AyPio
}
}
internal class PdtReader
internal class Pdt4Reader
{
IBinaryStream m_input;
PdtMetaData m_info;
public PdtReader (IBinaryStream input, PdtMetaData info)
public Pdt4Reader (IBinaryStream input, PdtMetaData info)
{
m_input = input;
m_info = info;

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;
}
}
}

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

@ -92,11 +92,17 @@
<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="Grocer\ImagePIC.cs" />
@ -147,6 +153,7 @@
<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" />
@ -155,6 +162,8 @@
<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" />
@ -234,14 +243,18 @@
<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" />

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;
}
}
}
}
}

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);
}
}
}

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,136 @@ using System.Windows.Media;
namespace GameRes.Formats.Pias
{
enum ResourceType
{
Undefined = -1,
Graphics = 1,
Sound = 2,
}
internal class IndexReader
{
internal const bool NamesAsHexOffset = 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 = NamesAsHexOffset ? offset.ToString ("X8") : m_dir.Count.ToString("D4"),
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;
}
}
[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 +179,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 +196,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 = NamesAsHexOffset ? entry.Offset.ToString ("X8") : i.ToString("D4");
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 = (NamesAsHexOffset ? offset.ToString ("X8") : m_dir.Count.ToString("D4")) + "_",
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

@ -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.210")]
[assembly: AssemblyFileVersion ("1.0.10.210")]
[assembly: AssemblyVersion ("1.0.10.212")]
[assembly: AssemblyFileVersion ("1.0.10.212")]

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;
}
}
}

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

@ -0,0 +1,335 @@
//! \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");
}
}
internal class GraBaseReader
{
protected IBinaryStream m_input;
protected ImageMetaData m_info;
protected int m_output_stride;
protected byte[] m_pixels;
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];
}
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;
}
}
}
int m_dst;
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;
}
ushort ReadPair (int pos)
{
byte al = ReadPixel (pos);
byte ah = ReadPixel (al);
return (ushort)(al | ah << 8);
}
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;
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;
}
}
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;
void InitBitReader ()
{
m_bit_count = 1;
}
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;
}
}
}

View File

@ -24,7 +24,6 @@
//
using GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
@ -82,266 +81,22 @@ namespace GameRes.Formats.Tiare
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GraReader (file, (GraMetaData)info);
return reader.Unpack();
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");
}
}
internal class GraReader
{
IBinaryStream m_input;
GraMetaData m_info;
public GraReader (IBinaryStream file, GraMetaData info)
{
m_input = file;
m_info = info;
}
byte[] m_pixels;
ushort[] m_buffer;
public ImageData Unpack ()
{
m_input.Position = m_info.DataOffset;
BitmapPalette palette;
if (m_info.HasPalette)
palette = ImageFormat.ReadPalette (m_input.AsStream, 16, PaletteFormat.Rgb);
else
palette = DefaultPalette;
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];
int stride = width >> 1;
m_pixels = new byte[stride * m_info.iHeight];
m_dst = 0;
InitFrame(); // 1BF0:32C
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;
if (GetNextBit() != 0)
{
src = -width << 1;
if (GetNextBit() != 0)
{
src = -width + 1;
if (GetNextBit() != 0)
{
src -= 2;
}
}
}
else
{
src = -width;
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;
FlushBuffer();
dst = wTimes2;
src -= wTimes4;
remaining = wTimes4 >> 1;
}
MovePixels (m_buffer, src, dst, count);
dst += count << 1;
if (dst == buffer_size)
{
FlushBuffer();
dst = wTimes2;
}
}
else
{
MovePixels (m_buffer, src, dst, 1);
dst += 2;
if (dst == buffer_size)
{
FlushBuffer();
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)
{
FlushBuffer();
dst = wTimes2;
}
}
while (GetNextBit() != 0);
prev_src = 0;
}
}
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_pixels, stride);
}
int m_dst;
void 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);
}
}
ushort ReadPair (int pos)
{
byte al = ReadPixel (pos);
byte ah = ReadPixel (al);
return (ushort)(al | ah << 8);
}
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;
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;
}
}
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;
void InitBitReader ()
{
m_bit_count = 1;
}
byte GetNextBit ()
{
if (--m_bit_count <= 0)
{
m_bits = m_input.ReadByte();
if (-1 == m_bits)
m_bits = 0;
m_bit_count = 8;
}
int bit = (m_bits >> 7) & 1;
m_bits <<= 1;
return (byte)bit;
}
static readonly BitmapPalette DefaultPalette = new BitmapPalette (new Color[] {
#region Default palette

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");
}
}
}

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

@ -0,0 +1,104 @@
//! \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.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");
}
}
internal class UgReader : System98.GraBaseReader
{
public UgReader (IBinaryStream input, ImageMetaData info) : base (input, info)
{
}
public ImageData Unpack ()
{
m_input.Position = 8;
var palette = ReadPalette();
UnpackBits();
return ImageData.Create (m_info, PixelFormats.Indexed4, palette, Pixels, 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);
}
}
}