Compare commits

..

37 Commits

Author SHA1 Message Date
morkt
3db6d9a732 released v1.4.26 2017-02-26 20:49:41 +04:00
morkt
3c5b8230e4 (NOA): look for archive key in accompanying exe files. 2017-02-26 02:42:05 +04:00
morkt
da438b6575 unused usings. 2017-02-25 15:54:43 +04:00
morkt
b03d2fdecc implemented MG2 and MAL images. 2017-02-25 05:42:15 +04:00
morkt
267353e1fc implemented ADP4 compressed audio. 2017-02-25 02:14:03 +04:00
morkt
f49598532d (WaveFormat.SetBPS): calculate AverageBytesPerSecond field. 2017-02-25 02:03:11 +04:00
morkt
89674e9d58 (FileSystemStack): use ExceptionDispatchInfo to re-throw exception. 2017-02-25 02:02:41 +04:00
morkt
99b8faab23 (AniOpener): check filename extension. 2017-02-25 02:01:34 +04:00
morkt
25f9285181 implemented DAF archives and CGF images. 2017-02-23 18:39:13 +04:00
morkt
10ff1cfcf9 (ImpDecoder.GetImageData): don't dispose input. 2017-02-23 17:52:27 +04:00
morkt
16c0daf643 (QLIE): attempt to extract archive key from game EXE resources. 2017-02-22 19:32:02 +04:00
morkt
1b6d27061e formats update. 2017-02-21 08:32:12 +04:00
morkt
cd2e43d61c (PsbOpener.OpenArcFile): new method. 2017-02-21 08:27:25 +04:00
morkt
872842d408 (PSB): ignore parse errors caused by invalid key. 2017-02-20 12:19:47 +04:00
morkt
7c82b909e0 implemented old 'Black Rainbow' archives. 2017-02-20 09:42:35 +04:00
morkt
dd85a7a618 implemented KOEPAC archives. 2017-02-19 19:59:15 +04:00
morkt
afe9159853 implemented FA2 archives and C24 images. 2017-02-19 16:01:11 +04:00
mireado
e1e252884b (GUI): update korean translation (#50) 2017-02-17 13:23:35 +04:00
morkt
01b6d41f82 updated formats database. 2017-02-15 23:01:08 +04:00
morkt
089d5f2fd6 added hyperlink. 2017-02-15 22:53:09 +04:00
morkt
49016837c6 moved supported list to docs. 2017-02-15 22:50:37 +04:00
morkt
02a591c83a fixed update page url. 2017-02-15 22:46:34 +04:00
morkt
ddf928f02a Merge remote-tracking branch 'refs/remotes/origin/check-updates' 2017-02-15 22:40:51 +04:00
morkt
08e5a6c69a misc. 2017-02-15 22:36:45 +04:00
morkt
526485c720 (GetSerializedSchemeVersion): moved header reading to separate method. 2017-02-15 02:26:31 +04:00
morkt
653ee4685d implemented async formats update. 2017-02-15 02:24:25 +04:00
morkt
d8eb3aed1a check updates - moved xml parsing around. 2017-02-15 01:40:24 +04:00
morkt
1d5d74ce93 download formats db. 2017-02-14 13:11:57 +04:00
morkt
ccd4424b1b check updates - translation strings. 2017-02-14 09:16:12 +04:00
morkt
987d57a4db check for updates - initial implementation. 2017-02-14 07:22:48 +04:00
morkt
2df8145f83 (GameSystem): support PureMail archives. 2017-02-13 20:13:20 +04:00
morkt
f6f989dd2f (PSB): support "icon" entries and RL compression. 2017-02-13 20:03:46 +04:00
morkt
dc1647c5bf (AdvSys3): use Entry.ChangeType. 2017-02-13 19:49:06 +04:00
morkt
4929d1efd1 (MAI4): format may have different compression encoding without any indication in the header.
haven't figured out yet how to work around it.
2017-02-10 18:04:05 +04:00
morkt
8b9ddad110 implemented Software House Parsley CG archives. 2017-02-10 18:00:42 +04:00
morkt
e6ca959ded support WARC 1.10 archives. 2017-02-10 03:27:39 +04:00
morkt
c5ac986cde implemented 'Voice PackData' archives. 2017-02-10 03:24:25 +04:00
56 changed files with 3719 additions and 182 deletions

View File

@@ -80,11 +80,7 @@ namespace GameRes.Formats.AdvSys
else
{
var res = AutoEntry.DetectFileType (signature);
if (res != null)
{
entry.Type = res.Type;
entry.Name = Path.ChangeExtension (entry.Name, res.Extensions.FirstOrDefault());
}
entry.ChangeType (res);
}
dir.Add (entry);
current_offset += size;

View File

@@ -57,6 +57,8 @@ namespace GameRes.Formats.Ags
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.EndsWith (".ani", StringComparison.InvariantCultureIgnoreCase))
return null;
uint first_offset = file.View.ReadUInt32 (0);
if (first_offset < 4 || file.MaxOffset > int.MaxValue || first_offset >= file.MaxOffset || 0 != (first_offset & 3))
return null;

View File

@@ -90,7 +90,19 @@
<Compile Include="Ankh\AudioPCM.cs" />
<Compile Include="Aoi\ImageAGF.cs" />
<Compile Include="ArcARCX.cs" />
<Compile Include="ArcCG.cs" />
<Compile Include="BlackRainbow\ArcCCF.cs" />
<Compile Include="BlackRainbow\ArcIMP.cs" />
<Compile Include="BlackRainbow\ArcSPPAK.cs" />
<Compile Include="Cadath\ArcDAF.cs" />
<Compile Include="Cadath\ImageCGF.cs" />
<Compile Include="Foster\ArcC24.cs" />
<Compile Include="Foster\ArcFA2.cs" />
<Compile Include="Foster\ImageC24.cs" />
<Compile Include="GameSystem\ArcPureMail.cs" />
<Compile Include="GameSystem\AudioADP4.cs" />
<Compile Include="Qlie\DelphiDeserializer.cs" />
<Compile Include="RealLive\ArcKOE.cs" />
<Compile Include="Software House Parsley\ArcCG.cs" />
<Compile Include="Artemis\ArcPFS.cs" />
<Compile Include="AudioWMA.cs" />
<Compile Include="BlackRainbow\ArcDAT.cs" />
@@ -488,7 +500,10 @@
<Compile Include="ShiinaRio\ArcS25.cs" />
<Compile Include="ArcSAF.cs" />
<Compile Include="BlackCyc\ArcVPK.cs" />
<Compile Include="Valkyria\ArcDAT.cs" />
<Compile Include="Valkyria\ArcODN.cs" />
<Compile Include="Valkyria\ImageMAL.cs" />
<Compile Include="Valkyria\ImageMG2.cs" />
<Compile Include="Vitamin\ImageMFC.cs" />
<Compile Include="Vitamin\ImageSBI.cs" />
<Compile Include="VnEngine\ArcAXR.cs" />

View File

@@ -0,0 +1,76 @@
//! \file ArcCCF.cs
//! \date Sun Feb 19 22:05:59 2017
//! \brief Black Rainbow audio archive.
//
// 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;
namespace GameRes.Formats.BlackRainbow
{
[Export(typeof(ArchiveFormat))]
public class CcfOpener : ArchiveFormat
{
public override string Tag { get { return "CCF"; } }
public override string Description { get { return "BlackRainbow audio archive"; } }
public override uint Signature { get { return 0x22664343; } } // 'CCf"'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public CcfOpener ()
{
Extensions = new string[] { "pak" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
uint index_offset = 8;
long base_offset = index_offset + count * 4;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index_offset);
index_offset += 4;
var name = string.Format ("{0}#{1:D4}", base_name, i);
var entry = AutoEntry.Create (file, base_offset+offset, name);
if (entry.Offset >= file.MaxOffset)
return null;
dir.Add (entry);
}
for (int i = 1; i < dir.Count; ++i)
{
dir[i-1].Size = (uint)(dir[i].Offset - dir[i-1].Offset);
}
var last_entry = dir[dir.Count-1];
last_entry.Size = (uint)(file.MaxOffset - last_entry.Offset);
return new ArcFile (file, this, dir);
}
}
}

View File

@@ -0,0 +1,147 @@
//! \file ArcIMP.cs
//! \date Sun Feb 19 22:23:48 2017
//! \brief Black Rainbow image archive.
//
// 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;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.BlackRainbow
{
internal class ImpArchive : ArcFile
{
public readonly uint Key;
public ImpArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, uint key)
: base (arc, impl, dir)
{
Key = key;
}
}
[Export(typeof(ArchiveFormat))]
public class ImpOpener : ArchiveFormat
{
public override string Tag { get { return "IMP"; } }
public override string Description { get { return "BlackRainbow image archive"; } }
public override uint Signature { get { return 0x3D66; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public ImpOpener ()
{
Signatures = KnownSchemes.Keys;
}
static readonly Dictionary<uint, uint> KnownSchemes = new Dictionary<uint, uint>
{
{ 0x3D66, 0xCE032ADB }, // Kannagi
{ 0x59E8, 0xD36050EC }, // From M
};
public override ArcFile TryOpen (ArcView file)
{
uint key = KnownSchemes[file.View.ReadUInt32 (0)];
var base_name = Path.GetFileNameWithoutExtension (file.Name);
uint base_offset = 0x404;
uint offset = file.View.ReadUInt32 (4);
uint index_offset = 8;
var dir = new List<Entry>();
for (int i = 0; i < 0xFF; ++i)
{
uint next_offset = file.View.ReadUInt32 (index_offset);
uint size = next_offset - offset;
if (size > 0x10)
{
var entry = new Entry {
Name = string.Format ("{0}#{1:D3}", base_name, i),
Type = "image",
Offset = base_offset + offset,
Size = size,
};
dir.Add (entry);
}
index_offset += 4;
offset = next_offset;
}
if (0 == dir.Count)
return null;
return new ImpArchive (file, this, dir, key);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var imp_arc = (ImpArchive)arc;
var offset = entry.Offset;
var info = new ImpMetaData
{
Width = arc.File.View.ReadUInt32 (offset),
Height = arc.File.View.ReadUInt32 (offset+4),
BPP = 32,
Key = imp_arc.Key,
HasAlpha = arc.File.View.ReadUInt32 (offset+12) != 0,
};
uint packed_size = arc.File.View.ReadUInt32 (offset+8);
var input = arc.File.CreateStream (offset, packed_size+0x10);
return new ImpDecoder (input, info);
}
}
internal class ImpMetaData : ImageMetaData
{
public uint Key;
public bool HasAlpha;
}
internal sealed class ImpDecoder : BinaryImageDecoder
{
byte[] m_key;
bool m_has_alpha;
public ImpDecoder (IBinaryStream input, ImpMetaData info) : base (input, info)
{
m_has_alpha = info.HasAlpha;
m_key = new byte[4];
LittleEndian.Pack (info.Key, m_key, 0);
}
protected override ImageData GetImageData ()
{
m_input.Position = 0x10;
var pixels = new byte[Info.Width * Info.Height * 4];
using (var lzs = new ByteStringEncryptedStream (m_input.AsStream, m_key, true))
using (var input = new LzssStream (lzs))
{
if (pixels.Length != input.Read (pixels, 0, pixels.Length))
throw new InvalidFormatException();
var format = m_has_alpha ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
return ImageData.Create (Info, format, null, pixels);
}
}
}
}

View File

@@ -0,0 +1,104 @@
//! \file ArcSPPAK.cs
//! \date Mon Feb 20 07:45:17 2017
//! \brief Black Rainbow script archive.
//
// 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.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.BlackRainbow
{
internal class SpArchive : ArcFile
{
public readonly byte Key;
public SpArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, byte key)
: base (arc, impl, dir)
{
Key = key;
}
}
[Export(typeof(ArchiveFormat))]
public class SpPakOpener : ArchiveFormat
{
public override string Tag { get { return "PAK/SP"; } }
public override string Description { get { return "BlackRainbow script archive"; } }
public override uint Signature { get { return 0x69695669; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public SpPakOpener ()
{
Signatures = KnownSchemes.Keys;
}
static readonly Dictionary<uint, byte> KnownSchemes = new Dictionary<uint, byte>
{
{ 0x69695669, 0x07 }, // Kannagi
{ 0x8492E36F, 0x9C }, // From M
};
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
byte key = KnownSchemes[file.View.ReadUInt32 (0)];
var base_name = Path.GetFileNameWithoutExtension (file.Name);
uint index_offset = 8;
long base_offset = index_offset + 4 * count;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = string.Format ("{0}#{1:D4}", base_name, i);
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = base_offset + file.View.ReadUInt32 (index_offset);
index_offset += 4;
dir.Add (entry);
}
for (int i = 1; i < dir.Count; ++i)
{
dir[i-1].Size = (uint)(dir[i].Offset - dir[i-1].Offset);
}
var last_entry = dir[dir.Count-1];
last_entry.Size = (uint)(file.MaxOffset - last_entry.Offset);
return new SpArchive (file, this, dir, key);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var sp_arc = (SpArchive)arc;
var key = sp_arc.Key;
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
for (int i = 0; i < data.Length; ++i)
{
data[i] = Binary.RotByteR ((byte)(data[i] ^ key), 2);
}
return new BinMemoryStream (data, entry.Name);
}
}
}

104
ArcFormats/Cadath/ArcDAF.cs Normal file
View File

@@ -0,0 +1,104 @@
//! \file ArcDAF.cs
//! \date Thu Feb 23 01:26:17 2017
//! \brief Cadath resource archive 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.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.Cadath
{
[Export(typeof(ArchiveFormat))]
public class DafOpener : ArchiveFormat
{
public override string Tag { get { return "ARC/DAF"; } }
public override string Description { get { return "Cadath resource archive"; } }
public override uint Signature { get { return 0x1A464144; } } // 'DAF'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
uint index_offset = 8;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index_offset);
uint size = file.View.ReadUInt32 (index_offset+4);
var name = file.View.ReadString (index_offset+8, 0x18);
var entry = FormatCatalog.Instance.Create<Entry> (name);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.Offset = offset;
entry.Size = size;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 0x20;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (!entry.Name.EndsWith (".snr", System.StringComparison.InvariantCultureIgnoreCase)
|| !arc.File.View.AsciiEqual (entry.Offset, "SNR\x1A"))
return base.OpenEntry (arc, entry);
try
{
var data = arc.File.View.ReadBytes (entry.Offset+12, entry.Size-12);
DecryptSnr (data);
CgfDecoder.Decrypt (data, data.Length);
// uint checksum = LittleEndian.ToUInt32 (data, 0);
// uint crc = Crc32Normal.Compute (data, 4, data.Length-4);
var input = new MemoryStream (data, 4, data.Length-4);
return new ZLibStream (input, CompressionMode.Decompress);
}
catch
{
return base.OpenEntry (arc, entry);
}
}
void DecryptSnr (byte[] data)
{
byte key = 0x84;
for (int i = 0; i < data.Length; ++i)
{
data[i] -= key;
for (int count = ((i & 0xF) + 2) / 3; count > 0; --count)
{
key += 0x99;
}
}
}
}
}

View File

@@ -0,0 +1,179 @@
//! \file ImageCGF.cs
//! \date Thu Feb 23 15:12:04 2017
//! \brief Cadath 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.Cadath
{
internal class CgfMetaData : ImageMetaData
{
public int Method;
}
[Export(typeof(ImageFormat))]
public class CgfFormat : ImageFormat
{
public override string Tag { get { return "CGF/CADATH"; } }
public override string Description { get { return "Cadath image format"; } }
public override uint Signature { get { return 0x1A464743; } } // 'CGF'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (10);
int method = header[4];
if (method != 1 && method != 2)
return null;
return new CgfMetaData
{
Width = header.ToUInt16 (6),
Height = header.ToUInt16 (8),
BPP = header[5],
Method = method,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new CgfDecoder (file, (CgfMetaData)info);
var pixels = reader.Unpack();
return ImageData.Create (info, reader.Format, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("CgfFormat.Write not implemented");
}
}
internal sealed class CgfDecoder
{
IBinaryStream m_input;
CgfMetaData m_info;
byte[] m_output;
public PixelFormat Format { get; private set; }
public CgfDecoder (IBinaryStream input, CgfMetaData info)
{
m_input = input;
m_info = info;
m_output = new byte[(int)m_info.Width * (int)m_info.Height * m_info.BPP / 8];
if (32 == m_info.BPP)
Format = PixelFormats.Bgra32;
else
Format = PixelFormats.Bgr24;
}
public byte[] Unpack ()
{
m_input.Position = 10;
int pixel_size = m_info.BPP / 8;
Action<byte[], int, byte[]> unpack_channel;
if (1 == m_info.Method)
unpack_channel = UnpackZLib;
else if (2 == m_info.Method)
unpack_channel = UnpackRle;
else
throw new NotSupportedException();
var channel = new byte[m_info.Width * m_info.Height];
byte[] buffer = null;
for (int i = 0; i < pixel_size; ++i)
{
int channel_length = m_input.ReadInt32();
if (channel_length < 0)
throw new InvalidFormatException();
if (null == buffer || channel_length > buffer.Length)
buffer = new byte[channel_length];
if (channel_length != m_input.Read (buffer, 0, channel_length))
throw new EndOfStreamException();
unpack_channel (buffer, channel_length, channel);
int dst = i;
for (int j = 0; j < channel.Length; ++j)
{
m_output[dst] = channel[j];
dst += pixel_size;
}
}
return m_output;
}
void UnpackZLib (byte[] input, int length, byte[] output)
{
Decrypt (input, length);
using (var zinput = new MemoryStream (input, 4, length - 4))
using (var z = new ZLibStream (zinput, CompressionMode.Decompress))
z.Read (output, 0, output.Length);
}
void UnpackRle (byte[] input, int length, byte[] output)
{
int unpacked_length = LittleEndian.ToInt32 (input, 0);
if (unpacked_length < 0 || unpacked_length > output.Length)
throw new InvalidFormatException();
int src = 4;
int dst = 0;
byte last_byte = 0;
while (dst < unpacked_length)
{
byte b = input[src++];
output[dst++] = b;
if (b == last_byte)
{
int count = input[src++];
for (int i = 0; i < count; ++i)
{
output[dst++] = b;
}
}
last_byte = b;
}
}
unsafe internal static void Decrypt (byte[] data, int length)
{
if (length < 4)
return;
if (length > data.Length)
throw new ArgumentOutOfRangeException ("length");
fixed (byte* data8 = data)
{
uint* data32 = (uint*)data8;
const uint seed = 0x3977141B;
uint key = seed;
for (int i = 0; i < length; i += 4)
{
key = Binary.RotL (key, 3);
*data32++ ^= key;
key += seed;
}
}
}
}
}

View File

@@ -39,13 +39,20 @@ namespace GameRes.Formats.Cromwell
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public GraphicPakOpener ()
{
Signatures = new uint[] { 0x70617247, 0x63696F56 };
}
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "hic PackData"))
bool is_graphic = file.View.AsciiEqual (0, "Graphic PackData");
if (!is_graphic && !file.View.AsciiEqual (0, "Voice PackData. "))
return null;
int count = file.View.ReadInt32 (0x10);
if (!IsSaneCount (count))
return null;
string type = is_graphic ? "image" : "audio";
uint index_offset = 0x14;
var dir = new List<Entry> (count);
@@ -55,7 +62,7 @@ namespace GameRes.Formats.Cromwell
if (string.IsNullOrWhiteSpace (name))
return null;
index_offset += 0xC;
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
var entry = new PackedEntry { Name = name, Type = type };
entry.Offset = file.View.ReadUInt32 (index_offset);
if (entry.Offset >= file.MaxOffset)
return null;

View File

