implemented IAR and WAR archives.

This commit is contained in:
morkt 2015-10-24 15:54:07 +04:00
parent 86d9121b94
commit f901f8252f
6 changed files with 819 additions and 0 deletions

View File

@ -70,6 +70,10 @@
<Compile Include="Debonosu\ArcPAK.cs" />
<Compile Include="elf\ImageG24.cs" />
<Compile Include="elf\ImageGP8.cs" />
<Compile Include="Sas5\ArcIAR.cs" />
<Compile Include="Sas5\ArcSec5.cs" />
<Compile Include="Sas5\ArcWAR.cs" />
<Compile Include="Sas5\ImageIAR.cs" />
<Compile Include="Silky\ImageMSK.cs" />
<Compile Include="Emic\ArcPACK.cs" />
<Compile Include="Emic\ImageMWP.cs" />

364
ArcFormats/Sas5/ArcIAR.cs Normal file
View File

@ -0,0 +1,364 @@
//! \file ArcIAR.cs
//! \date Fri Oct 23 12:31:15 2015
//! \brief Sas5 engine image archive.
//
// 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 GameRes.Utility;
using System.Text;
namespace GameRes.Formats.Sas5
{
internal class IarArchive : ArcFile
{
public readonly int Version;
public IarArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, int version)
: base (arc, impl, dir)
{
Version = version;
}
}
internal class IarImageInfo : ImageMetaData
{
public int Flags;
public bool Compressed;
public uint PaletteSize;
public int PackedSize;
public int UnpackedSize;
public int Stride;
}
[Export(typeof(ArchiveFormat))]
public class IarOpener : ArchiveFormat
{
public override string Tag { get { return "IAR"; } }
public override string Description { get { return "SAS5 engine images archive"; } }
public override uint Signature { get { return 0x20726169; } } // 'iar '
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int version = file.View.ReadInt16 (4);
if (version < 1 || version > 4)
return null;
int file_count = file.View.ReadInt32 (0x18);
int count = file.View.ReadInt32 (0x1C);
if (count < file_count || !IsSaneCount (count))
return null;
var index = Sec5Opener.LookupIndex (file.Name);
string base_name = Path.GetFileNameWithoutExtension (file.Name);
Func<int, Entry> CreateEntry;
if (null == index)
CreateEntry = n => GetDefaultEntry (base_name, n);
else
CreateEntry = (n) => {
Entry entry;
if (index.TryGetValue (n, out entry))
return new Entry { Name = entry.Name, Type = entry.Type };
return GetDefaultEntry (base_name, n);
};
uint offset_size = version < 3 ? 4u : 8u;
Func<uint, long> ReadOffset;
if (version < 3)
ReadOffset = x => file.View.ReadUInt32 (x);
else
ReadOffset = x => file.View.ReadInt64 (x);
uint index_offset = 0x20;
var dir = new List<Entry> (count);
var next_offset = ReadOffset (index_offset);
for (int i = 0; i < count; ++i)
{
var entry = CreateEntry (i);
entry.Offset = next_offset;
index_offset += offset_size;
next_offset = (i + 1) == count ? file.MaxOffset : ReadOffset (index_offset);
entry.Size = (uint)(next_offset - entry.Offset);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
return new IarArchive (file, this, dir, version);
}
static Entry GetDefaultEntry (string base_name, int n)
{
return new Entry { Name = string.Format ("{0}#{1:D5}", base_name, n) };
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var iarc = arc as IarArchive;
if (null == iarc)
return base.OpenEntry (arc, entry);
int flags = arc.File.View.ReadUInt16 (entry.Offset);
if (0 != (flags & 0x1000))
return base.OpenEntry (arc, entry);
using (var image = new IarImage (iarc, entry))
{
byte[] pixels = image.Data;
if (0 != (flags & 0x800))
pixels = CombineImage (pixels, image.Info, iarc);
// internal 'IAR SAS5' format
var header = new byte[0x28+image.Info.PaletteSize];
using (var mem = new MemoryStream (header))
using (var writer = new BinaryWriter (mem))
{
writer.Write (0x00524149); // 'IAR'
writer.Write (0x35534153); // 'SAS5'
writer.Write (image.Info.Width);
writer.Write (image.Info.Height);
writer.Write (image.Info.OffsetX);
writer.Write (image.Info.OffsetY);
writer.Write (image.Info.BPP);
writer.Write (image.Info.Stride);
writer.Write (image.Info.PaletteSize);
writer.Write (pixels.Length);
if (null != image.Palette)
writer.Write (image.Palette, 0, image.Palette.Length);
return new PrefixStream (header, new MemoryStream (pixels));
}
}
}
byte[] CombineImage (byte[] overlay, IarImageInfo info, IarArchive iarc)
{
using (var mem = new MemoryStream (overlay))
using (var input = new BinaryReader (mem))
{
var dir = (List<Entry>)iarc.Dir;
int base_index = input.ReadInt32();
if (base_index >= dir.Count)
throw new InvalidFormatException ("Invalid base image index");
int diff_y = input.ReadInt32();
int diff_count = input.ReadInt32();
using (var base_image = new IarImage (iarc, dir[base_index]))
{
int base_y = (int)base_image.Info.Height - (int)info.Height;
byte[] output = base_image.Data;
if (base_y != 0 || info.Stride != base_image.Info.Stride)
{
byte[] src = base_image.Data;
int base_stride = Math.Min (info.Stride, base_image.Info.Stride);
output = new byte[info.Height * info.Stride];
for (int y = base_y; y < base_image.Info.Height; ++y)
{
Buffer.BlockCopy (src, y * base_image.Info.Stride,
output, (y - base_y) * info.Stride, base_stride);
}
}
int pixel_size = info.BPP / 8;
int dst = diff_y * info.Stride;
for (int i = 0; i < diff_count; ++i)
{
int chunk_count = input.ReadUInt16();
int x = 0;
for (int j = 0; j < chunk_count; ++j)
{
int skip_count = pixel_size * input.ReadUInt16();
int copy_count = pixel_size * input.ReadUInt16();
x += skip_count;
input.Read (output, dst+x, copy_count);
x += copy_count;
}
dst += info.Stride;
}
return output;
}
}
}
}
internal sealed class IarImage : IDisposable
{
BinaryReader m_input;
IarImageInfo m_info;
byte[] m_palette;
byte[] m_output;
public IarImageInfo Info { get { return m_info; } }
public byte[] Palette { get { return m_palette; } }
public byte[] Data { get { return m_output; } }
public IarImage (IarArchive iarc, Entry entry)
{
int flags = iarc.File.View.ReadUInt16 (entry.Offset);
int bpp;
switch (flags & 0x3E)
{
case 0x02: bpp = 8; break;
case 0x1C: bpp = 24; break;
case 0x3C: bpp = 32; break;
default: throw new NotSupportedException ("Not supported IAR image format");
}
var offset = entry.Offset;
m_info = new IarImageInfo
{
Flags = flags,
BPP = bpp,
Compressed = iarc.File.View.ReadByte (offset+3) != 0,
Width = iarc.File.View.ReadUInt32 (offset+0x20),
Height = iarc.File.View.ReadUInt32 (offset+0x24),
Stride = iarc.File.View.ReadInt32 (offset+0x28),
OffsetX = iarc.File.View.ReadInt32 (offset+0x18),
OffsetY = iarc.File.View.ReadInt32 (offset+0x1C),
UnpackedSize = iarc.File.View.ReadInt32 (offset+8),
PaletteSize = iarc.File.View.ReadUInt32 (offset+0xC),
PackedSize = iarc.File.View.ReadInt32 (offset+0x10),
};
uint header_size = 1 == iarc.Version ? 0x30u : iarc.Version < 4 ? 0x40u : 0x48u;
offset += header_size;
uint input_size = entry.Size - header_size;
if (m_info.PaletteSize > 0)
{
m_palette = new byte[m_info.PaletteSize];
iarc.File.View.Read (offset, m_palette, 0, m_info.PaletteSize);
offset += m_info.PaletteSize;
input_size -= m_info.PaletteSize;
}
var input = iarc.File.CreateStream (offset, input_size);
m_input = new BinaryReader (input);
m_output = new byte[m_info.UnpackedSize];
if (!m_info.Compressed)
m_input.Read (m_output, 0, m_output.Length);
else
Unpack();
}
void Unpack ()
{
m_bits = 1;
int dst = 0;
while (dst < m_output.Length)
{
if (1 == GetNextBit())
{
m_output[dst++] = m_input.ReadByte();
continue;
}
int offset, count;
if (1 == GetNextBit())
{
int tmp = GetNextBit();
if (1 == GetNextBit())
offset = 1;
else if (1 == GetNextBit())
offset = 0x201;
else
{
tmp = (tmp << 1) | GetNextBit();
if (1 == GetNextBit())
offset = 0x401;
else
{
tmp = (tmp << 1) | GetNextBit();
if (1 == GetNextBit())
offset = 0x801;
else
{
offset = 0x1001;
tmp = (tmp << 1) | GetNextBit();
}
}
}
offset += (tmp << 8) | m_input.ReadByte();
if (1 == GetNextBit())
count = 3;
else if (1 == GetNextBit())
count = 4;
else if (1 == GetNextBit())
count = 5;
else if (1 == GetNextBit())
count = 6;
else if (1 == GetNextBit())
count = 7 + GetNextBit();
else if (1 == GetNextBit())
count = 17 + m_input.ReadByte();
else
{
count = GetNextBit() << 2;
count |= GetNextBit() << 1;
count |= GetNextBit();
count += 9;
}
}
else
{
count = 2;
if (1 == GetNextBit())
{
offset = GetNextBit() << 10;
offset |= GetNextBit() << 9;
offset |= GetNextBit() << 8;
offset = (offset | m_input.ReadByte()) + 0x100;
}
else
{
offset = 1 + m_input.ReadByte();
if (0x100 == offset)
break;
}
}
Binary.CopyOverlapped (m_output, dst - offset, dst, count);
dst += count;
}
}
int m_bits = 1;
int GetNextBit ()
{
if (1 == m_bits)
{
m_bits = m_input.ReadUInt16() | 0x10000;
}
int b = m_bits & 1;
m_bits >>= 1;
return b;
}
#region IDisposable Members
bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
m_input.Dispose();
_disposed = true;
}
}
#endregion
}
}

