diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index a08b6b69..a5a121a3 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -88,6 +88,7 @@ + @@ -135,6 +136,7 @@ + diff --git a/ArcFormats/ArcGCEX.cs b/ArcFormats/ArcGCEX.cs new file mode 100644 index 00000000..06dc293c --- /dev/null +++ b/ArcFormats/ArcGCEX.cs @@ -0,0 +1,286 @@ +//! \file ArcGCEX.cs +//! \date Tue Aug 18 04:25:02 2015 +//! \brief G2 engine resources 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.Diagnostics; +using System.IO; +using GameRes.Utility; + +namespace GameRes.Formats.G2 +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get { return "PAK/G2"; } } + public override string Description { get { return "G2 engine resource archive"; } } + public override uint Signature { get { return 0x58454347; } } // 'GCEX' + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (0 != file.View.ReadInt32 (4)) + return null; + long index_offset = file.View.ReadInt64 (8); + if (index_offset >= file.MaxOffset) + return null; + if (!file.View.AsciiEqual (index_offset, "GCE3")) + return null; + int count = file.View.ReadInt32 (index_offset+0x18); + if (!IsSaneCount (count)) + return null; + bool index_packed = 0x11 == file.View.ReadInt32 (index_offset+4); + uint index_size = file.View.ReadUInt32 (index_offset+8); + byte[] index = null; + if (index_packed) + { + index_size -= 0x28; + int unpacked_size = file.View.ReadInt32 (index_offset+0x20); + using (var input = file.CreateStream (index_offset+0x28, index_size)) + using (var reader = new GceReader (input, unpacked_size)) + index = reader.Data; + } + else + { + index_size -= 0x20; + index = new byte[index_size]; + if (index.Length != file.View.Read (index_offset+0x20, index, 0, index_size)) + return null; + } + int current_index = 0; + int current_filename = 0x20*count; + long current_offset = 0x10; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + int name_length = LittleEndian.ToUInt16 (index, current_filename); + if (current_filename+2+name_length > index.Length) + return null; + uint size = LittleEndian.ToUInt32 (index, current_index+0x18); + if (size != 0) + { + string name = Encodings.cp932.GetString (index, current_filename+2, name_length); + var entry = new PackedEntry + { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + Offset = current_offset, + Size = size, + UnpackedSize = LittleEndian.ToUInt32 (index, current_index+0x10), + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.IsPacked = entry.Size != entry.UnpackedSize; + current_offset += entry.Size; + dir.Add (entry); + } + current_index += 0x20; + current_filename += 2 + name_length; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (0 == entry.Size) + return Stream.Null; + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var pentry = entry as PackedEntry; + if (null == pentry || !pentry.IsPacked) + return input; + if (!arc.File.View.AsciiEqual (entry.Offset, "GCE")) + { + Trace.WriteLine ("Packed entry is not GCE", entry.Name); + return input; + } + using (input) + using (var reader = new GceReader (input, (int)pentry.UnpackedSize)) + { + return new MemoryStream (reader.Data); + } + } + } + + internal class GceReader : IDisposable + { + BinaryReader m_input; + int m_unpacked_size; + byte[] m_output = null; + int m_dst; + + public byte[] Data + { + get + { + if (null == m_output) + { + m_output = new byte[m_unpacked_size]; + Unpack(); + } + return m_output; + } + } + + public GceReader (Stream input, int unpacked_size) + { + m_input = new ArcView.Reader (input); + m_unpacked_size = unpacked_size; + } + + private void Unpack () + { + m_dst = 0; + byte[] id = new byte[4]; + while (m_input.BaseStream.Position < m_input.BaseStream.Length) + { + if (4 != m_input.Read (id, 0, 4)) + break; + int segment_length = m_input.ReadInt32(); + if (Binary.AsciiEqual (id, "GCE1")) + { + m_input.ReadInt32(); + int data_length = m_input.ReadInt32(); + + m_input.ReadInt32(); + int cmd_len = m_input.ReadInt32(); + long cmd_pos = m_input.BaseStream.Position + data_length; + ReadControlStream (cmd_pos, cmd_len); + + int next = m_dst + segment_length; + UnpackGce1Segment (data_length, segment_length); + m_dst = next; + m_input.BaseStream.Position = cmd_pos + cmd_len; + } + else if (Binary.AsciiEqual (id, "GCE0")) + { + if (segment_length != m_input.Read (m_output, m_dst, segment_length)) + throw new EndOfStreamException(); + m_dst += segment_length; + } + else + { + throw new InvalidFormatException ("Unknown compression type in GCE stream"); + } + } + } + + int[] m_frame = new int[0x10000]; + + void UnpackGce1Segment (int data_len, int segment_length) + { + for (int i = 0; i < m_frame.Length; ++i) + m_frame[i] = 0; + int frame_pos = 0; + + int data_pos = 0; + int dst_end = m_dst + segment_length; + while (data_pos < data_len && m_dst < dst_end) + { + int n = GetLength(); + while (n --> 0) + { + m_frame[frame_pos] = m_dst; + byte b = m_input.ReadByte(); + data_pos++; + frame_pos = ((frame_pos << 8) | b) & 0xFFFF; + m_output[m_dst++] = b; + } + if (m_dst >= dst_end) + break; + n = GetLength() + 1; + int src = m_frame[frame_pos]; + while (n --> 0) + { + m_frame[frame_pos] = m_dst; + frame_pos = ((frame_pos << 8) | m_output[src]) & 0xFFFF; + m_output[m_dst++] = m_output[src++]; + } + } + } + + int GetLength () + { + int v = 0; + if (0 == GetBit()) + { + int digits = 0; + while (0 == GetBit()) + ++digits; + v = 1 << digits; + while (digits --> 0) + v |= GetBit() << digits; + } + return v; + } + + byte[] m_control; + int m_control_pos; + int m_control_len; + int m_bit_pos; + + void ReadControlStream (long pos, int length) + { + var data_pos = m_input.BaseStream.Position; + if (null == m_control || m_control.Length < length) + m_control = new byte[length]; + m_input.BaseStream.Position = pos; + if (length != m_input.Read (m_control, 0, length)) + throw new EndOfStreamException(); + m_control_pos = 0; + m_control_len = length; + m_input.BaseStream.Position = data_pos; + m_bit_pos = 8; + } + + int GetBit () + { + if (0 == m_bit_pos--) + { + ++m_control_pos; + m_bit_pos = 7; + --m_control_len; + if (0 == m_control_len) + throw new EndOfStreamException(); + } + return 1 & (m_control[m_control_pos] >> m_bit_pos); + } + + #region IDisposable Members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + if (m_input != null) + m_input.Dispose(); + m_disposed = true; + GC.SuppressFinalize (this); + } + } + #endregion + } +} diff --git a/ArcFormats/ImageBGRA.cs b/ArcFormats/ImageBGRA.cs new file mode 100644 index 00000000..a00094d8 --- /dev/null +++ b/ArcFormats/ImageBGRA.cs @@ -0,0 +1,75 @@ +//! \file ImageBGRA.cs +//! \date Tue Aug 18 15:56:18 2015 +//! \brief G2 enging image format. +// +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.G2 +{ + [Export(typeof(ImageFormat))] + public class BgraFormat : ImageFormat + { + public override string Tag { get { return "BGRA"; } } + public override string Description { get { return "G2 engine image format"; } } + public override uint Signature { get { return 0x41524742; } } // 'BGRA' + + public BgraFormat () + { + Extensions = new string[] { "argb", "arg" }; + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x10]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + uint bitmap = LittleEndian.ToUInt32 (header, 4); + if (0x08080808 != bitmap) + return null; + return new ImageMetaData + { + Width = LittleEndian.ToUInt32 (header, 8), + Height = LittleEndian.ToUInt32 (header, 12), + BPP = 32, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + stream.Seek (0x10, SeekOrigin.Current); + var pixels = new byte[info.Width*info.Height*4]; + if (pixels.Length != stream.Read (pixels, 0, pixels.Length)) + throw new EndOfStreamException(); + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("BgraFormat.Write not implemented"); + } + } +} diff --git a/supported.html b/supported.html index 0ba52f20..bf6d4e62 100644 --- a/supported.html +++ b/supported.html @@ -26,6 +26,7 @@ tr.odd td { background-color: #eee } Anata no Osanazuma
Ecchi na Bunny-san wa Kirai?
Kana ~Imouto~
+Kohitsuji-tachi no Rakuen
Natsu no Hitoshizuku
Private Nurse
Sensei 2
@@ -92,7 +93,10 @@ Sweet Pool
Zoku Satsuriku no Django
*.npa-YesNitro+Steins;Gate -*.pak\003\000\000\000NoNitro+Hanachirasu +*.pak\002\000\000\000
\003\000\000\000NoNitro+ +Hanachirasu
+Jingai Makyou
+ *.nsa
*.sar-YesNScripter Binary Pot
Tsukihime
@@ -289,8 +293,13 @@ Jokei Kazoku ~Inbou~
*.gccG24n
G24m
R24n
R24mNo *.arc-NoTumugiKimi no Omoi, Sono Negai *.iksNPSRNoX[iks]Shikkan ~Hazukashimerareta Karada, Oreta Kokoro~ -*.wbpARCFORM3 WBUGNoWild BugYuukyou Gangu 2 -*.wbmWPXNo +*.wbpARCFORM3 WBUGNoWild Bug +Happy Planning
+Yuukyou Gangu 2
+ +*.wbmWPX\x1ABMPNo +*.wpnWBD\x1AWAVNo +*.wwaWPX\x1AWAVNo *.mrgMRGNoF&CKonata yori Kanata made *.mcgMCG 2.00No *.acdACD 1.00No @@ -332,6 +341,10 @@ Kikouyoku Senki Gin no Toki no Corona
SuGirly Wish
*.ykgYKG000No +*.pakGCEXNoG2 +Majiresu!! ~Omatase Little Wing~
+ +*.arg
*.argbBGRANo

1 Non-encrypted only