@@ -74,29 +74,31 @@ namespace GameRes.Formats.Emote
{
foreach (var key in KnownKeys)
{
if (reader.Parse (key))
try
{
var dir = reader.GetTextures();
if (null == dir)
dir = reader.GetLayers();
if (null == dir || 0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
if (reader.Parse (key))
return OpenArcFile (reader, file);
if (!reader.IsEncrypted)
break;
}
if (!reader.IsEncrypted)
break;
catch { /* ignore parse errors caused by invalid key */ }
}
if (reader.ParseNonEncrypted())
{
var dir = reader.GetLayers();
if (null == dir)
return null;
return new ArcFile (file, this, dir);
}
return OpenArcFile (reader, file);
return null;
}
}
ArcFile OpenArcFile (PsbReader reader, ArcView file)
{
var dir = reader.GetTextures();
if (null == dir)
dir = reader.GetLayers();
if (null == dir || 0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var tex = (TexEntry)entry;
@@ -246,26 +248,64 @@ namespace GameRes.Formats.Emote
var item_value = item.Value as IDictionary;
if (null == item_value)
continue;
var texture = item_value["texture"] as IDictionary;
if (null == texture)
continue;
var pixel = texture["pixel"] as EmChunk;
if (item_value.Contains ("texture"))
{
AddTextureEntry (dir, item.Key, item_value["texture"] as IDictionary);
}
else if (item_value.Contains ("icon"))
{
AddIconEntry (dir, item.Key, item_value["icon"] as IDictionary);
}
}
return dir;
}
void AddTextureEntry (List<Entry> dir, object name, IDictionary texture)
{
if (null == texture)
return;
var pixel = texture["pixel"] as EmChunk;
if (null == pixel)
return;
var entry = new TexEntry {
Name = name.ToString(),
Type = "image",
Offset = DataOffset + pixel.Offset,
Size = (uint)pixel.Length,
TexType = texture["type"].ToString(),
Width = Convert.ToInt32 (texture["width"]),
Height = Convert.ToInt32 (texture["height"]),
TruncatedWidth = Convert.ToInt32 (texture["truncated_width"]),
TruncatedHeight = Convert.ToInt32 (texture["truncated_height"]),
};
dir.Add (entry);
}
void AddIconEntry (List<Entry> dir, object name, IDictionary icon_list)
{
if (null == icon_list)
return;
foreach (DictionaryEntry icon in icon_list)
{
var layer = icon.Value as IDictionary;
var pixel = layer["pixel"] as EmChunk;
if (null == pixel)
continue;
var entry = new TexEntry {
Name = item.Key.ToString(),
Type = "image",
Offset = DataOffset + pixel.Offset,
Size = (uint)pixel.Length,
TexType = texture["type"].ToString(),
Width = Convert.ToInt32 (texture["width"]),
Height = Convert.ToInt32 (texture["height"]),
TruncatedWidth = Convert.ToInt32 (texture["truncated_width"]),
TruncatedHeight = Convert.ToInt32 (texture["truncated_height"]),
Name = name.ToString()+'#'+icon.Key.ToString(),
Type = "image",
Offset = DataOffset + pixel.Offset,
Size = (uint)pixel.Length,
Width = Convert.ToInt32 (layer["width"]),
Height = Convert.ToInt32 (layer["height"]),
OffsetX = Convert.ToInt32 (layer["originX"]),
OffsetY = Convert.ToInt32 (layer["originY"]),
TexType = layer.Contains ("compress") ? layer["compress"].ToString() : "RGBA8",
};
entry.TruncatedWidth = entry.Width;
entry.TruncatedHeight = entry.Height;
dir.Add (entry);
}
return dir;
}
int m_names;
@@ -651,6 +691,8 @@ namespace GameRes.Formats.Emote
ReadA8L8 (pixels, stride);
else if ("RGBA4444" == m_info.TexType)
ReadRgba4444 (pixels, stride);
else if ("RL" == m_info.TexType)
ReadRle (pixels, stride);
else
throw new NotImplementedException (string.Format ("PSB texture format '{0}' not implemented", m_info.TexType));
return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, stride);
@@ -734,5 +776,28 @@ namespace GameRes.Formats.Emote
}
}
}
void ReadRle (byte[] output, int dst_stride)
{
const int pixel_size = 4;
m_input.Position = 0;
int dst = 0;
while (dst < output.Length)
{
int count = m_input.ReadUInt8();
if (0 == (count & 0x80))
{
count = pixel_size * (count + 1);
dst += m_input.Read (output, dst, count);
}
else
{
count = pixel_size * ((count & 0x7F) + 3);
m_input.Read (output, dst, pixel_size);
Binary.CopyOverlapped (output, dst, dst+pixel_size, count-pixel_size);
dst += count;
}
}
}
}
}

View File

@@ -28,7 +28,9 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
using GameRes.Utility;
@@ -136,8 +138,12 @@ namespace GameRes.Formats.Entis
string GetArcPassword (string arc_name)
{
var title = FormatCatalog.Instance.LookupGame (arc_name, @"..\*.exe");
string password = null;
if (string.IsNullOrEmpty (title) || !KnownKeys.ContainsKey (title))
{
password = ExtractNoaPassword (arc_name);
if (password != null)
return password;
var options = Query<NoaOptions> (arcStrings.ArcEncryptedNotice);
if (!string.IsNullOrEmpty (options.PassPhrase))
{
@@ -145,7 +151,6 @@ namespace GameRes.Formats.Entis
}
title = options.Scheme;
}
string password = null;
if (!string.IsNullOrEmpty (title))
{
Dictionary<string, string> filemap;
@@ -158,6 +163,51 @@ namespace GameRes.Formats.Entis
return password;
}
string ExtractNoaPassword (string arc_name)
{
if (VFS.IsVirtual)
return null;
var dir = VFS.GetDirectoryName (arc_name);
var noa_name = Path.GetFileName (arc_name);
var parent_dir = Directory.GetParent (dir).FullName;
var exe_files = VFS.GetFiles (VFS.CombinePath (parent_dir, "*.exe")).Concat (VFS.GetFiles (VFS.CombinePath (dir, "*.exe")));
foreach (var exe_entry in exe_files)
{
try
{
using (var exe = new ExeFile.ResourceAccessor (exe_entry.Name))
{
var cotomi = exe.GetResource ("IDR_COTOMI", "#10");
if (null == cotomi)
continue;
using (var res = new MemoryStream (cotomi))
using (var input = DecodeNemesis (res))
{
var xml = new XmlDocument();
xml.Load (input);
var password = XmlFindArchiveKey (xml, noa_name);
if (password != null)
return password;
}
}
}
catch { /* ignore errors */ }
}
return null;
}
string XmlFindArchiveKey (XmlDocument xml, string filename)
{
foreach (XmlNode archive in xml.DocumentElement.SelectNodes ("archive[@path and @key]"))
{
var attr = archive.Attributes;
var path = attr["path"].Value;
if (VFS.IsPathEqualsToFileName (path, filename))
return attr["key"].Value;
}
return null;
}
Stream DecodeNemesis (Stream input)
{
var decoder = new NemesisDecodeContext();

View File

@@ -0,0 +1,92 @@
//! \file ArcC24.cs
//! \date Sun Feb 19 14:21:21 2017
//! \brief Foster's game engine multi-frame image.
//
// 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;
namespace GameRes.Formats.Foster
{
[Export(typeof(ArchiveFormat))]
public class C24Opener : ArchiveFormat
{
public override string Tag { get { return "C24"; } }
public override string Description { get { return "Foster game engine multi-image"; } }
public override uint Signature { get { return 0x00343243; } } // 'C24'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
var dir = new List<Entry> (count);
uint index_offset = 8;
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index_offset);
index_offset += 4;
if (offset > 0 && offset <= file.MaxOffset)
{
var entry = new Entry
{
Name = string.Format ("{0}@{1:D4}", base_name, i),
Type = "image",
Offset = offset,
};
dir.Add (entry);
}
}
dir.Sort ((a, b) => (int)(a.Offset - b.Offset));
for (int i = 1; i < dir.Count; ++i)
{
dir[i-1].Size = (uint)(dir[i].Offset - dir[i-1].Offset);
}
var last_entry = dir[dir.Count-1];
last_entry.Size = (uint)(file.MaxOffset - last_entry.Offset);
return new ArcFile (file, this, dir);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var offset = entry.Offset;
var info = new C24MetaData
{
Width = arc.File.View.ReadUInt32 (offset),
Height = arc.File.View.ReadUInt32 (offset+4),
OffsetX = arc.File.View.ReadInt32 (offset+8),
OffsetY = arc.File.View.ReadInt32 (offset+12),
BPP = 24,
DataOffset = (uint)(offset + 0x10),
};
var input = arc.File.CreateStream (0, (uint)arc.File.MaxOffset);
return new C24Decoder (input, info);
}
}
}

246
ArcFormats/Foster/ArcFA2.cs Normal file
View File

@@ -0,0 +1,246 @@
//! \file ArcFA2.cs
//! \date Sun Feb 19 10:46:57 2017
//! \brief Foster game engine resource archive.
//
// 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 GameRes.Utility;
namespace GameRes.Formats.Foster
{
[Export(typeof(ArchiveFormat))]
public class Fa2Opener : ArchiveFormat
{
public override string Tag { get { return "FA2"; } }
public override string Description { get { return "Foster game engine resource archive"; } }
public override uint Signature { get { return 0x00324146; } } // 'FA2'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0xC);
if (!IsSaneCount (count))
return null;
bool is_packed = (file.View.ReadByte (4) & 1) != 0;
uint index_offset = file.View.ReadUInt32 (8);
byte[] index;
using (var input = file.CreateStream (index_offset))
{
if (is_packed)
index = Decompress (input, (uint)count * 0x20);
else
index = file.View.ReadBytes (index_offset, (uint)(file.MaxOffset - index_offset));
}
uint data_offset = 0x10;
int index_pos = 0;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = Binary.GetCString (index, index_pos, 0xF);
index_pos += 0xF;
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.IsPacked = (index[index_pos] & 2) != 0;
entry.Offset = data_offset;
index_pos += 9;
entry.UnpackedSize = index.ToUInt32 (index_pos);
entry.Size = index.ToUInt32 (index_pos+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_pos += 8;
data_offset += (entry.Size + 0xFu) & ~0xFu;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var pent = entry as PackedEntry;
if (null == pent || !pent.IsPacked)
return input;
var data = Decompress (input, pent.UnpackedSize);
return new BinMemoryStream (data, entry.Name);
}
byte[] Decompress (IBinaryStream input, uint unpacked_size)
{
var comp = new Fa2Compression (input, unpacked_size);
return comp.Unpack();
}
}
internal class Fa2Compression
{
IBinaryStream m_input;
byte[] m_output;
public Fa2Compression (IBinaryStream input, uint unpacked_size)
{
m_input = input;
m_output = new byte[unpacked_size];
}
public byte[] Unpack ()
{
m_bit_count = 0;
int dst = 0;
while (dst < m_output.Length)
{
if (GetNextBit() != 0)
{
m_output[dst++] = m_input.ReadUInt8();
continue;
}
int offset;
if (GetNextBit() != 0)
{
if (GetNextBit() != 0)
{
offset = m_input.ReadUInt8() << 3;
offset |= GetBits (3);
offset += 0x100;
if (offset >= 0x8FF)
break;
}
else
{
offset = m_input.ReadUInt8();
}
m_output[dst ] = m_output[dst-offset-1];
m_output[dst+1] = m_output[dst-offset ];
dst += 2;
}
else
{
if (GetNextBit() != 0)
{
offset = m_input.ReadUInt8() << 1;
offset |= GetNextBit();
}
else
{
offset = 0x100;
if (GetNextBit() != 0)
{
offset |= m_input.ReadUInt8();
offset <<= 1;
offset |= GetNextBit();
}
else if (GetNextBit() != 0)
{
offset |= m_input.ReadUInt8();
offset <<= 2;
offset |= GetBits (2);
}
else if (GetNextBit() != 0)
{
offset |= m_input.ReadUInt8();
offset <<= 3;
offset |= GetBits (3);
}
else
{
offset |= m_input.ReadUInt8();
offset <<= 4;
offset |= GetBits (4);
}
}
int count = 0;
if (GetNextBit() != 0)
{
count = 3;
}
else if (GetNextBit() != 0)
{
count = 4;
}
else if (GetNextBit() != 0)
{
count = 5 + GetNextBit();
}
else if (GetNextBit() != 0)
{
count = 7 + GetBits (2);
}
else if (GetNextBit() != 0)
{
count = 11 + GetBits (4);
}
else
{
count = 27 + m_input.ReadUInt8();
}
Binary.CopyOverlapped (m_output, dst - offset - 1, dst, count);
dst += count;
}
}
return m_output;
}
uint m_bits;
int m_bit_count;
void FetchBits ()
{
m_bits = m_input.ReadUInt32();
m_bit_count = 32;
}
int GetNextBit ()
{
if (0 == m_bit_count)
FetchBits();
int bit = (int)((m_bits >> 31) & 1);
m_bits <<= 1;
--m_bit_count;
return bit;
}
int GetBits (int count)
{
uint bits = 0;
int avail_bits = Math.Min (count, m_bit_count);
if (avail_bits > 0)
{
bits = m_bits >> (32 - avail_bits);
m_bits <<= avail_bits;
m_bit_count -= avail_bits;
count -= avail_bits;
}
if (count > 0)
{
FetchBits();
bits = bits << count | m_bits >> (32 - count);
m_bits <<= count;
m_bit_count -= count;
}
return (int)bits;
}
}
}

View File

@@ -0,0 +1,162 @@
//! \file ImageC24.cs
//! \date Sun Feb 19 13:13:16 2017
//! \brief Foster game engine 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;
namespace GameRes.Formats.Foster
{
internal class C24MetaData : ImageMetaData
{
public uint DataOffset;
}
/// <summary>
/// ShiinaRio S25 predecessor.
/// </summary>
[Export(typeof(ImageFormat))]
public class C24Format : ImageFormat
{
public override string Tag { get { return "C24"; } }
public override string Description { get { return "Foster game engine image format"; } }
public override uint Signature { get { return 0x00343243; } } // 'C24'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (12);
int count = header.ToInt32 (4);
if (count <= 0)
return null;
file.Position = header.ToUInt32 (8);
var info = new C24MetaData { BPP = 24 };
info.Width = file.ReadUInt32();
info.Height = file.ReadUInt32();
info.OffsetX = file.ReadInt32();
info.OffsetY = file.ReadInt32();
info.DataOffset = (uint)file.Position;
return info;
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
using (var reader = new C24Decoder (file, (C24MetaData)info, true))
return reader.Image;
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("C24Format.Write not implemented");
}
}
internal sealed class C24Decoder : IImageDecoder
{
IBinaryStream m_input;
C24MetaData m_info;
byte[] m_output;
bool m_should_dispose;
ImageData m_image;
public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } }
public ImageFormat SourceFormat { get { return null; } }
public ImageMetaData Info { get { return m_info; } }
public ImageData Image
{
get
{
if (null == m_image)
{
var pixels = Unpack();
m_image = ImageData.Create (m_info, PixelFormats.Bgr24, null, pixels);
}
return m_image;
}
}
public C24Decoder (IBinaryStream file, C24MetaData info, bool leave_open = false)
{
m_input = file;
m_info = info;
m_output = new byte[3 * m_info.Width * m_info.Height];
m_should_dispose = !leave_open;
}
public byte[] Unpack ()
{
m_input.Position = m_info.DataOffset;
var rows = new uint[m_info.Height];
for (int i = 0; i < rows.Length; ++i)
rows[i] = m_input.ReadUInt32();
int dst = 0;
int width = (int)m_info.Width;
foreach (uint row_offset in rows)
{
m_input.Position = row_offset;
bool rle = false;
for (int x = 0; x < width; )
{
int count = m_input.ReadUInt8();
if (!rle)
{
if (0xFF == count)
count = m_input.ReadUInt16();
int byte_count = count * 3;
for (int i = 0; i < byte_count; ++i)
m_output[dst++] = 0xFF;
}
else
{
if (0 == count)
count = m_input.ReadUInt16();
int byte_count = count * 3;
m_input.Read (m_output, dst, byte_count);
dst += byte_count;
}
x += count;
rle = !rle;
}
}
return m_output;
}
#region IDisposable Members
bool m_disposed = false;
public void Dispose ()
{
if (!m_disposed)
{
if (m_should_dispose)
{
m_input.Dispose();
}
m_disposed = true;
}
System.GC.SuppressFinalize (this);
}
#endregion
}
}

View File

@@ -96,7 +96,7 @@ namespace GameRes.Formats.GameSystem
var cent = (ChrEntry)entry;
var input = arc.File.CreateStream (entry.Offset, entry.Size);
if (cent.Info is ChrMetaData)
return new ChrDecoder (input, cent.Info);
return new ChrDecoder (input, cent.Info as ChrMetaData);
else
return new ChrFrameDecoder (input, cent.Info);
}
@@ -109,16 +109,15 @@ namespace GameRes.Formats.GameSystem
public ImageMetaData Info;
}
internal class ChrDecoder : BinaryImageDecoder
internal class ChrDecoder : ChrReader
{
public ChrDecoder (IBinaryStream input, ImageMetaData info) : base (input, info)
public ChrDecoder (IBinaryStream input, ChrMetaData 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);
var pixels = UnpackBaseline();
return ImageData.CreateFlipped (Info, PixelFormats.Bgra32, null, pixels, Stride);
}
}

View File

@@ -65,6 +65,8 @@ namespace GameRes.Formats.GameSystem
long next_offset = (long)file.View.ReadUInt32 (index_offset+12) << 9;
if (next_offset < offset || next_offset > file.MaxOffset)
return null;
if (name.EndsWith (".CRGB") || name.EndsWith (".CHAR"))
entry.Type = "image";
entry.Offset = offset;
entry.Size = (uint)(next_offset - offset);
dir.Add (entry);
@@ -101,5 +103,25 @@ namespace GameRes.Formats.GameSystem
var ext = Encoding.ASCII.GetString (name_buf, 12, ext_end-12);
return name + '.' + ext;
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
if (entry.Name.EndsWith (".BGD") || entry.Name.EndsWith (".CRGB"))
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var info = new ImageMetaData { Width = 800, Height = 600, BPP = 24 };
return new CgdReader (input, info);
}
else if (entry.Name.EndsWith (".CHAR"))
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
var info = new ChrMetaData {
Width = 800, Height = 600, BPP = 32,
DataOffset = 0, RgbSize = (int)input.Length,
};
return new ChrReader (input, info);
}
return base.OpenImage (arc, entry);
}
}
}

View File

