diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 39213698..b30669be 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -102,9 +102,11 @@
+
+
diff --git a/ArcFormats/GameSystem/ArcCHR.cs b/ArcFormats/GameSystem/ArcCHR.cs
new file mode 100644
index 00000000..ee7ed26e
--- /dev/null
+++ b/ArcFormats/GameSystem/ArcCHR.cs
@@ -0,0 +1,140 @@
+//! \file ArcCHR.cs
+//! \date Mon Jan 16 20:15:54 2017
+//! \brief 'Game System' character image format.
+//
+// Copyright (C) 2017 by morkt
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+
+namespace GameRes.Formats.GameSystem
+{
+ [Export(typeof(ArchiveFormat))]
+ public class ChrOpener : ArchiveFormat
+ {
+ public override string Tag { get { return "CHR/GAMESYSTEM"; } }
+ public override string Description { get { return "'Game System' character frames"; } }
+ public override uint Signature { get { return 0; } }
+ public override bool IsHierarchic { get { return false; } }
+ public override bool CanWrite { get { return false; } }
+
+ static readonly Lazy s_ChrFormat = new Lazy (() => ImageFormat.FindByTag ("CHR"));
+
+ public override ArcFile TryOpen (ArcView file)
+ {
+ if (!file.Name.EndsWith (".CHR", StringComparison.InvariantCultureIgnoreCase)
+ || file.View.ReadUInt32 (0) != file.MaxOffset)
+ return null;
+ using (var input = file.CreateStream())
+ {
+ var info = s_ChrFormat.Value.ReadMetaData (input) as ChrMetaData;
+ if (null == info)
+ return null;
+ input.Position = info.RgbSize;
+ uint overlay_size = input.ReadUInt32();
+ if (0 == overlay_size)
+ return null;
+ input.ReadInt32();
+ int count = input.ReadInt32();
+ if (!IsSaneCount (count))
+ return null;
+ int x = input.ReadInt16();
+ int y = input.ReadInt16();
+ int w = input.ReadInt16();
+ int h = input.ReadInt16() * count;
+ var frame_info = new ImageMetaData
+ {
+ Width = (uint)w, Height = (uint)h, OffsetX = x, OffsetY = y, BPP = 32
+ };
+
+ var base_name = Path.GetFileNameWithoutExtension (file.Name);
+ var dir = new List (2);
+ var entry = new ChrEntry
+ {
+ Name = string.Format ("{0}#00", base_name),
+ Offset = 0,
+ Size = (uint)info.RgbSize,
+ Info = info,
+ };
+ dir.Add (entry);
+ entry = new ChrEntry
+ {
+ Name = string.Format ("{0}#01", base_name),
+ Offset = info.RgbSize+4,
+ Size = overlay_size,
+ Info = frame_info,
+ };
+ dir.Add (entry);
+ return new ArcFile (file, this, dir);
+ }
+ }
+
+ public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
+ {
+ var cent = (ChrEntry)entry;
+ var input = arc.File.CreateStream (entry.Offset, entry.Size);
+ if (cent.Info is ChrMetaData)
+ return new ChrDecoder (input, cent.Info);
+ else
+ return new ChrFrameDecoder (input, cent.Info);
+ }
+ }
+
+ internal class ChrEntry : Entry
+ {
+ public override string Type { get { return "image"; } }
+
+ public ImageMetaData Info;
+ }
+
+ internal class ChrDecoder : BinaryImageDecoder
+ {
+ public ChrDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
+ { }
+
+ protected override ImageData GetImageData ()
+ {
+ var reader = new ChrReader (m_input, (ChrMetaData)Info);
+ var pixels = reader.UnpackBaseline();
+ return ImageData.CreateFlipped (Info, PixelFormats.Bgra32, null, pixels, reader.Stride);
+ }
+ }
+
+ internal class ChrFrameDecoder : BinaryImageDecoder
+ {
+ public ChrFrameDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
+ { }
+
+ protected override ImageData GetImageData ()
+ {
+ m_input.Position = 0x10;
+ int stride = (int)Info.Width * 4;
+ var pixels = new byte[stride * (int)Info.Height];
+ m_input.Read (pixels, 0, pixels.Length);
+ return ImageData.CreateFlipped (Info, PixelFormats.Bgr32, null, pixels, stride);
+
+ }
+ }
+}
diff --git a/ArcFormats/GameSystem/ImageCHR.cs b/ArcFormats/GameSystem/ImageCHR.cs
new file mode 100644
index 00000000..ffe5b621
--- /dev/null
+++ b/ArcFormats/GameSystem/ImageCHR.cs
@@ -0,0 +1,193 @@
+//! \file ImageCHR.cs
+//! \date Mon Jan 16 07:53:42 2017
+//! \brief 'Game System' character image format.
+//
+// Copyright (C) 2017 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 GameRes.Utility;
+
+namespace GameRes.Formats.GameSystem
+{
+ internal class ChrMetaData : ImageMetaData
+ {
+ public int RgbSize;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class ChrFormat : ImageFormat
+ {
+ public override string Tag { get { return "CHR"; } }
+ public override string Description { get { return "'Game System' character image format"; } }
+ public override uint Signature { get { return 0; } }
+
+ public override ImageMetaData ReadMetaData (IBinaryStream file)
+ {
+ if (file.Signature != file.Length)
+ return null;
+ var header = file.ReadHeader (0x18);
+ int rgb_size = header.ToInt32 (4);
+ if (rgb_size <= 0x20 || rgb_size > file.Length)
+ return null;
+ uint width = header.ToUInt32 (8);
+ uint height = header.ToUInt32 (0xC);
+ int x = header.ToInt32 (0x10);
+ int y = header.ToInt32 (0x14);
+ if (0 == width || width > 0x8000 || 0 == height || height > 0x8000
+ || x < 0 || x + width > 0x8000 || y < 0 || y + height > 0x8000)
+ return null;
+ return new ChrMetaData
+ {
+ Width = width,
+ Height = height,
+ OffsetX = x,
+ OffsetY = y,
+ BPP = 32,
+ RgbSize = rgb_size,
+ };
+ }
+
+ public override ImageData Read (IBinaryStream file, ImageMetaData info)
+ {
+ var reader = new ChrReader (file, (ChrMetaData)info);
+ var pixels = reader.Unpack();
+ return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, pixels, reader.Stride);
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("ChrFormat.Write not implemented");
+ }
+ }
+
+ internal sealed class ChrReader
+ {
+ IBinaryStream m_input;
+ ChrMetaData m_info;
+ byte[] m_output;
+ int m_stride;
+
+ public byte[] Data { get { return m_output; } }
+ public int Stride { get { return m_stride; } }
+
+ public ChrReader (IBinaryStream input, ChrMetaData info)
+ {
+ m_input = input;
+ m_info = info;
+ m_stride = (int)m_info.Width * 4;
+ m_output = new byte[m_stride * (int)m_info.Height];
+ }
+
+ public byte[] Unpack ()
+ {
+ UnpackBaseline();
+ if (m_info.RgbSize < m_input.Length)
+ {
+ m_input.Position = m_info.RgbSize;
+ int overlay_length = m_input.ReadInt32();
+ if (overlay_length > 0)
+ ReadOverlay();
+ }
+ return m_output;
+ }
+
+ public byte[] UnpackBaseline ()
+ {
+ m_input.Position = 0x20;
+ UnpackRgb ((int)m_info.Height);
+ return m_output;
+ }
+
+ void UnpackRgb (int row_count)
+ {
+ int row = 0;
+ while (row_count --> 0)
+ {
+ int dst = row;
+ int x = 0;
+ for (;;)
+ {
+ int ctl = m_input.ReadUInt8();
+ if (ctl < 0x7F)
+ {
+ int alpha = -(2 * ctl - 0xFE);
+ m_input.Read (m_output, dst, 3);
+ m_output[dst+3] = (byte)~alpha;
+ dst += 4;
+ ++x;
+ }
+ else if (ctl < 0x9F)
+ {
+ int count = ctl - 0x7E;
+ x += count;
+ m_input.Read (m_output, dst, 3);
+ m_output[dst+3] = 0xFF;
+ count *= 4;
+ Binary.CopyOverlapped (m_output, dst, dst+4, count-4);
+ dst += count;
+ }
+ else if (0xFF == ctl)
+ break;
+ else
+ {
+ int count = ctl - 0x9E;
+ dst += count * 4;
+ x += count;
+ }
+ }
+ row += m_stride;
+ }
+ }
+
+ void ReadOverlay ()
+ {
+ m_input.ReadInt32();
+ int frame_count = m_input.ReadInt32();
+ if (frame_count <= 0)
+ return;
+ int x = m_input.ReadInt16() - m_info.OffsetX;
+ int y = m_input.ReadInt16();
+ int w = m_input.ReadInt16();
+ int h = m_input.ReadInt16();
+ y = (int)m_info.Height + m_info.OffsetY - y - h;
+ if (x < 0 || y < 0)
+ return;
+ int output = y * m_stride + x * 4;
+ for (int i = 0; i < h; ++i)
+ {
+ int dst = output;
+ for (int j = 0; j < w; ++j)
+ {
+ m_input.Read (m_output, dst, 3);
+ int a = m_input.ReadByte();
+ if (a != 0x80)
+ throw new InvalidFormatException ("Error reading overlay frame");
+ m_output[dst+3] = 0xFF;
+ dst += 4;
+ }
+ output += m_stride;
+ }
+ }
+ }
+}
diff --git a/supported.html b/supported.html
index ab64dcfc..526881fb 100644
--- a/supported.html
+++ b/supported.html
@@ -704,6 +704,8 @@ Yuukyou Gangu 2
Asa no Konai Yoru ni Dakarete -Eternal Night-
Guren ni Somaru Gin no Rozario
Konata yori Kanata made
+Natural ~Mi mo Kokoro mo~
+Natural2 -DUO-
Niji no Kanata ni
Thirua Panic
@@ -741,6 +743,7 @@ Vampire Crusaders
*.hip *.hiz
hip hiz
No
*.gpk+*.gtb *.vpk+*.vtb
-
No
Black Cyc
Before Dawn Daybreak ~Shinen no Utahime~
+Extravaganza ~Mushi Mederu Shoujo~
Gun-Katana
Hana Goyomi
Jishou Seirei Majutsushi vs Shinsei Daiikkyuu Akuma
@@ -1330,7 +1333,7 @@ Onepapa ~Onegai PaPa!~