173
ArcFormats/Sas5/ArcSec5.cs Normal file
View File

@ -0,0 +1,173 @@
//! \file ArcSec5.cs
//! \date Fri Oct 23 18:10:06 2015
//! \brief Sas5 engine resource index file.
//
// 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 GameRes.Utility;
using System.Text;
namespace GameRes.Formats.Sas5
{
[Export(typeof(ArchiveFormat))]
public class Sec5Opener : ArchiveFormat
{
public override string Tag { get { return "SEC5"; } }
public override string Description { get { return "SAS5 engine resource index file"; } }
public override uint Signature { get { return 0x35434553; } } // 'SEC5'
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
uint offset = 8;
var dir = new List<Entry>();
while (offset < file.MaxOffset)
{
string name = file.View.ReadString (offset, 4, Encoding.ASCII);
if ("ENDS" == name)
break;
uint section_size = file.View.ReadUInt32 (offset+4);
offset += 8;
var entry = new Entry {
Name = name,
Offset = offset,
Size = section_size,
};
dir.Add (entry);
offset += section_size;
}
if (dir.Count > 0)
return new ArcFile (file, this, dir);
return null;
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if ("CODE" != entry.Name)
return arc.File.CreateStream (entry.Offset, entry.Size);
var code = new byte[entry.Size];
arc.File.View.Read (entry.Offset, code, 0, entry.Size);
DecryptCodeSection (code);
return new MemoryStream (code);
}
static void DecryptCodeSection (byte[] code)
{
byte key = 0;
for (int i = 0; i < code.Length; ++i)
{
int x = code[i] + 18;
code[i] ^= key;
key += (byte)x;
}
}
static internal Dictionary<string, Dictionary<int, Entry>> CurrentIndex;
static internal Dictionary<int, Entry> LookupIndex (string filename)
{
if (null == CurrentIndex)
CurrentIndex = FindSec5Resr (filename);
if (null == CurrentIndex)
return null;
Dictionary<int, Entry> arc_map = null;
CurrentIndex.TryGetValue (Path.GetFileName (filename), out arc_map);
return arc_map;
}
static internal Dictionary<string, Dictionary<int, Entry>> FindSec5Resr (string arc_name)
{
string dir_name = Path.GetDirectoryName (arc_name);
var match = Directory.GetFiles (dir_name, "*.sec5");
if (0 == match.Length)
{
string parent = Path.GetDirectoryName (dir_name);
if (!string.IsNullOrEmpty (parent))
match = Directory.GetFiles (parent, "*.sec5");
}
if (0 == match.Length)
return null;
using (var sec5 = new ArcView (match[0]))
{
if (!sec5.View.AsciiEqual (0, "SEC5"))
return null;
uint offset = 8;
while (offset < sec5.MaxOffset)
{
string id = sec5.View.ReadString (offset, 4, Encoding.ASCII);
if ("ENDS" == id)
break;
uint section_size = sec5.View.ReadUInt32 (offset+4);
offset += 8;
if ("RESR" == id)
{
using (var resr = sec5.CreateStream (offset, section_size))
return ReadResrSection (resr);
}
offset += section_size;
}
}
return null;
}
static internal Dictionary<string, Dictionary<int, Entry>> ReadResrSection (Stream input)
{
using (var resr = new BinaryReader (input, Encodings.cp932, true))
{
int count = resr.ReadInt32();
if (0 == count)
return null;
var map = new Dictionary<string, Dictionary<int, Entry>> (StringComparer.InvariantCultureIgnoreCase);
for (int i = 0; i < count; ++i)
{
string name = resr.BaseStream.ReadCString();
string type = resr.BaseStream.ReadCString();
string arc_type = resr.BaseStream.ReadCString();
int res_length = resr.ReadInt32();
var next_pos = resr.BaseStream.Position + res_length;
if (arc_type == "file-war" || arc_type == "file-iar")
{
string arc_name = resr.BaseStream.ReadCString();
int id = resr.ReadInt32();
var base_arc_name = Path.GetFileName (arc_name);
if (!map.ContainsKey (base_arc_name))
map[base_arc_name] = new Dictionary<int, Entry>();
var entry = new Entry
{
Name = name,
Type = type,
};
map[base_arc_name][id] = entry;
}
resr.BaseStream.Position = next_pos;
}
return map.Count > 0 ? map : null;
}
}
}
}