@@ -0,0 +1,201 @@
//! \file ArcPureMail.cs
//! \date Sun Feb 12 04:18:56 2017
//! \brief 0verflow Game System resource archive.
//
// 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
{
internal class PmDatEntry : PackedEntry
{
public bool StoredSize;
}
[Export(typeof(ArchiveFormat))]
public class PmDatOpener : ArchiveFormat
{
public override string Tag { get { return "DAT/PUREMAIL"; } }
public override string Description { get { return "PureMail 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 ArcFile TryOpen (ArcView file)
{
if (file.MaxOffset <= 12 || file.MaxOffset > uint.MaxValue)
return null;
uint packed_size = file.View.ReadUInt32 (file.MaxOffset-12) ^ 0xF0F0F0F0;
uint unpacked_size = file.View.ReadUInt32 (file.MaxOffset-8) ^ 0xF0F0F0F0;
const uint entry_record_size = 0x50;
int count = (int)(unpacked_size / entry_record_size);
if (unpacked_size % entry_record_size != 0 || packed_size >= file.MaxOffset || !IsSaneCount (count))
return null;
var unpacked = new byte[unpacked_size];
using (var packed = file.CreateStream (file.MaxOffset-12-packed_size, packed_size))
LzUnpack (packed, unpacked);
using (var index = new BinMemoryStream (unpacked))
{
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
int flags = index.ReadInt32();
var name = index.ReadCString (0x40);
var entry = FormatCatalog.Instance.Create<PmDatEntry> (name);
entry.Offset = index.ReadUInt32();
entry.Size = index.ReadUInt32();
entry.UnpackedSize = index.ReadUInt32();
if (!entry.CheckPlacement (file.MaxOffset))
return null;
if (entry.Name.EndsWith (".CRGB", StringComparison.InvariantCultureIgnoreCase) ||
entry.Name.EndsWith (".CHAR", StringComparison.InvariantCultureIgnoreCase) ||
entry.Name.EndsWith (".rol", StringComparison.InvariantCultureIgnoreCase) ||
entry.Name.EndsWith (".edg", StringComparison.InvariantCultureIgnoreCase))
entry.Type = "image";
entry.IsPacked = (flags & 0xFF0000) != 0;
entry.StoredSize = (flags & 0x2000000) != 0;
dir.Add (entry);
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var pent = entry as PmDatEntry;
if (null == pent || !pent.IsPacked)
return base.OpenEntry (arc, entry);
using (var input = arc.File.CreateStream (entry.Offset, entry.Size))
{
var unpacked_size = pent.UnpackedSize;
if (pent.StoredSize)
{
unpacked_size = input.ReadUInt32();
pent.UnpackedSize = unpacked_size;
}
var output = new byte[unpacked_size];
LzUnpack (input, output);
return new BinMemoryStream (output);
}
}
void LzUnpack (IBinaryStream input, byte[] output)
{
var frame = new byte[0x1000];
int dst = 0;
int bits = 0;
int mask = 0;
int frame_pos = 0xFEE;
while (dst < output.Length)
{
mask >>= 1;
if (0 == mask)
{
bits = input.ReadByte();
if (-1 == bits)
break;
mask = 0x80;
}
if (0 == (bits & mask))
{
int b = input.ReadByte();
if (-1 == b)
break;
output[dst++] = (byte)b;
frame[frame_pos++ & 0xFFF] = (byte)b;
}
else
{
int offset = input.ReadUInt16();
int count = (offset & 0xF) + 3;
offset >>= 4;
while (count --> 0 && dst < output.Length)
{
byte v = frame[offset++ & 0xFFF];
frame[frame_pos++ & 0xFFF] = v;
output[dst++] = v;
}
}
}
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
if (entry.Name.EndsWith (".BGD", StringComparison.InvariantCultureIgnoreCase) ||
entry.Name.EndsWith (".CRGB", StringComparison.InvariantCultureIgnoreCase))
{
var input = OpenEntry (arc, entry);
var info = new ImageMetaData { Width = 800, Height = 600, BPP = 24 };
return new CgdReader (BinaryStream.FromStream (input, entry.Name), info);
}
else if (entry.Name.EndsWith (".edg", StringComparison.InvariantCultureIgnoreCase))
{
var input = OpenEntry (arc, entry);
var info = new ImageMetaData { Width = 460, Height = 345, BPP = 24 };
return new ImgReader (BinaryStream.FromStream (input, entry.Name), info);
}
else if (entry.Name.EndsWith (".rol", StringComparison.InvariantCultureIgnoreCase))
{
var input = OpenEntry (arc, entry);
uint width = 202;
uint height = (uint)input.Length / (((width * 3u) + 3u) & ~3u);
var info = new ImageMetaData { Width = width, Height = height, BPP = 24 };
return new ImgReader (BinaryStream.FromStream (input, entry.Name), info);
}
else if (entry.Name.EndsWith (".CHAR", StringComparison.InvariantCultureIgnoreCase))
{
var input = OpenEntry (arc, entry);
var info = new ChrMetaData {
Width = 800, Height = 600, BPP = 32,
DataOffset = 0, RgbSize = (int)input.Length,
};
return new ChrReader (BinaryStream.FromStream (input, entry.Name), info);
}
return base.OpenImage (arc, entry);
}
}
internal class ImgReader : BinaryImageDecoder
{
public ImgReader (IBinaryStream input, ImageMetaData info) : base (input, info)
{
}
protected override ImageData GetImageData ()
{
m_input.Position = 0;
int stride = (int)Info.Width * Info.BPP / 8;
stride = (stride + 3) & ~3;
int total = stride * (int)Info.Height;
var pixels = m_input.ReadBytes (total);
if (pixels.Length != total)
throw new EndOfStreamException();
return ImageData.CreateFlipped (Info, PixelFormats.Bgr24, null, pixels, stride);
}
}
}

View File

@@ -0,0 +1,331 @@
//! \file AudioADP4.cs
//! \date Fri Feb 24 14:14:17 2017
//! \brief 'Game System' compressed audio 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.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.GameSystem
{
[Export(typeof(AudioFormat))]
public class Adp4Audio : AudioFormat
{
public override string Tag { get { return "ADP4"; } }
public override string Description { get { return "'GameSystem' compressed audio"; } }
public override uint Signature { get { return 0; } }
public override bool CanWrite { get { return false; } }
public override SoundInput TryOpen (IBinaryStream file)
{
bool is_adp4 = file.Name.EndsWith (".adp4", StringComparison.InvariantCultureIgnoreCase);
if (!is_adp4 || file.Length <= 4)
return null;
var decoder = new AdpDecoder (file);
var pcm = decoder.Decode();
var format = new WaveFormat {
FormatTag = 1,
Channels = 2,
SamplesPerSecond = 44100, // XXX varies
BlockAlign = 4,
BitsPerSample = 16,
};
format.SetBPS();
var input = new MemoryStream (pcm);
return new RawPcmInput (input, format);
}
}
internal sealed class AdpDecoder
{
IBinaryStream m_input;
public AdpDecoder (IBinaryStream input)
{
m_input = input;
}
byte[] m_output;
public byte[] Decode ()
{
m_input.Position = 0;
int sample_count = m_input.ReadInt32();
m_output = new byte[sample_count * 8];
DecodeBits (sample_count);
return m_output;
}
void DecodeBits (int sample_count)
{
bool copy_stereo = true;
uint last_sample = 0;
short sample = 0;
uint key = 0;
int dst = 0;
int bits = 0;
while (dst < m_output.Length)
{
if (bits < 0x100)
{
bits = m_input.ReadUInt8() | 0x8000;
}
int count = m_input.ReadUInt8();
if (count < 0x80)
count = m_input.ReadUInt8() | count << 8;
else
count &= 0x7F;
count++;
if (0 == (bits & 1))
{
do
{
if (dst >= m_output.Length)
return;
uint data_bits = m_input.ReadUInt8() ^ key++;
last_sample += data_bits & 0xF;
int s = AdpSamples[last_sample] + sample;
last_sample = AdpAdjust[last_sample];
sample = Clamp (s);
LittleEndian.Pack (sample, m_output, dst);
dst += 2;
if (copy_stereo)
LittleEndian.Pack (sample, m_output, dst);
dst += 2;
last_sample += (data_bits >> 4) & 0xF;
s = AdpSamples[last_sample] + sample;
last_sample = AdpAdjust[last_sample];
sample = Clamp (s);
LittleEndian.Pack (sample, m_output, dst);
dst += 2;
if (copy_stereo)
LittleEndian.Pack (sample, m_output, dst);
dst += 2;
}
while (--count > 0);
}
else
{
last_sample = 0;
sample = 0;
dst += count * 8;
}
bits >>= 1;
}
}
static short Clamp (int sample)
{
if (sample > 0x7FFF)
sample = 0x7FFF;
else if (sample < -32768)
sample = -32768;
return (short)sample;
}
static readonly int[] AdpSamples = {
0, 2, 4, 6, 7, 9, 11, 13, 0, -2, -4, -6, -7, -9, -11, -13,
1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15,
1, 3, 5, 7, 10, 12, 14, 16, -1, -3, -5, -7, -10, -12, -14, -16,
1, 3, 6, 8, 11, 13, 16, 18, -1, -3, -6, -8, -11, -13, -16, -18,
1, 4, 6, 9, 12, 15, 17, 20, -1, -4, -6, -9, -12, -15, -17, -20,
1, 4, 7, 10, 13, 16, 19, 22, -1, -4, -7, -10, -13, -16, -19, -22,
1, 4, 8, 11, 14, 17, 21, 24, -1, -4, -8, -11, -14, -17, -21, -24,
1, 5, 8, 12, 15, 19, 22, 26, -1, -5, -8, -12, -15, -19, -22, -26,
2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30,
2, 6, 10, 14, 19, 23, 27, 31, -2, -6, -10, -14, -19, -23, -27, -31,
2, 7, 11, 16, 21, 26, 30, 35, -2, -7, -11, -16, -21, -26, -30, -35,
2, 7, 13, 18, 23, 28, 34, 39, -2, -7, -13, -18, -23, -28, -34, -39,
2, 8, 14, 20, 25, 31, 37, 43, -2, -8, -14, -20, -25, -31, -37, -43,
3, 9, 15, 21, 28, 34, 40, 46, -3, -9, -15, -21, -28, -34, -40, -46,
3, 10, 17, 24, 31, 38, 45, 52, -3, -10, -17, -24, -31, -38, -45, -52,
3, 11, 19, 27, 34, 42, 50, 58, -3, -11, -19, -27, -34, -42, -50, -58,
4, 12, 21, 29, 38, 46, 55, 63, -4, -12, -21, -29, -38, -46, -55, -63,
4, 13, 23, 32, 41, 50, 60, 69, -4, -13, -23, -32, -41, -50, -60, -69,
5, 15, 25, 35, 46, 56, 66, 76, -5, -15, -25, -35, -46, -56, -66, -76,
5, 16, 28, 39, 50, 61, 73, 84, -5, -16, -28, -39, -50, -61, -73, -84,
6, 18, 31, 43, 56, 68, 81, 93, -6, -18, -31, -43, -56, -68, -81, -93,
6, 20, 34, 48, 61, 75, 89, 103, -6, -20, -34, -48, -61, -75, -89, -103,
7, 22, 37, 52, 67, 82, 97, 112, -7, -22, -37, -52, -67, -82, -97, -112,
8, 24, 41, 57, 74, 90, 107, 123, -8, -24, -41, -57, -74, -90, -107, -123,
9, 27, 45, 63, 82, 100, 118, 136, -9, -27, -45, -63, -82, -100, -118, -136,
10, 30, 50, 70, 90, 110, 130, 150, -10, -30, -50, -70, -90, -110, -130, -150,
11, 33, 55, 77, 99, 121, 143, 165, -11, -33, -55, -77, -99, -121, -143, -165,
12, 36, 60, 84, 109, 133, 157, 181, -12, -36, -60, -84, -109, -133, -157, -181,
13, 40, 66, 93, 120, 147, 173, 200, -13, -40, -66, -93, -120, -147, -173, -200,
14, 44, 73, 103, 132, 162, 191, 221, -14, -44, -73, -103, -132, -162, -191, -221,
16, 48, 81, 113, 146, 178, 211, 243, -16, -48, -81, -113, -146, -178, -211, -243,
17, 53, 89, 125, 160, 196, 232, 268, -17, -53, -89, -125, -160, -196, -232, -268,
19, 58, 98, 137, 176, 215, 255, 294, -19, -58, -98, -137, -176, -215, -255, -294,
21, 64, 108, 151, 194, 237, 281, 324, -21, -64, -108, -151, -194, -237, -281, -324,
23, 71, 118, 166, 213, 261, 308, 356, -23, -71, -118, -166, -213, -261, -308, -356,
26, 78, 130, 182, 235, 287, 339, 391, -26, -78, -130, -182, -235, -287, -339, -391,
28, 86, 143, 201, 258, 316, 373, 431, -28, -86, -143, -201, -258, -316, -373, -431,
31, 94, 158, 221, 284, 347, 411, 474, -31, -94, -158, -221, -284, -347, -411, -474,
34, 104, 174, 244, 313, 383, 453, 523, -34, -104, -174, -244, -313, -383, -453, -523,
38, 115, 191, 268, 345, 422, 498, 575, -38, -115, -191, -268, -345, -422, -498, -575,
42, 126, 210, 294, 379, 463, 547, 631, -42, -126, -210, -294, -379, -463, -547, -631,
46, 139, 231, 324, 417, 510, 602, 695, -46, -139, -231, -324, -417, -510, -602, -695,
51, 153, 255, 357, 459, 561, 663, 765, -51, -153, -255, -357, -459, -561, -663, -765,
56, 168, 280, 392, 505, 617, 729, 841, -56, -168, -280, -392, -505, -617, -729, -841,
61, 185, 308, 432, 555, 679, 802, 926, -61, -185, -308, -432, -555, -679, -802, -926,
68, 204, 340, 476, 612, 748, 884, 1020, -68, -204, -340, -476, -612, -748, -884, -1020,
74, 224, 373, 523, 672, 822, 971, 1121, -74, -224, -373, -523, -672, -822, -971, -1121,
82, 246, 411, 575, 740, 904, 1069, 1233, -82, -246, -411, -575, -740, -904, -1069, -1233,
90, 271, 452, 633, 814, 995, 1176, 1357, -90, -271, -452, -633, -814, -995, -1176, -1357,
99, 298, 497, 696, 895, 1094, 1293, 1492, -99, -298, -497, -696, -895, -1094, -1293, -1492,
109, 328, 547, 766, 985, 1204, 1423, 1642, -109, -328, -547, -766, -985, -1204, -1423, -1642,
120, 361, 601, 842, 1083, 1324, 1564, 1805, -120, -361, -601, -842, -1083, -1324, -1564, -1805,
132, 397, 662, 927, 1192, 1457, 1722, 1987, -132, -397, -662, -927, -1192, -1457, -1722, -1987,
145, 437, 728, 1020, 1311, 1603, 1894, 2186, -145, -437, -728, -1020, -1311, -1603, -1894, -2186,
160, 480, 801, 1121, 1442, 1762, 2083, 2403, -160, -480, -801, -1121, -1442, -1762, -2083, -2403,
176, 529, 881, 1234, 1587, 1940, 2292, 2645, -176, -529, -881, -1234, -1587, -1940, -2292, -2645,
194, 582, 970, 1358, 1746, 2134, 2522, 2910, -194, -582, -970, -1358, -1746, -2134, -2522, -2910,
213, 640, 1066, 1493, 1920, 2347, 2773, 3200, -213, -640, -1066, -1493, -1920, -2347, -2773, -3200,
234, 704, 1173, 1643, 2112, 2582, 3051, 3521, -234, -704, -1173, -1643, -2112, -2582, -3051, -3521,
258, 774, 1291, 1807, 2324, 2840, 3357, 3873, -258, -774, -1291, -1807, -2324, -2840, -3357, -3873,
284, 852, 1420, 1988, 2556, 3124, 3692, 4260, -284, -852, -1420, -1988, -2556, -3124, -3692, -4260,
312, 937, 1561, 2186, 2811, 3436, 4060, 4685, -312, -937, -1561, -2186, -2811, -3436, -4060, -4685,
343, 1030, 1718, 2405, 3092, 3779, 4467, 5154, -343, -1030, -1718, -2405, -3092, -3779, -4467, -5154,
378, 1134, 1890, 2646, 3402, 4158, 4914, 5670, -378, -1134, -1890, -2646, -3402, -4158, -4914, -5670,
415, 1247, 2079, 2911, 3742, 4574, 5406, 6238, -415, -1247, -2079, -2911, -3742, -4574, -5406, -6238,
457, 1372, 2287, 3202, 4117, 5032, 5947, 6862, -457, -1372, -2287, -3202, -4117, -5032, -5947, -6862,
503, 1509, 2516, 3522, 4529, 5535, 6542, 7548, -503, -1509, -2516, -3522, -4529, -5535, -6542, -7548,
553, 1660, 2767, 3874, 4981, 6088, 7195, 8302, -553, -1660, -2767, -3874, -4981, -6088, -7195, -8302,
608, 1826, 3044, 4262, 5479, 6697, 7915, 9133, -608, -1826, -3044, -4262, -5479, -6697, -7915, -9133,
669, 2009, 3348, 4688, 6027, 7367, 8706, 10046, -669, -2009, -3348, -4688, -6027, -7367, -8706, -10046,
736, 2210, 3683, 5157, 6630, 8104, 9577, 11051, -736, -2210, -3683, -5157, -6630, -8104, -9577, -11051,
810, 2431, 4052, 5673, 7294, 8915, 10536, 12157, -810, -2431, -4052, -5673, -7294, -8915, -10536, -12157,
891, 2674, 4457, 6240, 8023, 9806, 11589, 13372, -891, -2674, -4457, -6240, -8023, -9806, -11589, -13372,
980, 2941, 4903, 6864, 8825, 10786, 12748, 14709, -980, -2941, -4903, -6864, -8825, -10786, -12748, -14709,
1078, 3236, 5393, 7551, 9708, 11866, 14023, 16181, -1078, -3236, -5393, -7551, -9708, -11866, -14023, -16181,
1186, 3559, 5933, 8306, 10679, 13052, 15426, 17799, -1186, -3559, -5933, -8306, -10679, -13052, -15426, -17799,
1305, 3915, 6526, 9136, 11747, 14357, 16968, 19578, -1305, -3915, -6526, -9136, -11747, -14357, -16968, -19578,
1435, 4307, 7179, 10051, 12922, 15794, 18666, 21538, -1435, -4307, -7179, -10051, -12922, -15794, -18666, -21538,
1579, 4738, 7896, 11055, 14214, 17373, 20531, 23690, -1579, -4738, -7896, -11055, -14214, -17373, -20531, -23690,
1737, 5212, 8686, 12161, 15636, 19111, 22585, 26060, -1737, -5212, -8686, -12161, -15636, -19111, -22585, -26060,
1911, 5733, 9555, 13377, 17200, 21022, 24844, 28666, -1911, -5733, -9555, -13377, -17200, -21022, -24844, -28666,
2102, 6306, 10511, 14715, 18920, 23124, 27329, 31533, -2102, -6306, -10511, -14715, -18920, -23124, -27329, -31533,
2312, 6937, 11562, 16187, 20812, 25437, 30062, 34687, -2312, -6937, -11562, -16187, -20812, -25437, -30062, -34687,
2543, 7631, 12718, 17806, 22893, 27981, 33068, 38156, -2543, -7631, -12718, -17806, -22893, -27981, -33068, -38156,
2798, 8394, 13990, 19586, 25183, 30779, 36375, 41971, -2798, -8394, -13990, -19586, -25183, -30779, -36375, -41971,
3077, 9233, 15389, 21545, 27700, 33856, 40012, 46168, -3077, -9233, -15389, -21545, -27700, -33856, -40012, -46168,
3385, 10157, 16928, 23700, 30471, 37243, 44014, 50786, -3385, -10157, -16928, -23700, -30471, -37243, -44014, -50786,
3724, 11172, 18621, 26069, 33518, 40966, 48415, 55863, -3724, -11172, -18621, -26069, -33518, -40966, -48415, -55863,
};
static readonly ushort[] AdpAdjust = {
0, 0, 0, 0, 32, 64, 96, 128, 0, 0, 0, 0, 32, 64, 96, 128,
0, 0, 0, 0, 48, 80, 112, 144, 0, 0, 0, 0, 48, 80, 112, 144,
16, 16, 16, 16, 64, 96, 128, 160, 16, 16, 16, 16, 64, 96, 128, 160,
32, 32, 32, 32, 80, 112, 144, 176, 32, 32, 32, 32, 80, 112, 144, 176,
48, 48, 48, 48, 96, 128, 160, 192, 48, 48, 48, 48, 96, 128, 160, 192,
64, 64, 64, 64, 112, 144, 176, 208, 64, 64, 64, 64, 112, 144, 176, 208,
80, 80, 80, 80, 128, 160, 192, 224, 80, 80, 80, 80, 128, 160, 192, 224,
96, 96, 96, 96, 144, 176, 208, 240, 96, 96, 96, 96, 144, 176, 208, 240,
112, 112, 112, 112, 160, 192, 224, 256, 112, 112, 112, 112, 160, 192, 224, 256,
128, 128, 128, 128, 176, 208, 240, 272, 128, 128, 128, 128, 176, 208, 240, 272,
144, 144, 144, 144, 192, 224, 256, 288, 144, 144, 144, 144, 192, 224, 256, 288,
160, 160, 160, 160, 208, 240, 272, 304, 160, 160, 160, 160, 208, 240, 272, 304,
176, 176, 176, 176, 224, 256, 288, 320, 176, 176, 176, 176, 224, 256, 288, 320,
192, 192, 192, 192, 240, 272, 304, 336, 192, 192, 192, 192, 240, 272, 304, 336,
208, 208, 208, 208, 256, 288, 320, 352, 208, 208, 208, 208, 256, 288, 320, 352,
224, 224, 224, 224, 272, 304, 336, 368, 224, 224, 224, 224, 272, 304, 336, 368,
240, 240, 240, 240, 288, 320, 352, 384, 240, 240, 240, 240, 288, 320, 352, 384,
256, 256, 256, 256, 304, 336, 368, 400, 256, 256, 256, 256, 304, 336, 368, 400,
272, 272, 272, 272, 320, 352, 384, 416, 272, 272, 272, 272, 320, 352, 384, 416,
288, 288, 288, 288, 336, 368, 400, 432, 288, 288, 288, 288, 336, 368, 400, 432,
304, 304, 304, 304, 352, 384, 416, 448, 304, 304, 304, 304, 352, 384, 416, 448,
320, 320, 320, 320, 368, 400, 432, 464, 320, 320, 320, 320, 368, 400, 432, 464,
336, 336, 336, 336, 384, 416, 448, 480, 336, 336, 336, 336, 384, 416, 448, 480,
352, 352, 352, 352, 400, 432, 464, 496, 352, 352, 352, 352, 400, 432, 464, 496,
368, 368, 368, 368, 416, 448, 480, 512, 368, 368, 368, 368, 416, 448, 480, 512,
384, 384, 384, 384, 432, 464, 496, 528, 384, 384, 384, 384, 432, 464, 496, 528,
400, 400, 400, 400, 448, 480, 512, 544, 400, 400, 400, 400, 448, 480, 512, 544,
416, 416, 416, 416, 464, 496, 528, 560, 416, 416, 416, 416, 464, 496, 528, 560,
432, 432, 432, 432, 480, 512, 544, 576, 432, 432, 432, 432, 480, 512, 544, 576,
448, 448, 448, 448, 496, 528, 560, 592, 448, 448, 448, 448, 496, 528, 560, 592,
464, 464, 464, 464, 512, 544, 576, 608, 464, 464, 464, 464, 512, 544, 576, 608,
480, 480, 480, 480, 528, 560, 592, 624, 480, 480, 480, 480, 528, 560, 592, 624,
496, 496, 496, 496, 544, 576, 608, 640, 496, 496, 496, 496, 544, 576, 608, 640,
512, 512, 512, 512, 560, 592, 624, 656, 512, 512, 512, 512, 560, 592, 624, 656,
528, 528, 528, 528, 576, 608, 640, 672, 528, 528, 528, 528, 576, 608, 640, 672,
544, 544, 544, 544, 592, 624, 656, 688, 544, 544, 544, 544, 592, 624, 656, 688,
560, 560, 560, 560, 608, 640, 672, 704, 560, 560, 560, 560, 608, 640, 672, 704,
576, 576, 576, 576, 624, 656, 688, 720, 576, 576, 576, 576, 624, 656, 688, 720,
592, 592, 592, 592, 640, 672, 704, 736, 592, 592, 592, 592, 640, 672, 704, 736,
608, 608, 608, 608, 656, 688, 720, 752, 608, 608, 608, 608, 656, 688, 720, 752,
624, 624, 624, 624, 672, 704, 736, 768, 624, 624, 624, 624, 672, 704, 736, 768,
640, 640, 640, 640, 688, 720, 752, 784, 640, 640, 640, 640, 688, 720, 752, 784,
656, 656, 656, 656, 704, 736, 768, 800, 656, 656, 656, 656, 704, 736, 768, 800,
672, 672, 672, 672, 720, 752, 784, 816, 672, 672, 672, 672, 720, 752, 784, 816,
688, 688, 688, 688, 736, 768, 800, 832, 688, 688, 688, 688, 736, 768, 800, 832,
704, 704, 704, 704, 752, 784, 816, 848, 704, 704, 704, 704, 752, 784, 816, 848,
720, 720, 720, 720, 768, 800, 832, 864, 720, 720, 720, 720, 768, 800, 832, 864,
736, 736, 736, 736, 784, 816, 848, 880, 736, 736, 736, 736, 784, 816, 848, 880,
752, 752, 752, 752, 800, 832, 864, 896, 752, 752, 752, 752, 800, 832, 864, 896,
768, 768, 768, 768, 816, 848, 880, 912, 768, 768, 768, 768, 816, 848, 880, 912,
784, 784, 784, 784, 832, 864, 896, 928, 784, 784, 784, 784, 832, 864, 896, 928,
800, 800, 800, 800, 848, 880, 912, 944, 800, 800, 800, 800, 848, 880, 912, 944,
816, 816, 816, 816, 864, 896, 928, 960, 816, 816, 816, 816, 864, 896, 928, 960,
832, 832, 832, 832, 880, 912, 944, 976, 832, 832, 832, 832, 880, 912, 944, 976,
848, 848, 848, 848, 896, 928, 960, 992, 848, 848, 848, 848, 896, 928, 960, 992,
864, 864, 864, 864, 912, 944, 976, 1008, 864, 864, 864, 864, 912, 944, 976, 1008,
880, 880, 880, 880, 928, 960, 992, 1024, 880, 880, 880, 880, 928, 960, 992, 1024,
896, 896, 896, 896, 944, 976, 1008, 1040, 896, 896, 896, 896, 944, 976, 1008, 1040,
912, 912, 912, 912, 960, 992, 1024, 1056, 912, 912, 912, 912, 960, 992, 1024, 1056,
928, 928, 928, 928, 976, 1008, 1040, 1072, 928, 928, 928, 928, 976, 1008, 1040, 1072,
944, 944, 944, 944, 992, 1024, 1056, 1088, 944, 944, 944, 944, 992, 1024, 1056, 1088,
960, 960, 960, 960, 1008, 1040, 1072, 1104, 960, 960, 960, 960, 1008, 1040, 1072, 1104,
976, 976, 976, 976, 1024, 1056, 1088, 1120, 976, 976, 976, 976, 1024, 1056, 1088, 1120,
992, 992, 992, 992, 1040, 1072, 1104, 1136, 992, 992, 992, 992, 1040, 1072, 1104, 1136,
1008, 1008, 1008, 1008, 1056, 1088, 1120, 1152, 1008, 1008, 1008, 1008, 1056, 1088, 1120, 1152,
1024, 1024, 1024, 1024, 1072, 1104, 1136, 1168, 1024, 1024, 1024, 1024, 1072, 1104, 1136, 1168,
1040, 1040, 1040, 1040, 1088, 1120, 1152, 1184, 1040, 1040, 1040, 1040, 1088, 1120, 1152, 1184,
1056, 1056, 1056, 1056, 1104, 1136, 1168, 1200, 1056, 1056, 1056, 1056, 1104, 1136, 1168, 1200,
1072, 1072, 1072, 1072, 1120, 1152, 1184, 1216, 1072, 1072, 1072, 1072, 1120, 1152, 1184, 1216,
1088, 1088, 1088, 1088, 1136, 1168, 1200, 1232, 1088, 1088, 1088, 1088, 1136, 1168, 1200, 1232,
1104, 1104, 1104, 1104, 1152, 1184, 1216, 1248, 1104, 1104, 1104, 1104, 1152, 1184, 1216, 1248,
1120, 1120, 1120, 1120, 1168, 1200, 1232, 1264, 1120, 1120, 1120, 1120, 1168, 1200, 1232, 1264,
1136, 1136, 1136, 1136, 1184, 1216, 1248, 1280, 1136, 1136, 1136, 1136, 1184, 1216, 1248, 1280,
1152, 1152, 1152, 1152, 1200, 1232, 1264, 1296, 1152, 1152, 1152, 1152, 1200, 1232, 1264, 1296,
1168, 1168, 1168, 1168, 1216, 1248, 1280, 1312, 1168, 1168, 1168, 1168, 1216, 1248, 1280, 1312,
1184, 1184, 1184, 1184, 1232, 1264, 1296, 1328, 1184, 1184, 1184, 1184, 1232, 1264, 1296, 1328,
1200, 1200, 1200, 1200, 1248, 1280, 1312, 1344, 1200, 1200, 1200, 1200, 1248, 1280, 1312, 1344,
1216, 1216, 1216, 1216, 1264, 1296, 1328, 1360, 1216, 1216, 1216, 1216, 1264, 1296, 1328, 1360,
1232, 1232, 1232, 1232, 1280, 1312, 1344, 1376, 1232, 1232, 1232, 1232, 1280, 1312, 1344, 1376,
1248, 1248, 1248, 1248, 1296, 1328, 1360, 1392, 1248, 1248, 1248, 1248, 1296, 1328, 1360, 1392,
1264, 1264, 1264, 1264, 1312, 1344, 1376, 1392, 1264, 1264, 1264, 1264, 1312, 1344, 1376, 1392,
1280, 1280, 1280, 1280, 1328, 1360, 1392, 1392, 1280, 1280, 1280, 1280, 1328, 1360, 1392, 1392,
1296, 1296, 1296, 1296, 1344, 1376, 1392, 1392, 1296, 1296, 1296, 1296, 1344, 1376, 1392, 1392,
1312, 1312, 1312, 1312, 1360, 1392, 1392, 1392, 1312, 1312, 1312, 1312, 1360, 1392, 1392, 1392,
1328, 1328, 1328, 1328, 1376, 1392, 1392, 1392, 1328, 1328, 1328, 1328, 1376, 1392, 1392, 1392,
1344, 1344, 1344, 1344, 1392, 1392, 1392, 1392, 1344, 1344, 1344, 1344, 1392, 1392, 1392, 1392,
1360, 1360, 1360, 1360, 1392, 1392, 1392, 1392, 1360, 1360, 1360, 1360, 1392, 1392, 1392, 1392,
1376, 1376, 1376, 1376, 1392, 1392, 1392, 1392, 1376, 1376, 1376, 1376, 1392, 1392, 1392, 1392,
};
}
}

View File

@@ -36,6 +36,11 @@ namespace GameRes.Formats.GameSystem
public override string Description { get { return "'GameSystem' CG image format"; } }
public override uint Signature { get { return 0; } }
public CgdFormat ()
{
Extensions = new string[] { "cgd", "crgb" };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (file.Signature != file.Length)
@@ -55,9 +60,9 @@ namespace GameRes.Formats.GameSystem
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 0x10;
var reader = new CgdReader (file, info);
var pixels = reader.Unpack();
return ImageData.CreateFlipped (info, reader.Format, null, pixels, reader.Stride);
return reader.Image;
}
public override void Write (Stream file, ImageData image)
@@ -66,29 +71,27 @@ namespace GameRes.Formats.GameSystem
}
}
internal sealed class CgdReader
internal sealed class CgdReader : BinaryImageDecoder
{
IBinaryStream m_input;
int m_width;
int m_height;
byte[] m_output;
public int Stride { get; private set; }
public PixelFormat Format { get { return PixelFormats.Bgr24; } }
public byte[] Data { get { return m_output; } }
public CgdReader (IBinaryStream input, ImageMetaData info)
public CgdReader (IBinaryStream input, ImageMetaData info) : base (input, info)
{
m_input = input;
m_width = (int)info.Width;
m_height = (int)info.Height;
Stride = 3 * m_width;
m_output = new byte[Stride * m_height];
Stride = 3 * (int)info.Width;
m_output = new byte[Stride * (int)info.Height];
}
public byte[] Unpack ()
protected override ImageData GetImageData ()
{
var pixels = Unpack();
return ImageData.CreateFlipped (Info, Format, null, pixels, Stride);
}
byte[] Unpack ()
{
m_input.Position = 0x10;
int dst = 0;
byte r = 0, g = 0, b = 0;
while (dst < m_output.Length)
@@ -131,7 +134,7 @@ namespace GameRes.Formats.GameSystem
return m_output;
}
static readonly byte[,] ColorTable = InitColorTable();
internal static readonly byte[,] ColorTable = InitColorTable();
private static byte[,] InitColorTable ()
{

View File

@@ -32,6 +32,7 @@ namespace GameRes.Formats.GameSystem
{
internal class ChrMetaData : ImageMetaData
{
public uint DataOffset;
public int RgbSize;
}
@@ -65,14 +66,14 @@ namespace GameRes.Formats.GameSystem
OffsetY = y,
BPP = 32,
RgbSize = rgb_size,
DataOffset = 0x20,
};
}
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);
return reader.Image;
}
public override void Write (Stream file, ImageData image)
@@ -81,9 +82,8 @@ namespace GameRes.Formats.GameSystem
}
}
internal sealed class ChrReader
internal class ChrReader : BinaryImageDecoder
{
IBinaryStream m_input;
ChrMetaData m_info;
byte[] m_output;
int m_stride;
@@ -91,14 +91,19 @@ namespace GameRes.Formats.GameSystem
public byte[] Data { get { return m_output; } }
public int Stride { get { return m_stride; } }
public ChrReader (IBinaryStream input, ChrMetaData info)
public ChrReader (IBinaryStream input, ChrMetaData info) : base (input, info)
{
m_input = input;
m_info = info;
m_stride = (int)m_info.Width * 4;
m_output = new byte[m_stride * (int)m_info.Height];
}
protected override ImageData GetImageData ()
{
var pixels = Unpack();
return ImageData.CreateFlipped (Info, PixelFormats.Bgra32, null, pixels, Stride);
}
public byte[] Unpack ()
{
UnpackBaseline();
@@ -114,7 +119,7 @@ namespace GameRes.Formats.GameSystem
public byte[] UnpackBaseline ()
{
m_input.Position = 0x20;
m_input.Position = m_info.DataOffset;
UnpackRgb ((int)m_info.Height);
return m_output;
}

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.2.29.1269")]
[assembly: AssemblyFileVersion ("1.2.29.1269")]
[assembly: AssemblyVersion ("1.2.30.1292")]
[assembly: AssemblyFileVersion ("1.2.30.1292")]

View File

@@ -2,7 +2,7 @@
//! \date Mon Jun 15 04:03:18 2015
//! \brief QLIE engine archives implementation.
//
// Copyright (C) 2015 by morkt
// Copyright (C) 2015-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
@@ -32,6 +32,7 @@ using System.Linq;
using GameRes.Utility;
using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
using GameRes.Formats.Borland;
namespace GameRes.Formats.Qlie
{
@@ -136,7 +137,7 @@ namespace GameRes.Formats.Qlie
use_pack_keyfile = key_file != null;
// currently, user is prompted to choose encryption scheme only if there's 'key.fkey' file found.
if (use_pack_keyfile)
arc_key = QueryEncryption();
arc_key = QueryEncryption (file);
// use_pack_keyfile = null != arc_key;
var key_data = file.View.ReadBytes (file.MaxOffset-0x41C, 0x100);
@@ -423,10 +424,20 @@ namespace GameRes.Formats.Qlie
return new GUI.WidgetQLIE();
}
byte[] QueryEncryption ()
byte[] QueryEncryption (ArcView file)
{
var options = Query<QlieOptions> (arcStrings.ArcEncryptedNotice);
return options.GameKeyData;
var title = FormatCatalog.Instance.LookupGame (file.Name, @"..\*.exe");
byte[] key = null;
if (!string.IsNullOrEmpty (title))
key = GetKeyData (title);
if (null == key)
key = GuessKeyData (file.Name);
if (null == key)
{
var options = Query<QlieOptions> (arcStrings.ArcEncryptedNotice);
key = options.GameKeyData;
}
return key;
}
static byte[] GetKeyData (string scheme)
@@ -457,5 +468,46 @@ namespace GameRes.Formats.Qlie
}
return null;
}
byte[] GuessKeyData (string arc_name)
{
if (VFS.IsVirtual)
return null;
// XXX add button to query dialog like with CatSystem?
var pattern = VFS.CombinePath (VFS.GetDirectoryName (arc_name), @"..\*.exe");
foreach (var file in VFS.GetFiles (pattern))
{
try
{
var key = GetKeyDataFromExe (file.Name);
if (key != null)
return key;
}
catch { /* ignore errors */ }
}
return null;
}
public static byte[] GetKeyDataFromExe (string filename)
{
using (var exe = new ExeFile.ResourceAccessor (filename))
{
var tform = exe.GetResource ("TFORM1", "#10");
if (null == tform || !tform.AsciiEqual (0, "TPF0"))
return null;
using (var input = new BinMemoryStream (tform))
{
var deserializer = new DelphiDeserializer (input);
var form = deserializer.Deserialize();
var image = form.Contents.FirstOrDefault (n => n.Name == "IconKeyImage");
if (null == image)
return null;
var icon = image.Props["Picture.Data"] as byte[];
if (null == icon || icon.Length < 0x106 || !icon.AsciiEqual (0, "\x05TIcon"))
return null;
return new CowArray<byte> (icon, 6, 0x100).ToArray();
}
}
}
}
}

View File

@@ -0,0 +1,147 @@
//! \file DelphiDeserializer.cs
//! \date Wed Feb 22 15:40:33 2017
//! \brief Borland Delphi binary data deserializer.
//
// 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.Collections;
using System.Collections.Generic;
using System.Text;
namespace GameRes.Formats.Borland
{
public sealed class DelphiDeserializer
{
IBinaryStream m_input;
public Encoding Encoding { get; set; }
public DelphiDeserializer (IBinaryStream input)
{
m_input = input;
Encoding = Encodings.cp932;
}
public DelphiObject Deserialize ()
{
if (m_input.ReadUInt32() != 0x30465054) // 'TPF0'
return null;
return DeserializeNode();
}
DelphiObject DeserializeNode ()
{
int type_len = m_input.ReadByte();
if (type_len <= 0)
return null;
var node = new DelphiObject();
node.Type = ReadString (type_len);
node.Name = ReadString();
int key_length;
while ((key_length = m_input.ReadUInt8()) > 0)
{
var key = ReadString (key_length);
node.Props[key] = ReadValue();
}
DelphiObject child;
while ((child = DeserializeNode()) != null)
{
node.Contents.Add (child);
}
return node;
}
object ReadValue ()
{
int type = m_input.ReadUInt8();
switch (type)
{
case 2: return (int)m_input.ReadUInt8();
case 3: return (int)m_input.ReadUInt16();
case 5: return ReadLongDouble();
case 6:
case 7: return ReadString();
case 8:
case 9: return true;
case 10: return ReadByteString();
case 11: return ReadStringArray();
case 18: return ReadUnicodeString();
default: throw new System.NotImplementedException();
}
}
string ReadString ()
{
return ReadString (m_input.ReadUInt8());
}
string ReadString (int length)
{
return m_input.ReadCString (length, Encoding);
}
string ReadUnicodeString ()
{
int length = m_input.ReadInt32();
if (length < 0)
throw new InvalidFormatException();
if (0 == length)
return "";
var bytes = m_input.ReadBytes (length * 2);
return Encoding.Unicode.GetString (bytes);
}
byte[] ReadByteString ()
{
int length = m_input.ReadInt32();
if (length < 0)
throw new InvalidFormatException();
if (0 == length)
return new byte[0];
return m_input.ReadBytes (length);
}
IList<string> ReadStringArray ()
{
var list = new List<string>();
int length;
while ((length = m_input.ReadUInt8()) > 0)
{
list.Add (ReadString (length));
}
return list;
}
object ReadLongDouble ()
{
return m_input.ReadBytes (10); // long double deserialization not implemented
}
}
public class DelphiObject
{
public string Type;
public string Name;
public IDictionary Props = new Hashtable();
public IList<DelphiObject> Contents = new List<DelphiObject>();
}
}

View File