135
ArcFormats/Sas5/ArcWAR.cs Normal file
View File

@ -0,0 +1,135 @@
//! \file ArcWAR.cs
//! \date Fri Oct 23 19:11:56 2015
//! \brief Sas5 engine audio archive.
//
// 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 GameRes.Utility;
namespace GameRes.Formats.Sas5
{
internal class WarEntry : Entry
{
public int Format;
}
[Export(typeof(ArchiveFormat))]
public class WarOpener : ArchiveFormat
{
public override string Tag { get { return "WAR/SAS5"; } }
public override string Description { get { return "SAS5 engine audio archive"; } }
public override uint Signature { get { return 0x20726177; } } // 'war '
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public WarOpener ()
{
Extensions = new string[] { "war" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
return null;
uint entry_size = file.View.ReadUInt32 (12);
if (entry_size < 0x18)
return null;
var index = Sec5Opener.LookupIndex (file.Name);
string base_name = Path.GetFileNameWithoutExtension (file.Name);
Func<int, string> GetEntryName;
if (null == index)
GetEntryName = n => GetDefaultName (base_name, n);
else
GetEntryName = (n) => {
Entry entry;
if (index.TryGetValue (n, out entry))
return entry.Name;
return GetDefaultName (base_name, n);
};
uint index_offset = 0x10;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var entry = new WarEntry {
Name = GetEntryName (i),
Offset = file.View.ReadUInt32 (index_offset),
Size = file.View.ReadUInt32 (index_offset+4),
Format = file.View.ReadByte (index_offset+0x14),
};
if (!entry.CheckPlacement (file.MaxOffset))
return null;
if (0 == entry.Format)
{
entry.Name = Path.ChangeExtension (entry.Name, "wav");
entry.Type = "audio";
}
else if (2 == entry.Format)
{
entry.Name = Path.ChangeExtension (entry.Name, "ogg");
entry.Type = "audio";
}
dir.Add (entry);
index_offset += entry_size;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var went = entry as WarEntry;
if (null == went || 0 != went.Format)
return arc.File.CreateStream (entry.Offset, entry.Size);
uint fmt_size = arc.File.View.ReadUInt32 (entry.Offset);
uint data_size = arc.File.View.ReadUInt32 (entry.Offset+4);
var wav_header = new byte[8+12+fmt_size+8];
uint total_size = (uint)wav_header.Length + data_size - 8;
arc.File.View.Read (entry.Offset+8, wav_header, 0x14, fmt_size);
using (var mem = new MemoryStream (wav_header))
using (var buffer = new BinaryWriter (mem))
{
buffer.Write (AudioFormat.Wav.Signature);
buffer.Write (total_size);
buffer.Write (0x45564157); // 'WAVE'
buffer.Write (0x20746d66); // 'fmt '
buffer.Write (fmt_size);
buffer.BaseStream.Seek (fmt_size, SeekOrigin.Current);
buffer.Write (0x61746164); // 'data'
buffer.Write (data_size);
}
var pcm_data = arc.File.CreateStream (entry.Offset+8+fmt_size, entry.Size-8-fmt_size);
return new PrefixStream (wav_header, pcm_data);
}
static string GetDefaultName (string base_name, int n)
{
return string.Format ("{0}#{1:D5}", base_name, n);
}
}
}

134
ArcFormats/Sas5/ImageIAR.cs Normal file
View File

@ -0,0 +1,134 @@
//! \file ImageIAR.cs
//! \date Fri Oct 23 15:17:52 2015
//! \brief Intermediate image format representing IAR archives entries.
//
// 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 GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Sas5
{
internal class IarMetaData : ImageMetaData
{
public int Stride;
public int PaletteSize;
public int ImageSize;
}
// This is an artificial format, used by GARbro internally. structure:
// 0x0000 'IAR\x00'
// 0x0004 'SAS5'
// 0x0008 Width
// 0x000C Height
// 0x0010 OffsetX
// 0x0014 OffsetY
// 0x0018 BPP
// 0x001C Stride
// 0x0020 PaletteSize
// 0x0024 ImageSize
// 0x0028 Palette [if PaletteSize > 0]
// ...... Image data
[Export(typeof(ImageFormat))]
public class IarFormat : ImageFormat
{
public override string Tag { get { return "IAR/IMAGE"; } }
public override string Description { get { return "SAS5 engine compressed image format"; } }
public override uint Signature { get { return 0x00524149; } } // 'IAR'
public IarFormat ()
{
Extensions = new string[] { "" };
}
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[0x28];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
if (!Binary.AsciiEqual (header, 4, "SAS5"))
return null;
return new IarMetaData
{
Width = LittleEndian.ToUInt32 (header, 0x08),
Height = LittleEndian.ToUInt32 (header, 0x0C),
OffsetX = LittleEndian.ToInt32 (header, 0x10),
OffsetY = LittleEndian.ToInt32 (header, 0x14),
BPP = LittleEndian.ToInt32 (header, 0x18),
Stride = LittleEndian.ToInt32 (header, 0x1C),
PaletteSize = LittleEndian.ToInt32 (header, 0x20),
ImageSize = LittleEndian.ToInt32 (header, 0x24),
};
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
var meta = info as IarMetaData;
if (null == meta)
throw new ArgumentException ("IarFormat.Read should be supplied with IarMetaData", "info");
PixelFormat format;
if (32 == meta.BPP)
format = PixelFormats.Bgra32;
else if (24 == meta.BPP)
format = PixelFormats.Bgr24;
else if (0 == meta.PaletteSize)
format = PixelFormats.Gray8;
else
format = PixelFormats.Indexed8;
stream.Position = 0x28;
BitmapPalette palette = null;
if (meta.PaletteSize > 0)
palette = ReadPalette (stream, meta.PaletteSize);
var pixels = new byte[meta.ImageSize];
stream.Read (pixels, 0, pixels.Length);
return ImageData.Create (info, format, palette, pixels, meta.Stride);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("IarFormat.Write not implemented");
}
static BitmapPalette ReadPalette (Stream input, int palette_size)
{
var palette_data = new byte[palette_size];
if (palette_data.Length != input.Read (palette_data, 0, palette_data.Length))
throw new EndOfStreamException();
palette_size = Math.Min (0x400, palette_size);
int color_size = palette_size / 0x100;
var palette = new Color[0x100];
for (int i = 0; i < palette.Length; ++i)
{
int c = i * color_size;
palette[i] = Color.FromRgb (palette_data[c+2], palette_data[c+1], palette_data[c]);
}
return new BitmapPalette (palette);
}
}
}

View File

@ -426,6 +426,15 @@ Blood Royal<br/>
Heroine<br/>
Promise<br/>
</td></tr>
<tr class="odd"><td>*.arc</td><td>-</td><td>No</td><td rowspan="3">Silky's</td><td rowspan="3">
Shitai o Arau<br/>
</td></tr>
<tr class="odd"><td>*.g24</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>*.msk</td><td><tt>Rmsk</tt></td><td>No</td></tr>
<tr><td>*.iar<br/>*.war</td><td><tt>iar</tt><br/><tt>war</tt></td><td>No</td><td>Studio Ryokucha</td><td>
Katakoi no Tsuki<br/>
Katakoi no Tsuki Extra<br/>
</td></tr>
</table>
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
</body>