@@ -0,0 +1,227 @@
//! \file ArcKOE.cs
//! \date Sun Feb 19 14:58:05 2017
//! \brief RealLive audio archive.
//
// 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.Text;
namespace GameRes.Formats.RealLive
{
internal class KoeEntry : PackedEntry
{
public ushort SampleCount;
}
internal class KoeArchive : ArcFile
{
public readonly WaveFormat Format;
public KoeArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, WaveFormat format)
: base (arc, impl, dir)
{
Format = format;
}
}
[Export(typeof(ArchiveFormat))]
public class KoeOpener : ArchiveFormat
{
public override string Tag { get { return "KOE"; } }
public override string Description { get { return "RealLive engine audio archive"; } }
public override uint Signature { get { return 0x50454F4B; } } // 'KOEPAC'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "AC\0"))
return null;
int count = file.View.ReadInt32 (0x10);
if (!IsSaneCount (count))
return null;
uint data_offset = file.View.ReadUInt32 (0x14);
uint sample_rate = file.View.ReadUInt32 (0x18);
if (0 == sample_rate)
sample_rate = 22050;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
uint index_offset = 0x20;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
int id = file.View.ReadUInt16 (index_offset);
var entry = new KoeEntry
{
Name = string.Format ("{0}#{1:D4}.wav", base_name, id),
Type = "audio",
SampleCount = file.View.ReadUInt16 (index_offset+2),
Offset = file.View.ReadUInt32 (index_offset+4),
IsPacked = true,
};
entry.Size = entry.SampleCount * 2u;
index_offset += 8;
dir.Add (entry);
}
var format = new WaveFormat
{
FormatTag = 1,
Channels = 2,
SamplesPerSecond = sample_rate,
AverageBytesPerSecond = 4 * sample_rate,
BlockAlign = 4,
BitsPerSample = 16,
};
return new KoeArchive (file, this, dir, format);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var kent = (KoeEntry)entry;
var karc = (KoeArchive)arc;
var table = new ushort[kent.SampleCount];
uint packed_size = 0;
var offset = kent.Offset;
for (int i = 0; i < table.Length; ++i)
{
table[i] = arc.File.View.ReadUInt16 (offset);
offset += 2;
packed_size += table[i];
}
int total_size = kent.SampleCount * 0x1000;
var wav = new MemoryStream (total_size);
WaveAudio.WriteRiffHeader (wav, karc.Format, (uint)total_size);
using (var output = new BinaryWriter (wav, Encoding.ASCII, true))
using (var input = arc.File.CreateStream (offset, packed_size))
{
foreach (ushort chunk_length in table)
{
if (0 == chunk_length)
{
output.Seek (0x1000, SeekOrigin.Current);
}
else if (0x400 == chunk_length)
{
for (int i = 0; i < 0x400; ++i)
{
ushort sample = SampleTable[input.ReadUInt8()];
output.Write (sample);
output.Write (sample);
}
}
else
{
byte src = 0;
for (int i = 0; i < 0x400; i += 2)
{
byte bits = input.ReadUInt8();
if (0 != ((bits + 1) & 0xF))
{
src -= AdjustTable[bits & 0xF];
}
else
{
int idx = bits >> 4;
bits = input.ReadUInt8();
idx |= (bits << 4) & 0xF0;
src -= AdjustTable[idx];
}
ushort sample = SampleTable[src];
output.Write (sample);
output.Write (sample);
bits >>= 4;
if (0 != ((bits + 1) & 0xF))
src -= AdjustTable[bits & 0xF];
else
src -= AdjustTable[input.ReadUInt8()];
sample = SampleTable[src];
output.Write (sample);
output.Write (sample);
}
}
}
}
kent.UnpackedSize = (uint)wav.Length;
wav.Position = 0;
return wav;
}
static readonly ushort[] SampleTable = new ushort[] {
0x8000, 0x81FF, 0x83F9, 0x85EF, 0x87E1, 0x89CF, 0x8BB9, 0x8D9F,
0x8F81, 0x915F, 0x9339, 0x950F, 0x96E1, 0x98AF, 0x9A79, 0x9C3F,
0x9E01, 0x9FBF, 0xA179, 0xA32F, 0xA4E1, 0xA68F, 0xA839, 0xA9DF,
0xAB81, 0xAD1F, 0xAEB9, 0xB04F, 0xB1E1, 0xB36F, 0xB4F9, 0xB67F,
0xB801, 0xB97F, 0xBAF9, 0xBC6F, 0xBDE1, 0xBF4F, 0xC0B9, 0xC21F,
0xC381, 0xC4DF, 0xC639, 0xC78F, 0xC8E1, 0xCA2F, 0xCB79, 0xCCBF,
0xCE01, 0xCF3F, 0xD079, 0xD1AF, 0xD2E1, 0xD40F, 0xD539, 0xD65F,
0xD781, 0xD89F, 0xD9B9, 0xDACF, 0xDBE1, 0xDCEF, 0xDDF9, 0xDEFF,
0xE001, 0xE0FF, 0xE1F9, 0xE2EF, 0xE3E1, 0xE4CF, 0xE5B9, 0xE69F,
0xE781, 0xE85F, 0xE939, 0xEA0F, 0xEAE1, 0xEBAF, 0xEC79, 0xED3F,
0xEE01, 0xEEBF, 0xEF79, 0xF02F, 0xF0E1, 0xF18F, 0xF239, 0xF2DF,
0xF381, 0xF41F, 0xF4B9, 0xF54F, 0xF5E1, 0xF66F, 0xF6F9, 0xF77F,
0xF801, 0xF87F, 0xF8F9, 0xF96F, 0xF9E1, 0xFA4F, 0xFAB9, 0xFB1F,
0xFB81, 0xFBDF, 0xFC39, 0xFC8F, 0xFCE1, 0xFD2F, 0xFD79, 0xFDBF,
0xFE01, 0xFE3F, 0xFE79, 0xFEAF, 0xFEE1, 0xFF0F, 0xFF39, 0xFF5F,
0xFF81, 0xFF9F, 0xFFB9, 0xFFCF, 0xFFE1, 0xFFEF, 0xFFF9, 0xFFFF,
0x0000, 0x0001, 0x0007, 0x0011, 0x001F, 0x0031, 0x0047, 0x0061,
0x007F, 0x00A1, 0x00C7, 0x00F1, 0x011F, 0x0151, 0x0187, 0x01C1,
0x01FF, 0x0241, 0x0287, 0x02D1, 0x031F, 0x0371, 0x03C7, 0x0421,
0x047F, 0x04E1, 0x0547, 0x05B1, 0x061F, 0x0691, 0x0707, 0x0781,
0x07FF, 0x0881, 0x0907, 0x0991, 0x0A1F, 0x0AB1, 0x0B47, 0x0BE1,
0x0C7F, 0x0D21, 0x0DC7, 0x0E71, 0x0F1F, 0x0FD1, 0x1087, 0x1141,
0x11FF, 0x12C1, 0x1387, 0x1451, 0x151F, 0x15F1, 0x16C7, 0x17A1,
0x187F, 0x1961, 0x1A47, 0x1B31, 0x1C1F, 0x1D11, 0x1E07, 0x1F01,
0x1FFF, 0x2101, 0x2207, 0x2311, 0x241F, 0x2531, 0x2647, 0x2761,
0x287F, 0x29A1, 0x2AC7, 0x2BF1, 0x2D1F, 0x2E51, 0x2F87, 0x30C1,
0x31FF, 0x3341, 0x3487, 0x35D1, 0x371F, 0x3871, 0x39C7, 0x3B21,
0x3C7F, 0x3DE1, 0x3F47, 0x40B1, 0x421F, 0x4391, 0x4507, 0x4681,
0x47FF, 0x4981, 0x4B07, 0x4C91, 0x4E1F, 0x4FB1, 0x5147, 0x52E1,
0x547F, 0x5621, 0x57C7, 0x5971, 0x5B1F, 0x5CD1, 0x5E87, 0x6041,
0x61FF, 0x63C1, 0x6587, 0x6751, 0x691F, 0x6AF1, 0x6CC7, 0x6EA1,
0x707F, 0x7261, 0x7447, 0x7631, 0x781F, 0x7A11, 0x7C07, 0x7FFF
};
static readonly byte[] AdjustTable = new byte[] {
0x00, 0xFF, 0x01, 0xFE, 0x02, 0xFD, 0x03, 0xFC, 0x04, 0xFB, 0x05, 0xFA, 0x06, 0xF9, 0x07, 0xF8,
0x08, 0xF7, 0x09, 0xF6, 0x0A, 0xF5, 0x0B, 0xF4, 0x0C, 0xF3, 0x0D, 0xF2, 0x0E, 0xF1, 0x0F, 0xF0,
0x10, 0xEF, 0x11, 0xEE, 0x12, 0xED, 0x13, 0xEC, 0x14, 0xEB, 0x15, 0xEA, 0x16, 0xE9, 0x17, 0xE8,
0x18, 0xE7, 0x19, 0xE6, 0x1A, 0xE5, 0x1B, 0xE4, 0x1C, 0xE3, 0x1D, 0xE2, 0x1E, 0xE1, 0x1F, 0xE0,
0x20, 0xDF, 0x21, 0xDE, 0x22, 0xDD, 0x23, 0xDC, 0x24, 0xDB, 0x25, 0xDA, 0x26, 0xD9, 0x27, 0xD8,
0x28, 0xD7, 0x29, 0xD6, 0x2A, 0xD5, 0x2B, 0xD4, 0x2C, 0xD3, 0x2D, 0xD2, 0x2E, 0xD1, 0x2F, 0xD0,
0x30, 0xCF, 0x31, 0xCE, 0x32, 0xCD, 0x33, 0xCC, 0x34, 0xCB, 0x35, 0xCA, 0x36, 0xC9, 0x37, 0xC8,
0x38, 0xC7, 0x39, 0xC6, 0x3A, 0xC5, 0x3B, 0xC4, 0x3C, 0xC3, 0x3D, 0xC2, 0x3E, 0xC1, 0x3F, 0xC0,
0x40, 0xBF, 0x41, 0xBE, 0x42, 0xBD, 0x43, 0xBC, 0x44, 0xBB, 0x45, 0xBA, 0x46, 0xB9, 0x47, 0xB8,
0x48, 0xB7, 0x49, 0xB6, 0x4A, 0xB5, 0x4B, 0xB4, 0x4C, 0xB3, 0x4D, 0xB2, 0x4E, 0xB1, 0x4F, 0xB0,
0x50, 0xAF, 0x51, 0xAE, 0x52, 0xAD, 0x53, 0xAC, 0x54, 0xAB, 0x55, 0xAA, 0x56, 0xA9, 0x57, 0xA8,
0x58, 0xA7, 0x59, 0xA6, 0x5A, 0xA5, 0x5B, 0xA4, 0x5C, 0xA3, 0x5D, 0xA2, 0x5E, 0xA1, 0x5F, 0xA0,
0x60, 0x9F, 0x61, 0x9E, 0x62, 0x9D, 0x63, 0x9C, 0x64, 0x9B, 0x65, 0x9A, 0x66, 0x99, 0x67, 0x98,
0x68, 0x97, 0x69, 0x96, 0x6A, 0x95, 0x6B, 0x94, 0x6C, 0x93, 0x6D, 0x92, 0x6E, 0x91, 0x6F, 0x90,
0x70, 0x8F, 0x71, 0x8E, 0x72, 0x8D, 0x73, 0x8C, 0x74, 0x8B, 0x75, 0x8A, 0x76, 0x89, 0x77, 0x88,
0x78, 0x87, 0x79, 0x86, 0x7A, 0x85, 0x7B, 0x84, 0x7C, 0x83, 0x7D, 0x82, 0x7E, 0x81, 0x7F, 0x80
};
}
}

View File

Binary file not shown.

View File

@@ -2,7 +2,7 @@
//! \date Fri Apr 10 03:10:42 2015
//! \brief ShiinaRio engine archive format.
//
// Copyright (C) 2015-2016 by morkt
// Copyright (C) 2015-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
@@ -83,14 +83,18 @@ namespace GameRes.Formats.ShiinaRio // 椎名里緒
if (!file.View.AsciiEqual (4, " 1."))
return null;
int version = file.View.ReadByte (7) - 0x30;
if (version < 1 || version > 7)
return null;
version = 100 + version * 10;
if (170 != version && 150 != version && 140 != version && 130 != version && 120 != version)
throw new NotSupportedException ("Not supported WARC version");
uint index_offset = 0xf182ad82u ^ file.View.ReadUInt32 (8);
uint index_offset = 0xF182AD82u ^ file.View.ReadUInt32 (8);
if (index_offset >= file.MaxOffset)
return null;
var scheme = QueryEncryption (file.Name);
EncryptionScheme scheme;
if (version > 110)
scheme = QueryEncryption (file.Name);
else
scheme = EncryptionScheme.Warc110;
if (null == scheme)
return null;
var decoder = new Decoder (version, scheme);
@@ -111,7 +115,7 @@ namespace GameRes.Formats.ShiinaRio // 椎名里緒
var zindex = new MemoryStream (enc_index, 8, (int)index_length-8);
index = new ZLibStream (zindex, CompressionMode.Decompress);
}
else
else if (version >= 120)
{
var unpacked = new byte[max_index_len];
index_length = UnpackRNG (enc_index, 0, index_length, unpacked);
@@ -119,6 +123,10 @@ namespace GameRes.Formats.ShiinaRio // 椎名里緒
return null;
index = new MemoryStream (unpacked, 0, (int)index_length);
}
else
{
index = new MemoryStream (enc_index, 0, (int)index_length);
}
using (var header = new BinaryReader (index))
{
byte[] name_buf = new byte[decoder.EntryNameSize];
@@ -151,15 +159,14 @@ namespace GameRes.Formats.ShiinaRio // 椎名里緒
var wentry = entry as WarcEntry;
if (null == warc || null == wentry || entry.Size < 8)
return arc.File.CreateStream (entry.Offset, entry.Size);
var enc_data = new byte[entry.Size];
if (entry.Size != arc.File.View.Read (entry.Offset, enc_data, 0, entry.Size))
return Stream.Null;
var enc_data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
if (enc_data.Length <= 8)
return new BinMemoryStream (enc_data, entry.Name);
uint sig = LittleEndian.ToUInt32 (enc_data, 0);
uint unpacked_size = LittleEndian.ToUInt32 (enc_data, 4);
sig ^= (unpacked_size ^ 0x82AD82) & 0xffffff;
if (entry.Size > 8)
if (warc.Decoder.WarcVersion > 110)
{
sig ^= (unpacked_size ^ 0x82AD82) & 0xFFFFFF;
if (0 != (wentry.Flags & 0x80000000u)) // encrypted entry
warc.Decoder.Decrypt (enc_data, 8, entry.Size-8);
if (warc.Decoder.ExtraCrypt != null)
@@ -185,10 +192,13 @@ namespace GameRes.Formats.ShiinaRio // 椎名里緒
{
unpacked = new byte[unpacked_size];
unpack (enc_data, unpacked);
if (0 != (wentry.Flags & 0x40000000))
warc.Decoder.Decrypt2 (unpacked, 0, (uint)unpacked.Length);
if (warc.Decoder.ExtraCrypt != null)
warc.Decoder.ExtraCrypt.Decrypt (unpacked, 0, (uint)unpacked.Length, 0x204);
if (warc.Decoder.WarcVersion > 110)
{
if (0 != (wentry.Flags & 0x40000000))
warc.Decoder.Decrypt2 (unpacked, 0, (uint)unpacked.Length);
if (warc.Decoder.ExtraCrypt != null)
warc.Decoder.ExtraCrypt.Decrypt (unpacked, 0, (uint)unpacked.Length, 0x204);
}
}
return new BinMemoryStream (unpacked, entry.Name);
}

View File

@@ -52,11 +52,9 @@ namespace GameRes.Formats.ShiinaRio
public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{
using (var reader = new Reader (stream, (int)info.Width, (int)info.Height))
{
reader.Unpack ();
return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data);
}
var reader = new Reader (stream, (int)info.Width, (int)info.Height);
reader.Unpack();
return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data, reader.Stride);
}
public override void Write (Stream file, ImageData image)
@@ -64,13 +62,14 @@ namespace GameRes.Formats.ShiinaRio
throw new System.NotImplementedException ("Mi4Format.Write not implemented");
}
internal sealed class Reader : IDisposable
internal sealed class Reader
{
IBinaryStream m_input;
byte[] m_output;
int m_stride;
public byte[] Data { get { return m_output; } }
public int Stride { get { return m_stride; } }
public Reader (IBinaryStream file, int width, int height)
{
@@ -79,6 +78,14 @@ namespace GameRes.Formats.ShiinaRio
m_output = new byte[m_stride*height];
}
public void Unpack ()
{
m_input.Position = 0x10;
m_bit_count = 0;
LoadBits();
UnpackV2();
}
int m_bit_count;
uint m_bits;
@@ -107,19 +114,101 @@ namespace GameRes.Formats.ShiinaRio
uint GetBits (int count)
{
uint bits = 0;
while (count --> 0)
int avail_bits = Math.Min (count, m_bit_count);
uint bits = m_bits >> (32 - avail_bits);
m_bits <<= avail_bits;
m_bit_count -= avail_bits;
count -= avail_bits;
if (0 == m_bit_count)
{
bits = (bits << 1) | GetBit();
LoadBits();
if (count > 0)
{
bits = bits << count | m_bits >> (32 - count);
m_bits <<= count;
m_bit_count -= count;
}
}
return bits;
}
public void Unpack ()
void UnpackV1 ()
{
int dst = 0;
byte b = 0, g = 0, r = 0;
while (dst < m_output.Length)
{
if (GetBit() == 0)
{
if (GetBit() != 0)
{
b = m_input.ReadUInt8();
g = m_input.ReadUInt8();
r = m_input.ReadUInt8();
}
else if (GetBit() != 0)
{
byte v = (byte)GetBits (2);
if (3 == v)
{
b = m_output[dst - m_stride];
g = m_output[dst - m_stride + 1];
r = m_output[dst - m_stride + 2];
}
else
{
b += (byte)(v - 1);
g += (byte)(GetBits(2) - 1);
r += (byte)(GetBits(2) - 1);
}
}
else if (GetBit() != 0)
{
byte v = (byte)(GetBits(3));
if (7 == v)
{
b = m_output[dst - m_stride + 3];
g = m_output[dst - m_stride + 4];
r = m_output[dst - m_stride + 5];
}
else
{
b += (byte)(v - 3);
g += (byte)(GetBits(3) - 3);
r += (byte)(GetBits(3) - 3);
}
}
else if (GetBit() != 0)
{
byte v = (byte)GetBits (4);
if (0xF == v)
{
b = m_output[dst - m_stride - 3];
g = m_output[dst - m_stride - 2];
r = m_output[dst - m_stride - 1];
}
else
{
b += (byte)(v - 7);
g += (byte)(GetBits(4) - 7);
r += (byte)(GetBits(4) - 7);
}
}
else
{
b += (byte)(GetBits(5) - 15);
g += (byte)(GetBits(5) - 15);
r += (byte)(GetBits(5) - 15);
}
}
m_output[dst++] = b;
m_output[dst++] = g;
m_output[dst++] = r;
}
}
void UnpackV2 ()
{
m_input.Position = 0x10;
m_bit_count = 0;
LoadBits();
int dst = 0;
byte b = 0, g = 0, r = 0;
while (dst < m_output.Length)
@@ -207,13 +296,6 @@ namespace GameRes.Formats.ShiinaRio
m_output[dst++] = r;
}
}
#region IDisposable Members
public void Dispose ()
{
GC.SuppressFinalize (this);
}
#endregion
}
}
}

View File

@@ -42,6 +42,8 @@ namespace GameRes.Formats.ShiinaRio
public byte[] DecodeBin;
public IByteArray ShiinaImage;
public IDecryptExtra ExtraCrypt;
public static readonly EncryptionScheme Warc110 = new EncryptionScheme { EntryNameSize = 0x10 };
}
public interface IByteArray
@@ -136,7 +138,7 @@ namespace GameRes.Formats.ShiinaRio
void DoEncryption (byte[] data, int index, uint data_length, ContentEncryptor encryptor)
{
if (data_length < 3)
if (data_length < 3 || WarcVersion < 120)
return;
uint effective_length = Math.Min (data_length, 1024u);
int a, b;

View File

@@ -0,0 +1,201 @@
//! \file ArcCG.cs
//! \date Tue Feb 16 02:48:55 2016
//! \brief Software House Parsley CG archive.
//
// Copyright (C) 2016-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.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Parsley
{
[Export(typeof(ArchiveFormat))]
public class CgOpener : ArchiveFormat
{
public override string Tag { get { return "CG/PARSLEY/2"; } }
public override string Description { get { return "Software House Parsley CG archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public CgOpener ()
{
Extensions = new string[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
return null;
int first_offset = file.View.ReadInt32 (0x2C);
if (12 + count*0x28 != first_offset)
return null;
uint index_offset = 0xC;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 0x20);
if (string.IsNullOrWhiteSpace (name))
return null;
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0x20);
entry.Size = file.View.ReadUInt32 (index_offset+0x24);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 0x28;
}
return new ArcFile (file, this, dir);
}
}
[Export(typeof(ArchiveFormat))]
public class CgV1Opener : ArchiveFormat
{
public override string Tag { get { return "CG/PARSLEY/1"; } }
public override string Description { get { return "Software House Parsley CG archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public CgV1Opener ()
{
Extensions = new string[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (!IsSaneCount (count))
return null;
var arc_name = Path.GetFileName (file.Name);
bool is_cg = arc_name == "CG";
using (var index = file.CreateStream())
{
index.Position = 4;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = index.ReadCString();
if (string.IsNullOrWhiteSpace (name) || name.Length > 0x100)
return null;
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = index.ReadUInt32();
if (entry.Offset >= file.MaxOffset || entry.Offset < index.Position)
return null;
dir.Add (entry);
}
for (int i = 0; i < count; ++i)
{
var entry = dir[i];
long next_offset = i+1 < count ? dir[i+1].Offset : file.MaxOffset;
entry.Size = (uint)(next_offset - entry.Offset);
if (is_cg)
entry.Type = "image";
}
if (is_cg)
{
var palette_entry = dir.FirstOrDefault (e => e.Name.Equals ("Palette", StringComparison.InvariantCultureIgnoreCase));
if (palette_entry != null && 1 == file.View.ReadByte (palette_entry.Offset+8))
{
var palette = ImageFormat.ReadPalette (file, palette_entry.Offset+9, 0x100, PaletteFormat.Rgb);
return new CgArchive (file, this, dir, palette);
}
}
return new ArcFile (file, this, dir);
}
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
int type = arc.File.View.ReadByte (entry.Offset+8);
if (type > 1)
return base.OpenImage (arc, entry);
var info = new CgMetaData {
Width = arc.File.View.ReadUInt32 (entry.Offset),
Height = arc.File.View.ReadUInt32 (entry.Offset+4),
BPP = 8,
HasPalette = type == 1,
};
var cg_arc = arc as CgArchive;
if (cg_arc != null)
info.DefaultPalette = cg_arc.DefaultPalette;
var input = arc.File.CreateStream (entry.Offset, entry.Size);
return new CgDecoder (input, info);
}
}
internal class CgMetaData : ImageMetaData
{
public bool HasPalette;
public BitmapPalette DefaultPalette;
}
internal class CgArchive : ArcFile
{
public readonly BitmapPalette DefaultPalette;
public CgArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, BitmapPalette palette)
: base (arc, impl, dir)
{
DefaultPalette = palette;
}
}
internal class CgDecoder : BinaryImageDecoder
{
bool m_has_palette;
BitmapPalette m_palette;
public CgDecoder (IBinaryStream input, CgMetaData info) : base (input, info)
{
m_has_palette = info.HasPalette;
m_palette = info.DefaultPalette;
}
protected override ImageData GetImageData ()
{
m_input.Position = 9;
if (m_has_palette)
ReadPalette();
var pixels = m_input.ReadBytes ((int)(Info.Width * Info.Height));
if (m_palette != null)
return ImageData.Create (Info, PixelFormats.Indexed8, m_palette, pixels);
else
return ImageData.Create (Info, PixelFormats.Gray8, null, pixels);
}
void ReadPalette ()
{
m_palette = ImageFormat.ReadPalette (m_input.AsStream, 0x100, PaletteFormat.Rgb);
m_input.Position = 0x20909;
}
}
}

View File

@@ -1,8 +1,8 @@
//! \file ArcCG.cs
//! \date Tue Feb 16 02:48:55 2016
//! \brief Software House Parsley CG archive.
//! \file ArcDAT.cs
//! \date Sat Feb 25 02:30:54 2017
//! \brief Valkyria resource archive.
//
// Copyright (C) 2016 by morkt
// 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
@@ -23,50 +23,42 @@
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Parsley
namespace GameRes.Formats.Valkyria
{
[Export(typeof(ArchiveFormat))]
public class CgOpener : ArchiveFormat
public class DatOpener : ArchiveFormat
{
public override string Tag { get { return "CG/PARSLEY"; } }
public override string Description { get { return "Software House Parsley CG archive"; } }
public override string Tag { get { return "DAT/VALKYRIA"; } }
public override string Description { get { return "Valkyria resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public CgOpener ()
{
Extensions = new string[] { "" };
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
uint index_size = file.View.ReadUInt32 (0);
if (0 == index_size || index_size >= file.MaxOffset)
return null;
int first_offset = file.View.ReadInt32 (0x2C);
if (12 + count*0x28 != first_offset)
int count = (int)index_size / 0x10C;
if (index_size != (uint)count * 0x10Cu || !IsSaneCount (count))
return null;
uint index_offset = 0xC;
uint index_offset = 4;
long base_offset = index_offset + index_size;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var name = file.View.ReadString (index_offset, 0x20);
if (string.IsNullOrWhiteSpace (name))
return null;
var name = file.View.ReadString (index_offset, 0x104);
index_offset += 0x104;
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset+0x20);
entry.Size = file.View.ReadUInt32 (index_offset+0x24);
entry.Offset = base_offset + file.View.ReadUInt32 (index_offset);
entry.Size = file.View.ReadUInt32 (index_offset+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
index_offset += 8;
dir.Add (entry);
index_offset += 0x28;
}
return new ArcFile (file, this, dir);
}

View File

@@ -0,0 +1,82 @@
//! \file ImageMAL.cs
//! \date Sat Feb 25 05:13:05 2017
//! \brief Valkyria mask 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;
namespace GameRes.Formats.Valkyria
{
[Export(typeof(ImageFormat))]
public class MalFormat : ImageFormat
{
public override string Tag { get { return "MAL"; } }
public override string Description { get { return "Valkyria mask image format"; } }
public override uint Signature { get { return 0x4F43494D; } } // 'MICO'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0xE);
if (!header.AsciiEqual (4, "MSK00"))
return null;
return new ImageMetaData
{
Width = header.ToUInt16 (0xA),
Height = header.ToUInt16 (0xC),
BPP = 8
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
file.Position = 0xE;
int total = (int)info.Width * (int)info.Height;
var pixels = new byte[total + 15];
int dst = 0;
while (dst < total)
{
int count = file.ReadUInt16();
if (count > 0x7FFF)
{
count &= 0x7FFF;
file.Read (pixels, dst, count);
dst += count;
}
else
{
byte c = file.ReadUInt8();
while (count --> 0)
pixels[dst++] = c;
}
}
return ImageData.Create (info, PixelFormats.Gray8, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("MalFormat.Write not implemented");
}
}
}

View File

@@ -0,0 +1,145 @@
//! \file ImageMG2.cs
//! \date Sat Feb 25 03:08:01 2017
//! \brief Valkyria 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 System.Windows.Media.Imaging;
namespace GameRes.Formats.Valkyria
{
internal class Mg2MetaData : ImageMetaData
{
public int ImageLength;
public int AlphaLength;
}
[Export(typeof(ImageFormat))]
public class Mg2Format : ImageFormat
{
public override string Tag { get { return "MG2"; } }
public override string Description { get { return "Valkyria image format"; } }
public override uint Signature { get { return 0x4F43494D; } } // 'MICO'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (0x10);
if (!header.AsciiEqual (4, "CG01"))
return null;
int length = header.ToInt32 (8);
using (var input = new Mg2EncryptedStream (file.AsStream, 0x10, length))
using (var png = new BinaryStream (input, file.Name))
{
var info = Png.ReadMetaData (png);
if (null == info)
return null;
return new Mg2MetaData
{
Width = info.Width,
Height = info.Height,
OffsetX = info.OffsetX,
OffsetY = info.OffsetY,
BPP = info.BPP,
ImageLength = length,
AlphaLength = header.ToInt32 (12)
};
}
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (Mg2MetaData)info;
BitmapSource frame;
using (var input = new Mg2EncryptedStream (file.AsStream, 0x10, meta.ImageLength))
{
var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
frame = decoder.Frames[0];
if (0 == meta.AlphaLength)
{
frame.Freeze();
return new ImageData (frame, info);
}
}
if (frame.Format.BitsPerPixel != 32)
frame = new FormatConvertedBitmap (frame, PixelFormats.Bgr32, null, 0);
int stride = frame.PixelWidth * 4;
var pixels = new byte[stride * (int)meta.Height];
frame.CopyPixels (pixels, stride, 0);
using (var input = new Mg2EncryptedStream (file.AsStream, 0x10+meta.ImageLength, meta.AlphaLength))
{
var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapSource alpha_frame = decoder.Frames[0];
if (alpha_frame.PixelWidth != frame.PixelWidth || alpha_frame.PixelHeight != frame.PixelHeight)
return ImageData.Create (info, PixelFormats.Bgr32, null, pixels, stride);
alpha_frame = new FormatConvertedBitmap (alpha_frame, PixelFormats.Gray8, null, 0);
var alpha = new byte[alpha_frame.PixelWidth * alpha_frame.PixelHeight];
alpha_frame.CopyPixels (alpha, alpha_frame.PixelWidth, 0);
int src = 0;
for (int dst = 3; dst < pixels.Length; dst += 4)
{
pixels[dst] = alpha[src++];
}
return ImageData.Create (info, PixelFormats.Bgra32, null, pixels, stride);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("Mg2Format.Write not implemented");
}
}
internal class Mg2EncryptedStream : StreamRegion
{
readonly int m_threshold;
public Mg2EncryptedStream (Stream main, int offset, int length)
: base (main, offset, length, true)
{
m_threshold = length / 5;
}
public override int Read (byte[] buffer, int offset, int count)
{
int pos = (int)Position;
int read = base.Read (buffer, offset, count);
for (int i = 0; i < read && pos < m_threshold; ++i)
buffer[offset+i] ^= (byte)pos++;
return read;
}
public override int ReadByte ()
{
long pos = Position;
int b = base.ReadByte();
if (b != -1 && pos < m_threshold)
b ^= (byte)pos;
return b;
}
}
}

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.2.12")]
[assembly: AssemblyFileVersion ("1.0.2.12")]
[assembly: AssemblyVersion ("1.0.3.14")]
[assembly: AssemblyFileVersion ("1.0.3.14")]

View File

@@ -33,7 +33,6 @@ IN THE SOFTWARE.
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner">
<Window.Resources>
<sys:Uri x:Key="DevLink">https://github.com/morkt/GARbro#readme</sys:Uri>
<local:BooleanToVisibiltyConverter x:Key="guiBoolToVisibilityConverter" />
<local:CanCreateConverter x:Key="guiCanCreateConverter"/>
<CollectionViewSource x:Key="ArcFormatsSource" Source="{Binding Source={x:Static gr:FormatCatalog.Instance}, Path=ArcFormats, Mode=OneWay}">

View File

@@ -189,20 +189,8 @@ namespace GARbro.GUI
private void Hyperlink_RequestNavigate (object sender, RequestNavigateEventArgs e)
{
try
{
if (e.Uri.IsAbsoluteUri)
{
Process.Start (new ProcessStartInfo (e.Uri.AbsoluteUri));
e.Handled = true;
}
else
throw new ApplicationException ("URI is not absolute");
}
catch (Exception X)
{
Trace.WriteLine ("Link navigation failed: "+X.Message, e.Uri.ToString());
}
if (App.NavigateUri (e.Uri))
e.Handled = true;
}
}

View File

@@ -1,9 +1,12 @@
<Application x:Class="GARbro.GUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=System"
StartupUri="MainWindow.xaml" Startup="ApplicationStartup"
ShutdownMode="OnMainWindowClose" Exit="ApplicationExit">
<Application.Resources>
<sys:Uri x:Key="DevLink">https://github.com/morkt/GARbro#readme</sys:Uri>
<sys:Uri x:Key="UpdateUrl">https://morkt.github.io/GARbro/version.xml</sys:Uri>
<BitmapImage x:Key="IconSearch" UriSource="pack://application:,,,/Images/search4files.ico" />
</Application.Resources>
</Application>

View File

@@ -29,6 +29,7 @@ using System.Diagnostics;
using GARbro.GUI.Properties;
using GameRes;
using GameRes.Compression;
using System.Reflection;
namespace GARbro.GUI
{
@@ -37,9 +38,8 @@ namespace GARbro.GUI
/// </summary>
public partial class App : Application
{
const StringComparison StringIgnoreCase = StringComparison.OrdinalIgnoreCase;
public static string Name { get { return "GARbro"; } }
public static string Name { get { return "GARbro"; } }
public static string FormatsDat { get { return "Formats.dat"; } }
/// <summary>
/// Initial browsing directory.
@@ -79,9 +79,24 @@ namespace GARbro.GUI
if (string.IsNullOrEmpty (InitPath))
InitPath = Directory.GetCurrentDirectory();
string scheme_file = Path.Combine (FormatCatalog.Instance.DataDirectory, "Formats.dat");
DeserializeScheme (Path.Combine (FormatCatalog.Instance.DataDirectory, FormatsDat));
DeserializeScheme (Path.Combine (GetLocalAppDataFolder(), FormatsDat));
}
public string GetLocalAppDataFolder ()
{
string local_app_data = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData);
var attribs = Assembly.GetExecutingAssembly().GetCustomAttributes (typeof(AssemblyCompanyAttribute), false);
string company = attribs.Length > 0 ? ((AssemblyCompanyAttribute)attribs[0]).Company : "";
return Path.Combine (local_app_data, company, Name);
}
public void DeserializeScheme (string scheme_file)
{
try
{
if (!File.Exists (scheme_file))
return;
using (var file = File.OpenRead (scheme_file))
FormatCatalog.Instance.DeserializeScheme (file);
}
@@ -116,5 +131,24 @@ namespace GARbro.GUI
if (Settings.Default.winState == System.Windows.WindowState.Minimized)
Settings.Default.winState = System.Windows.WindowState.Normal;
}
public static bool NavigateUri (Uri uri)
{
try
{
if (uri.IsAbsoluteUri)
{
Process.Start (new ProcessStartInfo (uri.AbsoluteUri));
return true;
}
else
throw new ApplicationException ("URI is not absolute");
}
catch (Exception X)
{
Trace.WriteLine ("Link navigation failed: "+X.Message, uri.ToString());
}
return false;
}
}
}

View File

@@ -154,6 +154,7 @@
<Compile Include="GarCreate.cs" />
<Compile Include="GarExtract.cs" />
<Compile Include="GarOperation.cs" />
<Compile Include="GarUpdate.cs" />
<Compile Include="HistoryStack.cs" />
<Compile Include="ImagePreview.cs" />
<Compile Include="ListViewEx.cs" />
@@ -173,6 +174,9 @@
<Compile Include="TextViewer.xaml.cs">
<DependentUpon>TextViewer.xaml</DependentUpon>
</Compile>
<Compile Include="UpdateDialog.xaml.cs">
<DependentUpon>UpdateDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Utility.cs" />
<Compile Include="ViewModel.cs" />
<Page Include="AboutBox.xaml">
@@ -227,6 +231,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UpdateDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">

308
GUI/GarUpdate.cs Normal file
View File

@@ -0,0 +1,308 @@
//! \file GarUpdate.cs
//! \date Tue Feb 14 00:02:14 2017
//! \brief Application update routines.
//
// 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;
using System.Net;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Xml;
using GameRes;
using GARbro.GUI.Strings;
using System.IO;
namespace GARbro.GUI
{
public partial class MainWindow : Window
{
GarUpdate m_updater;
private void InitUpdatesChecker ()
{
var update_url = App.Resources["UpdateUrl"] as Uri;
m_updater = new GarUpdate (this, update_url);
m_updater.CanExecuteChanged += (s, e) => CommandManager.InvalidateRequerySuggested();
}
public void CanExecuteUpdate (object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = m_updater.CanExecute (e.Parameter);
}
/// <summary>
/// Handle "Check for updates" command.
/// </summary>
private void CheckUpdatesExec (object sender, ExecutedRoutedEventArgs e)
{
m_updater.Execute (e.Parameter);
}
}
public class GarUpdateInfo
{
public Version ReleaseVersion { get; set; }
public Uri ReleaseUrl { get; set; }
public string ReleaseNotes { get; set; }
public int FormatsVersion { get; set; }
public Uri FormatsUrl { get; set; }
public IDictionary<string, Version> Assemblies { get; set; }
public static GarUpdateInfo Parse (XmlDocument xml)
{
var root = xml.DocumentElement.SelectSingleNode ("/GARbro");
if (null == root)
return null;
var info = new GarUpdateInfo
{
ReleaseVersion = Version.Parse (GetInnerText (root.SelectSingleNode ("Release/Version"))),
ReleaseUrl = new Uri (GetInnerText (root.SelectSingleNode ("Release/Url"))),
ReleaseNotes = GetInnerText (root.SelectSingleNode ("Release/Notes")),
FormatsVersion = Int32.Parse (GetInnerText (root.SelectSingleNode ("FormatsData/FileVersion"))),
FormatsUrl = new Uri (GetInnerText (root.SelectSingleNode ("FormatsData/Url"))),
Assemblies = ParseAssemblies (root.SelectNodes ("FormatsData/Requires/Assembly")),
};
return info;
}
static string GetInnerText (XmlNode node)
{
// XXX node?.InnerText ?? ""
return node != null ? node.InnerText : "";
}
static IDictionary<string, Version> ParseAssemblies (XmlNodeList nodes)
{
var dict = new Dictionary<string, Version>();
foreach (XmlNode node in nodes)
{
var attr = node.Attributes;
var name = attr["Name"];
var version = attr["Version"];
if (name != null && version != null)
dict[name.Value] = Version.Parse (version.Value);
}
return dict;
}
}
internal sealed class GarUpdate : ICommand, IDisposable
{
private readonly MainWindow m_main;
private readonly BackgroundWorker m_update_checker = new BackgroundWorker();
private readonly Uri m_url;
const int RequestTimeout = 20000; // milliseconds
public GarUpdate (MainWindow main, Uri url)
{
m_main = main;
m_url = url;
m_update_checker.DoWork += StartUpdatesCheck;
m_update_checker.RunWorkerCompleted += UpdatesCheckComplete;
}
public void Execute (object parameter)
{
if (!m_update_checker.IsBusy)
m_update_checker.RunWorkerAsync();
}
public bool CanExecute (object parameter)
{
return !m_update_checker.IsBusy;
}
public event EventHandler CanExecuteChanged;
void OnCanExecuteChanged ()
{
var handler = CanExecuteChanged;
if (handler != null)
handler (this, EventArgs.Empty);
}
private void StartUpdatesCheck (object sender, DoWorkEventArgs e)
{
OnCanExecuteChanged();
if (m_url != null)
e.Result = Check (m_url);
}
private void UpdatesCheckComplete (object sender, RunWorkerCompletedEventArgs e)
{
try
{
if (e.Error != null)
{
m_main.SetStatusText (string.Format ("{0} {1}", guiStrings.MsgUpdateFailed, e.Error.Message));
return;
}
else if (e.Cancelled)
return;
var result = e.Result as GarUpdateInfo;
if (null == result)
{
m_main.SetStatusText (guiStrings.MsgNoUpdates);
return;
}
ShowUpdateResult (result);
}
finally
{
OnCanExecuteChanged();
}
}
UpdateDialog m_dialog;
Uri m_formats_url;
private void ShowUpdateResult (GarUpdateInfo result)
{
var app_version = Assembly.GetExecutingAssembly().GetName().Version;
var db_version = FormatCatalog.Instance.CurrentSchemeVersion;
bool has_app_update = app_version < result.ReleaseVersion;
bool has_db_update = db_version < result.FormatsVersion && CheckAssemblies (result.Assemblies);
if (!has_app_update && !has_db_update)
{
m_main.SetStatusText (guiStrings.MsgUpToDate);
return;
}
m_formats_url = result.FormatsUrl;
m_dialog = new UpdateDialog (result, has_app_update, has_db_update);
m_dialog.Owner = m_main;
m_dialog.FormatsDownload.Click += StartFormatsDownload;
m_dialog.ShowDialog();
}
private async void StartFormatsDownload (object control, RoutedEventArgs e)
{
var dialog = m_dialog;
try
{
dialog.FormatsDownload.IsEnabled = false;
var app_data_folder = m_main.App.GetLocalAppDataFolder();
Directory.CreateDirectory (app_data_folder);
using (var client = new WebClientEx())
using (var tmp_file = new GARbro.Shell.TemporaryFile (app_data_folder, Path.GetRandomFileName()))
{
client.Timeout = RequestTimeout;
await client.DownloadFileTaskAsync (m_formats_url, tmp_file.Name);
m_main.App.DeserializeScheme (tmp_file.Name);
var local_formats_dat = Path.Combine (app_data_folder, App.FormatsDat);
if (!GARbro.Shell.File.Rename (tmp_file.Name, local_formats_dat))
throw new Win32Exception (GARbro.Shell.File.GetLastError());
}
SetFormatsUpdateStatus (dialog, guiStrings.MsgUpdateComplete);
}
catch (Exception X)
{
SetFormatsUpdateStatus (dialog, guiStrings.MsgDownloadFailed, X.Message);
}
finally
{
dialog.FormatsDownload.Visibility = Visibility.Hidden;
}
}
void SetFormatsUpdateStatus (UpdateDialog dialog, string text1, string text2 = null)
{
if (dialog.IsClosed)
m_main.SetStatusText (text1);
else if (null == text2)
dialog.FormatsUpdateText.Text = text1;
else
dialog.FormatsUpdateText.Text = string.Format ("{0}\n{1}", text1, text2);
}
/// <summary>
/// Check if loaded assemblies match required versions.
/// </summary>
bool CheckAssemblies (IDictionary<string, Version> assemblies)
{
var loaded = AppDomain.CurrentDomain.GetAssemblies().Select (a => a.GetName())
.ToDictionary (a => a.Name, a => a.Version);
foreach (var item in assemblies)
{
if (!loaded.ContainsKey (item.Key))
return false;
if (loaded[item.Key] < item.Value)
return false;
}
return true;
}
GarUpdateInfo Check (Uri version_url)
{
var request = WebRequest.Create (version_url);
request.Timeout = RequestTimeout;
var response = (HttpWebResponse)request.GetResponse();
using (var input = response.GetResponseStream())
{
var xml = new XmlDocument();
xml.Load (input);
return GarUpdateInfo.Parse (xml);
}
}
bool m_disposed = false;
public void Dispose ()
{
if (!m_disposed)
{
m_update_checker.Dispose();
m_disposed = true;
}
GC.SuppressFinalize (this);
}
}
/// <summary>
/// WebClient with timeout setting.
/// </summary>
internal class WebClientEx : WebClient
{
/// <summary>
/// Request timeout, in milliseconds.
/// </summary>
public int Timeout { get; set; }
public WebClientEx ()
{
Timeout = 60000;
}
protected override WebRequest GetWebRequest (Uri uri)
{
var request = base.GetWebRequest (uri);
request.Timeout = Timeout;
return request;
}
}
}

View File

@@ -151,6 +151,7 @@
</MenuItem>
<MenuItem Header="{x:Static s:guiStrings.MenuHelp}">
<MenuItem Header="{x:Static s:guiStrings.MenuAbout}" Command="{x:Static local:Commands.About}"/>
<MenuItem Header="{x:Static s:guiStrings.MenuCheckUpdates}" Command="{x:Static local:Commands.CheckUpdates}"/>
</MenuItem>
</Menu>
<Separator Height="1" Margin="0"/>
@@ -402,6 +403,7 @@
<CommandBinding Command="{x:Static local:Commands.HideMenuBar}" Executed="HideMenuBarExec" CanExecute="CanExecuteAlways"/>
<CommandBinding Command="{x:Static local:Commands.HideToolBar}" Executed="HideToolBarExec" CanExecute="CanExecuteAlways"/>
<CommandBinding Command="{x:Static local:Commands.About}" Executed="AboutExec" CanExecute="CanExecuteAlways"/>
<CommandBinding Command="{x:Static local:Commands.CheckUpdates}" Executed="CheckUpdatesExec" CanExecute="CanExecuteUpdate"/>
<CommandBinding Command="{x:Static local:Commands.Exit}" Executed="ExitExec" CanExecute="CanExecuteAlways"/>
</Window.CommandBindings>
</Window>

View File

@@ -54,6 +54,8 @@ namespace GARbro.GUI
{
private App m_app;
public App App { get { return m_app; } }
const StringComparison StringIgnoreCase = StringComparison.CurrentCultureIgnoreCase;
public MainWindow()
@@ -64,6 +66,7 @@ namespace GARbro.GUI
if (this.Left < 0) this.Left = 0;
InitDirectoryChangesWatcher();
InitPreviewPane();
InitUpdatesChecker();
if (null == Settings.Default.appRecentFiles)
Settings.Default.appRecentFiles = new StringCollection();
@@ -1478,6 +1481,7 @@ namespace GARbro.GUI
public static readonly RoutedCommand SortBy = new RoutedCommand();
public static readonly RoutedCommand Exit = new RoutedCommand();
public static readonly RoutedCommand About = new RoutedCommand();
public static readonly RoutedCommand CheckUpdates = new RoutedCommand();
public static readonly RoutedCommand GoBack = new RoutedCommand();
public static readonly RoutedCommand GoForward = new RoutedCommand();
public static readonly RoutedCommand DeleteItem = new RoutedCommand();

View File

@@ -51,5 +51,5 @@ using System.Windows;
// 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.3.25.1723")]
[assembly: AssemblyFileVersion ("1.3.25.1723")]
[assembly: AssemblyVersion ("1.4.26.1759")]
[assembly: AssemblyFileVersion ("1.4.26.1759")]

View File

@@ -96,6 +96,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to _Download.
/// </summary>
public static string ButtonDownload {
get {
return ResourceManager.GetString("ButtonDownload", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extract.
/// </summary>
@@ -456,6 +465,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Release notes.
/// </summary>
public static string LabelReleaseNotes {
get {
return ResourceManager.GetString("LabelReleaseNotes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Skip incovertible files..
/// </summary>
@@ -474,6 +492,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to _Check for updates....
/// </summary>
public static string MenuCheckUpdates {
get {
return ResourceManager.GetString("MenuCheckUpdates", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to E_xit.
/// </summary>
@@ -636,6 +663,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Update download failed..
/// </summary>
public static string MsgDownloadFailed {
get {
return ResourceManager.GetString("MsgDownloadFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to archive is empty.
/// </summary>
@@ -771,6 +807,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to No updates currently available..
/// </summary>
public static string MsgNoUpdates {
get {
return ResourceManager.GetString("MsgNoUpdates", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to File {0}
///already exists.
@@ -837,6 +882,42 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Formats database update available..
/// </summary>
public static string MsgUpdateAvailable {
get {
return ResourceManager.GetString("MsgUpdateAvailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Formats database updated..
/// </summary>
public static string MsgUpdateComplete {
get {
return ResourceManager.GetString("MsgUpdateComplete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Update check failed..
/// </summary>
public static string MsgUpdateFailed {
get {
return ResourceManager.GetString("MsgUpdateFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to GARbro version is up to date..
/// </summary>
public static string MsgUpToDate {
get {
return ResourceManager.GetString("MsgUpToDate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Version {0}.
/// </summary>
@@ -1093,6 +1174,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to New version available:.
/// </summary>
public static string TextNewVersion {
get {
return ResourceManager.GetString("TextNewVersion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Archive parameters.
/// </summary>
@@ -1138,6 +1228,24 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Application update.
/// </summary>
public static string TextUpdateTitle {
get {
return ResourceManager.GetString("TextUpdateTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Visit download page.
/// </summary>
public static string TextVisitPage {
get {
return ResourceManager.GetString("TextVisitPage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Either WAV, MP3 or OGG.
/// </summary>

View File

@@ -493,4 +493,40 @@
<data name="TextFileAlreadyExists" xml:space="preserve">
<value>대상폴더에 파일 {0} 이(가) 이미 존재합니다.</value>
</data>
<data name="ButtonDownload" xml:space="preserve">
<value>다운로드</value>
</data>
<data name="LabelReleaseNotes" xml:space="preserve">
<value>릴리스 정보</value>
</data>
<data name="MenuCheckUpdates" xml:space="preserve">
<value>업데이트 확인...</value>
</data>
<data name="MsgDownloadFailed" xml:space="preserve">
<value>업데이트 다운로드에 실패했습니다.</value>
</data>
<data name="MsgNoUpdates" xml:space="preserve">
<value>현재 사용가능한 업데이트가 없습니다.</value>
</data>
<data name="MsgUpdateAvailable" xml:space="preserve">
<value>포맷 데이터베이스를 업데이트 하실수 있습니다.</value>
</data>
<data name="MsgUpdateComplete" xml:space="preserve">
<value>포맷 데이터베이스를 업데이트했습니다.</value>
</data>
<data name="MsgUpdateFailed" xml:space="preserve">
<value>업데이트 확인에 실패했습니다.</value>
</data>
<data name="MsgUpToDate" xml:space="preserve">
<value>GARbro이 최신버전입니다.</value>
</data>
<data name="TextNewVersion" xml:space="preserve">
<value>새로운 버전을 사용하실 수 있습니다:</value>
</data>
<data name="TextUpdateTitle" xml:space="preserve">
<value>응용프로그램 업데이트</value>
</data>
<data name="TextVisitPage" xml:space="preserve">
<value>다운로드 페이지 방문</value>
</data>
</root>

View File

@@ -497,4 +497,40 @@ Overwrite?</value>
<data name="TextFileAlreadyExists" xml:space="preserve">
<value>File {0} already exists in the destination folder.</value>
</data>
<data name="ButtonDownload" xml:space="preserve">
<value>_Download</value>
</data>
<data name="LabelReleaseNotes" xml:space="preserve">
<value>Release notes</value>
</data>
<data name="MenuCheckUpdates" xml:space="preserve">
<value>_Check for updates...</value>
</data>
<data name="MsgNoUpdates" xml:space="preserve">
<value>No updates currently available.</value>
</data>
<data name="MsgUpdateAvailable" xml:space="preserve">
<value>Formats database update available.</value>
</data>
<data name="MsgUpdateComplete" xml:space="preserve">
<value>Formats database updated.</value>
</data>
<data name="MsgUpdateFailed" xml:space="preserve">
<value>Update check failed.</value>
</data>
<data name="MsgUpToDate" xml:space="preserve">
<value>GARbro version is up to date.</value>
</data>
<data name="TextNewVersion" xml:space="preserve">
<value>New version available:</value>
</data>
<data name="TextUpdateTitle" xml:space="preserve">
<value>Application update</value>
</data>
<data name="TextVisitPage" xml:space="preserve">
<value>Visit download page</value>
</data>
<data name="MsgDownloadFailed" xml:space="preserve">
<value>Update download failed.</value>
</data>
</root>

View File

@@ -518,4 +518,40 @@
<data name="TextFileAlreadyExists" xml:space="preserve">
<value>Файл с именем {0} уже существует.</value>
</data>
<data name="ButtonDownload" xml:space="preserve">
<value>Обновить</value>
</data>
<data name="LabelReleaseNotes" xml:space="preserve">
<value>Примечания к выпуску</value>
</data>
<data name="MenuCheckUpdates" xml:space="preserve">
<value>Проверить обновления...</value>
</data>
<data name="MsgDownloadFailed" xml:space="preserve">
<value>Не удалось обновить базу форматов.</value>
</data>
<data name="MsgNoUpdates" xml:space="preserve">
<value>Обновления недоступны.</value>
</data>
<data name="MsgUpdateAvailable" xml:space="preserve">
<value>Доступна обновлённая база форматов.</value>
</data>
<data name="MsgUpdateComplete" xml:space="preserve">
<value>База форматов успешно обновлена.</value>
</data>
<data name="MsgUpdateFailed" xml:space="preserve">
<value>Сбой проверки обновлений.</value>
</data>
<data name="MsgUpToDate" xml:space="preserve">
<value>Установлена актуальная версия GARbro.</value>
</data>
<data name="TextNewVersion" xml:space="preserve">
<value>Доступна новая версия:</value>
</data>
<data name="TextUpdateTitle" xml:space="preserve">
<value>Обновление программы</value>
</data>
<data name="TextVisitPage" xml:space="preserve">
<value>Перейти на страницу загрузки</value>
</data>
</root>

View File

@@ -505,4 +505,52 @@
<data name="TextFileAlreadyExists" xml:space="preserve">
<value>文件{0}已经存在。</value>
</data>
<data name="ButtonDownload" xml:space="preserve">
<value>Download</value>
<comment>translation pending</comment>
</data>
<data name="LabelReleaseNotes" xml:space="preserve">
<value>Release notes</value>
<comment>translation pending</comment>
</data>
<data name="MenuCheckUpdates" xml:space="preserve">
<value>Check for updates...</value>
<comment>translation pending</comment>
</data>
<data name="MsgDownloadFailed" xml:space="preserve">
<value>Update download failed.</value>
<comment>translation pending</comment>
</data>
<data name="MsgNoUpdates" xml:space="preserve">
<value>No updates currently available.</value>
<comment>translation pending</comment>
</data>
<data name="MsgUpdateAvailable" xml:space="preserve">
<value>Formats database update available.</value>
<comment>translation pending</comment>
</data>
<data name="MsgUpdateComplete" xml:space="preserve">
<value>Formats database updated.</value>
<comment>translation pending</comment>
</data>
<data name="MsgUpdateFailed" xml:space="preserve">
<value>Update check failed.</value>
<comment>translation pending</comment>
</data>
<data name="MsgUpToDate" xml:space="preserve">
<value>GARbro version is up to date.</value>
<comment>translation pending</comment>
</data>
<data name="TextNewVersion" xml:space="preserve">
<value>New version available:</value>
<comment>translation pending</comment>
</data>
<data name="TextUpdateTitle" xml:space="preserve">
<value>Application update</value>
<comment>translation pending</comment>
</data>
<data name="TextVisitPage" xml:space="preserve">
<value>Visit download page</value>
<comment>translation pending</comment>
</data>
</root>

61
GUI/UpdateDialog.xaml Normal file
View File

@@ -0,0 +1,61 @@
<!-- Game Resource browser
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.
-->
<Window x:Class="GARbro.GUI.UpdateDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:GARbro.GUI.Strings"
Title="{x:Static s:guiStrings.TextUpdateTitle}" ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
SizeToContent="WidthAndHeight" ResizeMode="NoResize">
<DockPanel>
<DockPanel DockPanel.Dock="Bottom" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<Border BorderThickness="0,1,0,0" BorderBrush="Black">
<Button HorizontalAlignment="Right" Content="{x:Static s:guiStrings.ButtonOK}"
Margin="10" Width="75" Height="25" Click="Button_Click" IsCancel="True"/>
</Border>
</DockPanel>
<Image DockPanel.Dock="Left" Source="Images/64x64/actions.png" Width="32" Height="32" Margin="10,20"
SnapsToDevicePixels="True" VerticalAlignment="Top" RenderOptions.BitmapScalingMode="HighQuality"/>
<StackPanel DockPanel.Dock="Right" Orientation="Vertical">
<StackPanel x:Name="ReleasePane" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static s:guiStrings.TextNewVersion}" Margin="0,10,0,0"/>
<TextBlock x:Name="ReleaseVersion" Text="{Binding ReleaseVersion}" Margin="5,10,10,0"/>
</StackPanel>
<Expander x:Name="ReleaseNotes" Header="{x:Static s:guiStrings.LabelReleaseNotes}" ExpandDirection="Down" IsExpanded="False" Margin="0,0,10,0">
<TextBlock Text="{Binding ReleaseNotes}"/>
</Expander>
<TextBlock Margin="0,5,10,10">
<Hyperlink NavigateUri="{Binding ReleaseUrl}" RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock Text="{x:Static s:guiStrings.TextVisitPage}" ToolTip="{Binding ReleaseUrl}"/>
</Hyperlink>
</TextBlock>
</StackPanel>
<StackPanel x:Name="FormatsPane" Orientation="Vertical">
<Separator Visibility="{Binding ElementName=ReleasePane, Path=Visibility}"/>
<TextBlock x:Name="FormatsUpdateText" Text="{x:Static s:guiStrings.MsgUpdateAvailable}" Margin="0,10,10,10"/>
<Button x:Name="FormatsDownload" Content="{x:Static s:guiStrings.ButtonDownload}" Width="75" Height="25" Margin="0,0,10,10" HorizontalAlignment="Left"/>
</StackPanel>
</StackPanel>
</DockPanel>
</Window>

34
GUI/UpdateDialog.xaml.cs Normal file
View File

@@ -0,0 +1,34 @@
using System.Windows;
namespace GARbro.GUI
{
/// <summary>
/// Interaction logic for UpdateDialog.xaml
/// </summary>
public partial class UpdateDialog : Window
{
public UpdateDialog (GarUpdateInfo info, bool enable_release, bool enable_formats)
{
InitializeComponent ();
this.ReleasePane.Visibility = enable_release ? Visibility.Visible : Visibility.Collapsed;
this.FormatsPane.Visibility = enable_formats ? Visibility.Visible : Visibility.Collapsed;
if (string.IsNullOrEmpty (info.ReleaseNotes))
this.ReleaseNotes.Visibility = Visibility.Collapsed;
this.DataContext = info;
this.Closed += (s, e) => IsClosed = true;
}
public bool IsClosed { get; private set; }
private void Hyperlink_RequestNavigate (object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
if (App.NavigateUri (e.Uri))
e.Handled = true;
}
private void Button_Click (object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
}
}

View File

@@ -38,6 +38,11 @@ namespace GameRes
public ushort BlockAlign;
public ushort BitsPerSample;
public ushort ExtraSize;
public void SetBPS ()
{
AverageBytesPerSecond = (uint)(SamplesPerSecond * Channels * BitsPerSample / 8);
}
}
public abstract class SoundInput : Stream

View File

@@ -27,6 +27,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Text.RegularExpressions;
using GameRes.Strings;
@@ -558,7 +559,7 @@ namespace GameRes
if (null == arc)
{
if (FormatCatalog.Instance.LastError is OperationCanceledException)
throw FormatCatalog.Instance.LastError;
ExceptionDispatchInfo.Capture (FormatCatalog.Instance.LastError).Throw();
else
throw new UnknownFormatException (FormatCatalog.Instance.LastError);
}

View File

@@ -254,15 +254,9 @@ namespace GameRes
public void DeserializeScheme (Stream input)
{
using (var reader = new BinaryReader (input, System.Text.Encoding.UTF8, true))
{
var header = reader.ReadChars (SchemeID.Length);
if (!header.SequenceEqual (SchemeID))
throw new FormatException ("Invalid serialization file");
int version = reader.ReadInt32();
if (version <= CurrentSchemeVersion)
return;
}
int version = GetSerializedSchemeVersion (input);
if (version <= CurrentSchemeVersion)
return;
using (var zs = new ZLibStream (input, CompressionMode.Decompress, true))
{
var bin = new BinaryFormatter();
@@ -307,6 +301,17 @@ namespace GameRes
using (var zs = new ZLibStream (output, CompressionMode.Compress, true))
bin.Serialize (zs, db);
}
public int GetSerializedSchemeVersion (Stream input)
{
using (var reader = new BinaryReader (input, System.Text.Encoding.UTF8, true))
{
var header = reader.ReadChars (SchemeID.Length);
if (!header.SequenceEqual (SchemeID))
throw new FormatException ("Invalid serialization file");
return reader.ReadInt32();
}
}
}
[Serializable]

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.4.26.237")]
[assembly: AssemblyFileVersion ("1.4.26.237")]
[assembly: AssemblyVersion ("1.4.27.241")]
[assembly: AssemblyFileVersion ("1.4.27.241")]

View File

@@ -5,7 +5,7 @@ Visual Novels resource browser.
Requires .NET Framework v4.5 or newer (https://www.microsoft.com/net)
[Supported formats](http://htmlpreview.github.io/?https://github.com/morkt/GARbro/blob/master/supported.html)
[Supported formats](https://morkt.github.io/GARbro/supported.html)
[Download latest release](https://github.com/morkt/GARbro/releases)

View File

@@ -15,7 +15,7 @@ tr.last { height: auto }
</style>
</head>
<body>
<p>Formats recognized by Game Archived Resources browser. "Titles" column lists tested titles only.</p>
<p>Formats recognized by <a href="https://github.com/morkt/GARbro">Game Archived Resources browser</a>. "Titles" column lists tested titles only.</p>
<table>
<tr><th>Files</th><th>Signature</th><th>Create</th><th>Brand/Engine<th>Titles</th></tr>
<tr class="odd"><td>*.pak</td><td><tt>ADPACK32</tt></td><td>No</td><td rowspan="3">Active Soft</td><td rowspan="3">Bible Black<br/>Discipline<br/>Discipline LS<br/>Fearless]</td></tr>
@@ -135,6 +135,7 @@ Katahane<br/>
Magical Kanon 2 ~Hiiro no Bergamot~<br/>
Narimono<br/>
Reconquista<br/>
Strawberry Feels<br/>
Switch!! ~Boku ga Natsu ni Omou Koto~<br/>
White ~blanche comme la lune~<br/>
</td></tr>
@@ -254,8 +255,10 @@ Ah, Ojou-sama<br/>
Altered Pink ~Tokumu Sentai Duel Ranger~<br/>
Amairo*Islenauts<br/>
Aozora Gakko no Sensei-kun<br/>
Bishuu ~Chigyaku no Mesu Dorei~<br/>
Boku to Koi Suru Ponkotsu Akuma. Suggoi Ecchi!<br/>
Cafe Sourire<br/>
Clover Day's<br/>
Coμ<br/>
Corona Blossom vol.1<br/>
Crime Rhyme series<br/>
@@ -276,6 +279,7 @@ Hime to Majin to Koi Suru Tamashii<br/>
Imouto Style<br/>
Inaho no Mirai<br/>
Karakara<br/>
Karenai Sekai to Owaru Hana<br/>
Kourin no Machi, Lavender no Shoujo<br/>
Kozukuri Onsen ~Ippai Tsukutte Ichizoku Hanei~<br/>
Kurenai no Tsuki<br/>
@@ -295,12 +299,14 @@ Okiba ga Nai!<br/>
Oku-sama wa Moto Yariman<br/>
Omana 2: Omaenchi Moeteruzo<br/>
Ore no Saimin Fantasia<br/>
Otome*Domain<br/>
Ouka Ryouran<br/>
Oyako Ninjutsu Kunoichi PonPon!!<br/>
Rakugaki Overheart<br/>
RGH ~Koi to Hero to Gakuen to~<br/>
Riding Incubus<br/>
Rui wa Tomo o Yobu<br/>
Sakura Sakura<br/>
Sanoba Witch<br/>
Seirei Tenshou<br/>
Senren*Banka<br/>
@@ -332,6 +338,7 @@ Ryuuyoku no Melodia -Diva with the blessed dragonol-<br/>
Sei Monmusu Festival!!<br/>
Shin Chikan Ou<br/>
Unionism Quartet<br/>
Unionism Quartet A3-Days<br/>
Usotsuki Ouji to Nayameru Ohime-sama -Princess Syndrome-<br/>
</td></tr>
<tr class="odd last"><td>*.ycg</td><td><tt>YCG</tt></td><td>No</td></tr>
@@ -383,6 +390,7 @@ Ren no Koi<br/>
<tr class="odd"><td>*.ald</td><td>-</td><td>No</td><td rowspan="6">Alice Soft</td><td rowspan="6">
Boku dake no Hokenshitsu<br/>
Daiteikoku<br/>
Majo no Shokuzai<br/>
Momoiro Guardian<br/>
Pastel Chime 3 Bind Seeker<br/>
Rance 03<br/>
@@ -439,11 +447,12 @@ Shouhei-kun no Hani-Kami Life☆<br/>
Touka Gettan<br/>
</td></tr>
<tr class="odd last"><td>*</td><td><tt>\x00\x00\x04\x00</tt></td><td>No</td></tr>
<tr><td>*.war</td><td><tt>WARC 1.7</tt><br/><tt>WARC 1.5</tt><br/><tt>WARC 1.4</tt><br/><tt>WARC 1.3</tt><br/><tt>WARC 1.2</tt></td><td>No</td><td rowspan="5">Shiina Rio</td><td rowspan="5">
<tr><td>*.war</td><td><tt>WARC 1.7</tt><br/><tt>WARC 1.5</tt><br/><tt>WARC 1.4</tt><br/><tt>WARC 1.3</tt><br/><tt>WARC 1.2</tt><br/><tt>WARC 1.1</tt></td><td>No</td><td rowspan="5">Shiina Rio</td><td rowspan="5">
Ao no Juuai <span class="footnote">ShiinaRio v2.34</span><br/>
Aneiro <span class="footnote">ShiinaRio v2.49</span><br/>
Bitch Nee-chan ga Seijun na Hazu ga Nai! <span class="footnote">ShiinaRio v2.50</span><br/>
Bitch Shimai ga Seijun na Hazu ga Nai!! <span class="footnote">ShiinaRio v2.50</span><br/>
Calendar Girl <span class="footnote">ShiinaRio v2.02</span><br/>
Can Fes! ~Itazura Majo to Naisho no Gakuensai~ <span class="footnote">ShiinaRio v2.47</span><br/>
Chikan Circle <span class="footnote">ShiinaRio v2.46</span><br/>
Chikan Circle 2 <span class="footnote">ShiinaRio v2.47</span><br/>
@@ -484,6 +493,7 @@ Nagagutsu wo Haita Deco <span class="footnote">ShiinaRio v2.39</span><br/>
Najimi no Oba-chan <span class="footnote">ShiinaRio v2.47</span><br/>
Niizuma to Yuukaihan <span class="footnote">ShiinaRio v2.45</span><br/>
Onegan! <span class="footnote">ShiinaRio v2.48</span><br/>
Oshikake! Entremets <span class="footnote">ShiinaRio v2.47</span><br/>
Otome Chibaku Yuugi <span class="footnote">ShiinaRio v2.40</span><br/>
Otome Juurin Yuugi <span class="footnote">ShiinaRio v2.37</span><br/>
Puchipuchi Idol Kouhosei <span class="footnote">ShiinaRio v2.49</span><br/>
@@ -500,6 +510,7 @@ Sorcery Jokers<span class="footnote">ShiinaRio v2.50</span><br/>
Tantei Shounen A<span class="footnote">some old version, like 2.twenty-something</span><br/>
Tekoire Princess!<span class="footnote">ShiinaRio v2.37</span><br/>
Toriko no Chigiri<span class="footnote">ShiinaRio v2.49</span><br/>
Vestige -Yaiba ni Nokoru wa Kimi no Omokage- <span class="footnote">ShiinaRio v2.47</span><br/>
Wana ~Hakudaku Mamire no Houkago~<span class="footnote">ShiinaRio v2.36</span><br/>
Wana II ~Gang Rape~<span class="footnote">ShiinaRio v2.44</span><br/>
Yakata Jukujo ~The Immoral Residence~ <span class="footnote">ShiinaRio v2.37</span><br/>
@@ -528,6 +539,7 @@ Prawf Clwyd<br/>
Rakuen no Rukia ~Blood Moon Rising~<br/>
Samayou Mitama ni Yasuragi no Toki wo<br/>
School Five ~Itsutsu no Himitsu no Monogatari~<br/>
Tappun☆Oneecher<br/>
Time Trouble ~Marie ni Kubikkake~<br/>
</td></tr>
<tr><td>*.dat</td><td>-</td><td>No</td><td>Ail</td><td>
@@ -564,6 +576,7 @@ Tsurugi Otome Noah<br/>
<tr><td>*.pmp<br/>*.pmw</td><td>-</td><td>Yes</td><td>ScenePlayer</td><td>
Eraburu ~Erabu + Love x Double de~<br/>
Nyuujoku Hitozuma Jogakuen<br/>
Nyuukan Nurse<br/>
WW&amp;F ~Taishou Teito Denkitan~<br/>
</td></tr>
<tr class="odd"><td>*.dat</td><td><tt>GAMEDAT PACK</tt><br/><tt>GAMEDAT PAC2</tt></td><td>No</td><td rowspan="2"> bootUP!<br/>Pajamas Soft<br/>Aries</td><td rowspan="2">
@@ -620,7 +633,9 @@ re-Laive<br/>
Sakura Musubi<br/>
Santaful☆Summer<br/>
Space☆Trouble<br/>
Spicy Spycy!<br/>
Tonarizuma<br/>
Tsubasa o Kudasai<br/>
Yatohime Zankikou<br/>
Zettai Joshi Ryouiki!<br/>
</td></tr>
@@ -670,6 +685,7 @@ Zetsuboushi<br/>
<tr><td>*.pack</td><td><tt>FilePackVer1.0</tt><br/><tt>FilePackVer2.0</tt><br/><tt>FilePackVer3.0</tt></td><td>No</td><td rowspan="3">QLIE</td><td rowspan="3">
Bishoujo Mangekyou -Kami ga Tsukuritamouta Shoujo-tachi-<br/>
Hidamari Basket<br/>
Kikan Bakumatsu Ibun Last Cavalier<br/>
Makai Tenshi Djibril -episode 3-<br/>
Mehime no Toriko<br/>
Nanatsu no Fushigi no Owaru Toki<br/>
@@ -755,6 +771,7 @@ Thirua Panic<br/>
<tr><td>*.mcg</td><td><tt>MCG 2.00</tt></td><td>No</td></tr>
<tr class="last"><td>*.acd</td><td><tt>ACD 1.00</tt></td><td>No</td></tr>
<tr class="odd"><td>*.pk<br/>*.gpk<br/>*.tpk<br/>*.wpk<br/></td><td>-</td><td>No</td><td rowspan="2">U-Me Soft</td><td rowspan="2">
Do M Otoko Tantei ga Iku Katte ni Ittara Oshioki yo!<br/>
Fetish Omote no Kioku/Ura no Kioku series<br/>
Kyonyuu Maid Nakadashi Tengoku!<br/>
</td></tr>
@@ -945,6 +962,7 @@ Kitto, Sumiwataru Asairo Yori mo,<br/>
</td></tr>
<tr class="odd last"><td>*.mgr</td><td>-</td><td>No</td></tr>
<tr><td>*.dat</td><td><tt>PAK0</tt></td><td>No</td><td>Studio e.go!</td><td>
GakuPara!! ~Gakuen Paradise!!~<br/>
Izumo<br/>
Izumo 2<br/>
Izumo 2 ~Gakuen Kyousoukyoku~<br/>
@@ -1117,13 +1135,16 @@ Angenehm Platz -Kleiner Garten Sie Erstellen-<br/>
<tr><td>*.zit</td><td><tt>ZT</tt></td><td>No</td><td>Silky's</td><td>
Sweet ~Hanjuku na Tenshi-tachi~<br/>
</td></tr>
<tr class="odd"><td>*.ovk</td><td>-</td><td>No</td><td rowspan="4">RealLive</td><td rowspan="4">
<tr class="odd"><td>*.ovk</td><td>-</td><td>No</td><td rowspan="5">RealLive</td><td rowspan="5">
Devote 2 Ikenai Houkago<br/>
Gakuen Taima! Holy x Moly<br/>
Joi-san no Iitsuke!!<br/>
Shiawase na Ohime-sama<br/>
Shin Genre Esudere!!<br/>
Warui Koto Shitai<br/>
</td></tr>
<tr class="odd"><td>*.nwa</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>*.koe</td><td><tt>KOEPAC</tt></td><td>No</td></tr>
<tr class="odd"><td>*.g00</td><td>-</td><td>No</td></tr>
<tr class="odd last"><td>*.pdt</td><td><tt>PDT10</tt></td><td>No</td></tr>
<tr><td>*.pak<br/>*.pac</td><td><tt>VC製品版</tt><br/><tt>\x01\x00\x00\x00</tt></td><td>No</td><td rowspan="2">Circus</td><td rowspan="2">
@@ -1143,6 +1164,7 @@ Uso x Mote ~Usonko Motemote-shon~<br/>
</td></tr>
<tr class="odd"><td>*.syg</td><td><tt>$SYG</tt></td><td>No</td><td><a title="Risa game platform system">Risa</a></td><td>
Chijoku Yuugi<br/>
Kakure Ecchi<br/>
OL Gari ~Nureta Office~<br/>
Taiiku Souko ~Shoujotachi no Sange~<br/>
</td></tr>
@@ -1181,7 +1203,7 @@ Natsunone -Ring-<br/>
<tr class="odd"><td>*.pac</td><td><tt>MGPK</tt><td>No</td><td>MangaGamer</td><td>
Cartagra<br/>
Kara no Shoujo 2<br/>
</td></td>
</td></tr>
<tr><td>data.NN<br/>ArcNN.dat</td><td>-</td><td>No</td><td>Cyberworks</td><td>
Abunai Kankei<br/>
Aneboku ~Onee-chan wa Bijin 3 Shimai~<br/>
@@ -1322,6 +1344,7 @@ Sakura no Sakukoro<br/>
Tsumi no Hikari Rendezvous<br/>
</td></tr>
<tr class="odd"><td>arc.dat</td><td>-</td><td>No</td><td rowspan="2">AdvSys3</td><td rowspan="2">
Chijoku Tsuma Natsuki<br/>
Jii Tousaku<br/>
Joku Dorei Tsuma<br/>
Okami Tsuma ~Ryokan Baito de no Himitsu~<br/>
@@ -1363,9 +1386,12 @@ Idol★Harem<br/>
Maou no Ingu<br/>
</td></tr>
<tr class="odd"><td>*.bin</td><td><tt>KAR</tt></td><td>No</td><td rowspan="2">Cadath</td><td rowspan="2">
DA Pantsu!!<br/>
Doreijou<br/>
</td></tr>
<tr class="odd last"><td>*.kgf</td><td><tt>KGF</tt></td><td>No</td></tr>
<tr class="odd"><td>*.kgf</td><td><tt>KGF</tt></td><td>No</td></tr>
<tr class="odd"><td>*.arc</td><td><tt>DAF\x1A</tt></td><td>No</td></tr>
<tr class="odd last"><td>*.cgf</td><td><tt>CGF\x1A</tt></td><td>No</td></tr>
<tr><td>*.pfs</td><td><tt>pf8</tt></td><td>No</td><td>Artemis Engine</td><td>
Boku no Elf Onee-san<br/>
</td></tr>
@@ -1381,11 +1407,14 @@ Teri ☆ Mix<br/>
Onepapa ~Onegai PaPa!~<br/>
</td></tr>
<tr class="odd last"><td>*.pb</td><td><tt>PB00</tt></td><td>No</td></tr>
<tr><td>*.cmp</td><td>-</td><td>No</td><td rowspan="2">0verflow</td><td rowspan="2">
<tr><td>*.cmp</td><td>-</td><td>No</td><td rowspan="3">0verflow</td><td rowspan="3">
Large PonPon<br/>
PureMail<br/>
Summer Radish Vacation!!<br/>
Summer Radish Vacation!! 2<br/>
</td></tr>
<tr><td>*.cgd<br/>*.bgd<br/>*.chr</td><td>-</td><td>No</td></tr>
<tr><td>*.cgd<br/>*.bgd<br/>*.chr<br/>*.crgb</td><td>-</td><td>No</td></tr>
<tr class="last"><td>*.adp4</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>*.vfs</td><td><tt>VFS File</tt></td><td>No</td><td>VNSystem</td><td>
Seika no Mori<br/>
</td></tr>
@@ -1419,11 +1448,30 @@ Ryoujoku Joshi Gakuen<br/>
<tr class="odd"><td>*.vpk</td><td><tt>VPK1</tt></td><td>No</td></tr>
<tr class="odd"><td>*.wrc</td><td><tt>WVX0</tt></td><td>No</td></tr>
<tr class="odd"><td>*.wsm</td><td><tt>WSM1</tt><br/><tt>WSM2</tt><br/><tt>WSM3</tt></td><td>No</td></tr>
<tr class="odd"><td>*</td><td><tt>BC</tt></td><td>No</td></tr>
<tr class="odd last"><td>*</td><td><tt>BC</tt></td><td>No</td></tr>
<tr><td>*.pak</td><td><tt>Graphic PackData</tt></td><td>No</td><td rowspan="2">cromwell</td><td rowspan="2">
Ikenie Reijou<br/>
Tremolo<br/>
</td></tr>
<tr><td>*.opk</td><td><tt>VoiceOggPackFile</tt></td><td>No</td></tr>
<tr class="odd"><td>CG</td><td>-</td><td>No</td><td>Software House Parsley</td><td>
Mugen no Meikyuu 3 Type S<br/>
Phantom Knight Mugen no Meikyuu II<br/>
</td></tr>
<tr><td>*.fa2</td><td><tt>FA2</tt></td><td>No</td><td rowspan="2">Foster</td><td rowspan="2">
Mania na Onna
</td></tr>
<tr class="last"><td>*.c24</td><td><tt>C24</tt></td><td>No</td></tr>
<tr class="odd"><td>*.imp</td><td>-</td><td>No</td><td rowspan="2">Black Rainbow</td><td rowspan="2">
From M<br/>
Kannagi<br/>
</td></tr>
<tr class="odd last"><td>*.pak</td><td><tt>CCf"</tt></td><td>No</td></tr>
<tr><td>*.dat</td><td>-</td><td>No</td><td rowspan="3">Valkyria</td><td rowspan="3">
Kangokujou ~Ingyaku no Shihai~<br/>
</td></tr>
<tr><td>*.mg2</td><td><tt>MICOCG01</tt></td><td>No</td></tr>
<tr><td class="last">*.mal</td><td><tt>MICOMSK00</tt></td><td>No</td></tr>
</table>
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
</body>

27
docs/version.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<GARbro>
<Release>
<Version>1.4.26</Version>
<Url>https://github.com/morkt/GARbro/releases/latest</Url>
<Notes>Attempt to extract NOA and QLIE archive keys from game executable files.
New formats:
- Software House Parsley CG archives
- PureMail archives and ADP4 compressed audio
- FA2 archives and C24 images
- RealLive KOEPAC archives
- Black Rainbow IMP and PAK archives
- DAF archives and CGF images
- Valkyria's MG2 and MAL images
- more KiriKiri and ShiinaRio encryption schemes
</Notes>
</Release>
<FormatsData>
<FileVersion>55</FileVersion>
<Url>https://github.com/morkt/GARbro/raw/master/ArcFormats/Resources/Formats.dat</Url>
<Requires>
<Assembly Name="ArcFormats" Version="1.2.29.1274"/>
<Assembly Name="GameRes" Version="1.4.26.238"/>
</Requires>
</FormatsData>
</GARbro>