Compare commits

..

31 Commits

Author SHA1 Message Date
morkt
6e035b7c25 released v1.4.32 2017-11-09 20:27:26 +04:00
morkt
cb536f040e (fPK): implemented archives version 2. 2017-11-09 00:57:32 +04:00
morkt
dcedc79678 (SG): implemented sRGB images type 3. 2017-11-09 00:16:05 +04:00
morkt
1d59b50ea1 (RPA): do not prepend empty header to entries. 2017-11-09 00:14:44 +04:00
morkt
857f4c544b (RPA): added missing Pickle codes for long integers (#104) 2017-11-07 16:00:58 +04:00
morkt
e783d26957 (GCP): non-standard bitmaps workaround. 2017-11-07 15:43:23 +04:00
morkt
62ccf44288 (GRP): archive variation. 2017-11-07 15:43:01 +04:00
morkt
84a94d1636 updated formats. 2017-10-30 08:54:17 +04:00
morkt
0bb857876b (RepiPack): only ASCII characters supposed to be low-cased. 2017-10-29 08:12:46 +04:00
morkt
9d2eb27383 implemented 'RepiPack' archives. 2017-10-28 12:08:48 +04:00
morkt
ac27791ea3 use FormatCatalog.ReadFileList method to read file lists. 2017-10-28 11:49:15 +04:00
morkt
4517bd55e8 (VZY): dispose original stream. 2017-10-28 11:47:39 +04:00
morkt
883cbb2954 (FormatCatalog): added ReadFileList method. 2017-10-28 11:46:03 +04:00
morkt
da1762dc64 (MD5): added final methods. 2017-10-28 11:43:25 +04:00
morkt
64ba3f8294 implemented ALO images and VZY audio. 2017-10-23 08:55:38 +04:00
morkt
703b097de5 (WMA): NAudio has been updated, workaround no longer necessary. 2017-10-07 00:54:49 +04:00
morkt
da61e5e3ba updated nuget packages. 2017-10-07 00:53:28 +04:00
morkt
bfe38d3ef8 updated formats. 2017-10-06 12:32:44 +04:00
morkt
13f53affbb (Csystem): workaround for some incremental images. 2017-10-01 17:59:31 +04:00
morkt
90941a0ba6 updated formats. 2017-09-30 20:18:20 +04:00
morkt
d54305d3ff (Csystem): slight image variation. 2017-09-30 20:14:52 +04:00
morkt
d7d49c010b (KiriKiri.ICrypt): added virtual method EntryReadFilter. 2017-09-27 18:04:42 +04:00
morkt
b713a01031 (XP3): added optional AES decryption. 2017-09-27 15:42:53 +04:00
morkt
99ab258678 (ERI): fixed incremental images with different stride. (#96) 2017-09-26 21:20:21 +04:00
morkt
c29aee53b8 updated formats. 2017-09-22 13:25:05 +04:00
morkt
d4132c2cac (SFA): replaced AosEntry with PackedEntry. 2017-09-22 13:22:21 +04:00
morkt
09793a08d8 variation of ABM bitmaps. 2017-09-17 07:08:58 +04:00
morkt
271ede6f07 implemented FGA archives. 2017-09-17 07:05:41 +04:00
morkt
81be6901a3 updated formats. 2017-09-12 17:40:21 +04:00
morkt
cb2b6e2f43 (KiriKiri): another encryption scheme. 2017-09-12 17:35:11 +04:00
morkt
481b0995a7 implemented LunaSoft PAC archives. 2017-09-12 17:34:12 +04:00
39 changed files with 1355 additions and 391 deletions

View File

@@ -62,6 +62,7 @@
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -93,6 +94,8 @@
<Compile Include="Aoi\ImageAGF.cs" />
<Compile Include="ArcARCX.cs" />
<Compile Include="Artemis\ArcMJA.cs" />
<Compile Include="BeF\AudioVZY.cs" />
<Compile Include="BeF\ImageALO.cs" />
<Compile Include="BlackRainbow\ArcCCF.cs" />
<Compile Include="BlackRainbow\ArcIMP.cs" />
<Compile Include="BlackRainbow\ArcSPPAK.cs" />
@@ -112,8 +115,11 @@
<Compile Include="Kaas\AudioKAAS.cs" />
<Compile Include="Kaguya\ArcAN21.cs" />
<Compile Include="Kaguya\ArcUF.cs" />
<Compile Include="KiriKiri\CzCrypt.cs" />
<Compile Include="Kurumi\ImageGRA.cs" />
<Compile Include="Leaf\ArcPX.cs" />
<Compile Include="Littlewitch\ArcDAT.cs" />
<Compile Include="LunaSoft\ArcPAC.cs" />
<Compile Include="Malie\ArcLIBU.cs" />
<Compile Include="Malie\LibScheme.cs" />
<Compile Include="Malie\MalieEncryption.cs" />
@@ -125,6 +131,7 @@
<Compile Include="Qlie\Encryption.cs" />
<Compile Include="RealLive\ArcKOE.cs" />
<Compile Include="rUGP\AudioRHA.cs" />
<Compile Include="Lilim\ArcFGA.cs" />
<Compile Include="Slg\ArcSPD.cs" />
<Compile Include="Software House Parsley\ArcCG.cs" />
<Compile Include="Artemis\ArcPFS.cs" />

View File

@@ -168,21 +168,9 @@ namespace GameRes.Formats.Neko
{
try
{
var dir = FormatCatalog.Instance.DataDirectory;
var lst_file = Path.Combine (dir, "nekopack.lst");
if (!File.Exists (lst_file))
return new string[0];
using (var input = new StreamReader (lst_file))
{
var names = new List<string>();
string line;
while ((line = input.ReadLine()) != null)
{
if (line.Length > 0)
names.Add (line);
}
return names.ToArray();
}
var names = new List<string>();
FormatCatalog.Instance.ReadFileList ("nekopack.lst", name => names.Add (name));
return names.ToArray();
}
catch (Exception X)
{

View File

@@ -112,43 +112,25 @@ namespace GameRes.Formats
}
/// <summary>
/// Custom implementation of MediaFoundationReader.
/// NAudio uses MFCreateMFByteStreamOnStreamEx API call which is not available in Windows 7.
/// Custom implementation of StreamMediaFoundationReader.
/// Extends it with Duration property.
/// </summary>
internal class CustomMediaFoundationReader : MediaFoundationReader
internal class CustomMediaFoundationReader : StreamMediaFoundationReader
{
private readonly Stream m_stream;
/// <summary>
/// Specifies the duration of a presentation, in 100-nanosecond units.
/// </summary>
public long Duration { get; private set; }
public CustomMediaFoundationReader (Stream stream, MediaFoundationReaderSettings settings = null)
: base (stream, settings)
{
m_stream = stream;
Init (settings);
}
protected override IMFSourceReader CreateReader (MediaFoundationReaderSettings settings)
{
const int MF_SOURCE_READER_ALL_STREAMS = -2;
const int MF_SOURCE_READER_FIRST_AUDIO_STREAM = -3;
IMFByteStream byteStream;
MFCreateMFByteStreamOnStream (new ComStream (m_stream), out byteStream);
var source_reader = MediaFoundationApi.CreateSourceReaderFromByteStream (byteStream);
source_reader.SetStreamSelection (MF_SOURCE_READER_ALL_STREAMS, false);
source_reader.SetStreamSelection (MF_SOURCE_READER_FIRST_AUDIO_STREAM, true);
source_reader.SetCurrentMediaType (MF_SOURCE_READER_FIRST_AUDIO_STREAM, IntPtr.Zero, new MediaType
{
MajorType = MediaTypes.MFMediaType_Audio,
SubType = settings.RequestFloatOutput ? AudioSubtypes.MFAudioFormat_Float : AudioSubtypes.MFAudioFormat_PCM
}.MediaFoundationObject);
var source_reader = base.CreateReader (settings);
Duration = GetDuration (source_reader);
return source_reader;
}
@@ -173,96 +155,5 @@ namespace GameRes.Formats
Marshal.FreeHGlobal (variantPtr);
}
}
[DllImport("mfplat.dll", ExactSpelling = true, PreserveSig = false)]
static extern void MFCreateMFByteStreamOnStream (IStream pStream, out IMFByteStream ppByteStream);
}
/// <summary>
/// Implementation of Com IStream.
/// NAudio implementation is inaccessible (private).
/// </summary>
internal class ComStream : ProxyStream, IStream
{
public ComStream (Stream stream) : base (Synchronized (stream))
{
}
void IStream.Clone (out IStream ppstm)
{
ppstm = null;
}
void IStream.Commit (int grfCommitFlags)
{
Flush();
}
void IStream.CopyTo (IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
{
}
void IStream.LockRegion (long libOffset, long cb, int dwLockType)
{
}
void IStream.Read (byte[] pv, int cb, IntPtr pcbRead)
{
if (!CanRead)
throw new InvalidOperationException ("Stream is not readable.");
int read = Read (pv, 0, cb);
if (pcbRead != IntPtr.Zero)
Marshal.WriteInt32 (pcbRead, read);
}
void IStream.Revert()
{
}
void IStream.Seek (long dlibMove, int dwOrigin, IntPtr plibNewPosition)
{
SeekOrigin origin = (SeekOrigin) dwOrigin;
long val = Seek (dlibMove, origin);
if (plibNewPosition != IntPtr.Zero)
Marshal.WriteInt64 (plibNewPosition, val);
}
void IStream.SetSize (long libNewSize)
{
SetLength (libNewSize);
}
void IStream.Stat (out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
{
const int STGM_READ = 0x00000000;
const int STGM_WRITE = 0x00000001;
const int STGM_READWRITE = 0x00000002;
var tmp = new System.Runtime.InteropServices.ComTypes.STATSTG { type = 2, cbSize = Length, grfMode = 0 };
if (CanWrite && CanRead)
tmp.grfMode |= STGM_READWRITE;
else if (CanRead)
tmp.grfMode |= STGM_READ;
else if (CanWrite)
tmp.grfMode |= STGM_WRITE;
else
throw new ObjectDisposedException ("Stream");
pstatstg = tmp;
}
void IStream.UnlockRegion (long libOffset, long cb, int dwLockType)
{
}
void IStream.Write (byte[] pv, int cb, IntPtr pcbWritten)
{
if (!CanWrite)
throw new InvalidOperationException ("Stream is not writeable.");
Write (pv, 0, cb);
if (pcbWritten != IntPtr.Zero)
Marshal.WriteInt32 (pcbWritten, cb);
}
}
}

View File

@@ -0,0 +1,73 @@
//! \file AudioVZY.cs
//! \date 2017 Oct 22
//! \brief Obfuscated WAVE file.
//
// 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;
namespace GameRes.Formats.BeF
{
[Export(typeof(AudioFormat))]
public class VzyAudio : AudioFormat
{
public override string Tag { get { return "VZY"; } }
public override string Description { get { return "Obfuscated WAVE audio"; } }
public override uint Signature { get { return 0; } }
public override bool CanWrite { get { return false; } }
public override SoundInput TryOpen (IBinaryStream file)
{
var header = file.ReadHeader (0x12);
if (!header.AsciiEqual ("\0\0\0\0"))
return null;
int riff_length = header.ToInt32 (4);
if (file.Length != riff_length+8)
return null;
if (!header.AsciiEqual (8, "\0\0\0\0\0\0\0\0"))
return null;
int header_length = header.ToUInt16 (0x10);
if (header_length < 0x10 || header_length > riff_length)
return null;
header = file.ReadHeader (0x18+header_length);
if (!header.AsciiEqual (0x14+header_length, "data"))
return null;
var header_bytes = new byte[0x10] {
(byte)'R', (byte)'I', (byte)'F', (byte)'F', header[4], header[5], header[6], header[7],
(byte)'W', (byte)'A', (byte)'V', (byte)'E', (byte)'f', (byte)'m', (byte)'t', (byte)' '
};
Stream riff = new StreamRegion (file.AsStream, 0x10);
riff = new PrefixStream (header_bytes, riff);
var wav = new BinaryStream (riff, file.Name);
try
{
return Wav.TryOpen (wav);
}
catch
{
wav.Dispose();
throw;
}
}
}
}

View File

@@ -0,0 +1,76 @@
//! \file ImageALO.cs
//! \date 2017 Oct 22
//! \brief Ancient obfuscated BMP 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.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.BeF
{
[Export(typeof(ImageFormat))]
public class AloFormat : BmpFormat
{
public override string Tag { get { return "ALO"; } }
public override string Description { get { return "Obfuscated bitmap"; } }
public override uint Signature { get { return 0; } }
public override bool CanWrite { get { return true; } }
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
if (!stream.Name.HasExtension (".alo"))
return null;
var header = stream.ReadHeader (2);
if (0 != header[0] || 0 != header[1])
return null;
using (var bmp = OpenAsBitmap (stream))
return base.ReadMetaData (bmp);
}
public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{
using (var bmp = OpenAsBitmap (stream))
return base.Read (bmp, info);
}
IBinaryStream OpenAsBitmap (IBinaryStream input)
{
var header = new byte[2] { (byte)'B', (byte)'M' };
Stream stream = new StreamRegion (input.AsStream, 2, true);
stream = new PrefixStream (header, stream);
return new BinaryStream (stream, input.Name);
}
public override void Write (Stream file, ImageData image)
{
using (var bmp = new MemoryStream())
{
base.Write (bmp, image);
file.WriteByte (0);
file.WriteByte (0);
bmp.Position = 2;
bmp.CopyTo (file);
}
}
}
}

View File

@@ -58,7 +58,7 @@ namespace GameRes.Formats.Cyberworks
{
if (!index.Read())
return null;
return new BellArchive (file, this, index.Dir, null);
return ArchiveFromDir (file, index.Dir, index.HasImages);
}
}
}

View File

@@ -167,11 +167,19 @@ namespace GameRes.Formats.Cyberworks
var header = ReadHeader();
if (0 == Info.Width || Info.Width >= 0x8000 || 0 == Info.Height || Info.Height >= 0x8000)
throw new InvalidFormatException();
int unpacked_size = header[5];
if (unpacked_size <= 0)
throw new InvalidFormatException();
int flags = header[0];
int unpacked_size = header[5];
int bits_size = header[7];
if (unpacked_size <= 0)
{
if (0 == unpacked_size && 0 == header[6]
&& (1 == (flags & 1) && Baseline != null))
{
UnpackV6NoAlpha (bits_size);
return;
}
throw new InvalidFormatException();
}
int data_offset = bits_size * 2;
if (0 == flags)
CopyV0 (unpacked_size);
@@ -399,6 +407,35 @@ namespace GameRes.Formats.Cyberworks
}
}
void UnpackV6NoAlpha (int bits_size)
{
var rgb_map = m_input.ReadBytes (bits_size);
int plane_size = Math.Min (Baseline.Length, bits_size*8);
m_output = Baseline;
if (m_info.Width * m_info.Height * 3 == m_output.Length)
Info.BPP = 24;
else
Info.BPP = 32;
int bit = 1;
int bit_src = 0;
int dst = 0;
int pixel_size = Info.BPP / 8;
for (int i = 0; i < plane_size; ++i)
{
if ((bit & rgb_map[bit_src]) != 0)
{
m_input.Read (m_output, dst, 3);
}
dst += pixel_size;
bit <<= 1;
if (0x100 == bit)
{
++bit_src;
bit = 1;
}
}
}
int GetInt ()
{
byte a = m_input.ReadUInt8();

View File

@@ -73,6 +73,7 @@ namespace GameRes.Formats.Entis
PtrProcedure[] m_pfnColorOperation;
byte[] m_src_frame;
public EriMetaData Info { get { return m_info; } }
public byte[] Data { get { return m_output; } }
public PixelFormat Format { get; private set; }
public int Stride { get { return Math.Abs (m_dwBytesPerLine); } }
@@ -150,6 +151,35 @@ namespace GameRes.Formats.Entis
};
}
internal void AddImageBuffer (EriReader src_reader)
{
var dst_reader = this;
var dst_img = dst_reader.Data;
var src_img = src_reader.Data;
int src_bpp = (src_reader.Info.BPP + 7) / 8;
int dst_bpp = (dst_reader.Info.BPP + 7) / 8;
bool has_alpha = src_bpp == 4 && src_bpp == dst_bpp;
int src_stride = src_reader.Stride;
int dst_stride = dst_reader.Stride;
int height = (int)dst_reader.Info.Height;
int width = (int)dst_reader.Info.Width;
for (int y = 0; y < height; ++y)
{
int src = src_stride * y;
int dst = dst_stride * y;
for (int x = 0; x < width; ++x)
{
dst_img[dst ] += src_img[src ];
dst_img[dst+1] += src_img[src+1];
dst_img[dst+2] += src_img[src+2];
if (has_alpha)
dst_img[dst+3] += src_img[src+3];
dst += dst_bpp;
src += src_bpp;
}
}
}
private void InitializeLossless ()
{
if (0 != m_info.BlockingDegree)

View File

@@ -296,7 +296,7 @@ namespace GameRes.Formats.Entis
throw new FileNotFoundException ("Referenced image not found", ref_file);
ref_info.FileName = ref_file;
var ref_reader = ReadImageData (ref_src, ref_info);
AddImageBuffer (meta, reader.Data, ref_info, ref_reader.Data);
reader.AddImageBuffer (ref_reader);
}
}
}
@@ -304,25 +304,6 @@ namespace GameRes.Formats.Entis
return reader;
}
void AddImageBuffer (EriMetaData dst_info, byte[] dst_img, EriMetaData src_info, byte[] src_img)
{
int src_bpp = (src_info.BPP + 7) / 8;
int dst_bpp = (dst_info.BPP + 7) / 8;
bool has_alpha = src_bpp == 4 && src_bpp == dst_bpp;
int dst = 0;
int src = 0;
while (dst < dst_img.Length)
{
dst_img[dst ] += src_img[src ];
dst_img[dst+1] += src_img[src+1];
dst_img[dst+2] += src_img[src+2];
if (has_alpha)
dst_img[dst+3] += src_img[src+3];
dst += dst_bpp;
src += src_bpp;
}
}
static readonly Regex s_TagRe = new Regex (@"^\s*#\s*(\S+)");
Dictionary<string, string> ParseTagInfo (string desc)

View File

@@ -85,7 +85,13 @@ namespace GameRes.Formats.ExHibit
bool arc_found = false;
for (int i = 0; i < res_count && index_offset < toc_file.MaxOffset; ++i)
{
if (toc_file.View.ReadInt32 (index_offset) == arc_index)
int num = toc_file.View.ReadInt32 (index_offset);
if (0x01000000 == num)
{
index_offset += 4;
num = toc_file.View.ReadInt32 (index_offset);
}
if (num == arc_index)
{
arc_found = true;
break;

View File

@@ -2,7 +2,7 @@
//! \date Mon Sep 12 01:39:49 2016
//! \brief 'fPK' resource archive.
//
// Copyright (C) 2016 by morkt
// 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
@@ -44,75 +44,95 @@ namespace GameRes.Formats.Ivory
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public PakOpener ()
{
Signatures = new uint[] { 0x204B5066, 0x324B5066 };
}
public override ArcFile TryOpen (ArcView file)
{
if (file.MaxOffset != file.View.ReadUInt32 (4))
return null;
int version = '2' == file.View.ReadByte (3) ? 2 : 1;
long base_offset = 0;
List<Entry> dir = null;
bool got_names = false;
long offset = 8;
while (offset < file.MaxOffset)
byte[] names = null;
using (var stream = file.CreateStream())
{
var id = new AsciiString (file.View.ReadBytes (offset, 4));
uint section_size = file.View.ReadUInt32 (offset+4);
if (0 == section_size)
Func<IBinaryStream, long> read_long;
if (2 == version)
read_long = s => s.ReadInt64();
else
read_long = s => s.ReadUInt32();
stream.Position = 4;
if (file.MaxOffset != read_long (stream))
return null;
uint header_size = file.View.ReadUInt32 (offset+8);
if ("cLST" == id)
for (;;)
{
int count = file.View.ReadInt32 (offset+0x10);
if (!IsSaneCount (count))
long section_start = stream.Position;
var id_bytes = stream.ReadBytes (4);
if (0 == id_bytes.Length)
break;
var id = new AsciiString (id_bytes);
var section_size = read_long (stream);
var header_size = read_long (stream);
if (section_size < 4 || header_size > section_size)
return null;
uint key = file.View.ReadUInt32 (offset+0x14);
var clst = file.View.ReadBytes (offset+header_size, section_size - header_size);
Decrypt (clst, key);
dir = new List<Entry> (count);
int index_offset = 0;
for (int i = 0; i < count; ++i)
var content_pos = section_start + header_size;
if ("cLST" == id)
{
var entry = new PkEntry {
NameOffset = LittleEndian.ToInt32 (clst, index_offset),
Offset = LittleEndian.ToUInt32 (clst, index_offset+4),
Size = LittleEndian.ToUInt32 (clst, index_offset+8),
};
dir.Add (entry);
index_offset += 12;
stream.ReadUInt32();
int count = stream.ReadInt32();
if (!IsSaneCount (count))
return null;
uint key = stream.ReadUInt32();
stream.Position = content_pos;
var clst = stream.ReadBytes ((int)(section_size - header_size));
Decrypt (clst, key);
dir = new List<Entry> (count);
using (var index = new BinMemoryStream (clst))
{
for (int i = 0; i < count; ++i)
{
var entry = new PkEntry {
NameOffset = (int)read_long (index),
Offset = (long)read_long (index),
Size = (uint)read_long (index),
};
dir.Add (entry);
}
}
}
}
else if ("cNAM" == id)
{
if (null == dir)
return null;
uint key = file.View.ReadUInt32 (offset+0x10);
var cnam = file.View.ReadBytes (offset+header_size, section_size - header_size);
Decrypt (cnam, key);
foreach (PkEntry entry in dir)
else if ("cNAM" == id)
{
var name = Binary.GetCString (cnam, entry.NameOffset);
entry.Name = name;
if (name.HasExtension (".px"))
entry.Type = "audio";
else
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
if (null == dir)
return null;
stream.ReadUInt32();
uint key = stream.ReadUInt32();
stream.Position = content_pos;
names = stream.ReadBytes ((int)(section_size - header_size));
Decrypt (names, key);
}
got_names = true;
else if ("cDAT" == id)
{
base_offset = content_pos;
}
stream.Position = section_start + section_size;
}
else if ("cDAT" == id)
{
base_offset = offset + header_size;
}
offset += section_size;
}
if (null == dir || !got_names || 0 == base_offset)
if (null == dir || null == names || 0 == base_offset)
return null;
foreach (var entry in dir)
foreach (PkEntry entry in dir)
{
entry.Offset += base_offset;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
var name = Binary.GetCString (names, entry.NameOffset);
entry.Name = name;
if (name.HasExtension (".px"))
entry.Type = "audio";
else
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
}
return new ArcFile (file, this, dir);
}

View File

@@ -169,6 +169,7 @@ namespace GameRes.Formats.Ivory
else
UnpackV2();
break;
case 3: UnpackV3(); break;
default:
throw new NotImplementedException (string.Format ("sRGB image type {0} not implemented", m_info.RgbMode));
}
@@ -343,6 +344,45 @@ namespace GameRes.Formats.Ivory
}
}
void UnpackV3 ()
{
Format = 3 == m_channels ? PixelFormats.Bgr24 : PixelFormats.Bgra32;
m_stride = m_width * m_channels;
m_output = new byte[m_stride * m_height];
var index = new int[m_height];
for (int i = 0; i < m_height; ++i)
index[i] = m_input.ReadInt32();
var data_pos = m_input.Position;
int dst = 0;
for (int y = 0; y < m_height; ++y)
{
m_input.Position = data_pos + index[y];
for (int x = 0; x < m_width; )
{
int ctl = m_input.ReadUInt8();
int count = ctl & 0x3F;
if (0 != (ctl & 0x40))
{
count <<= 8;
count |= m_input.ReadUInt8();
}
x += count;
count *= m_channels;
if (0 != (ctl & 0x80))
{
m_input.Read (m_output, dst, m_channels);
if (count > m_channels)
Binary.CopyOverlapped (m_output, dst, dst+m_channels, count - m_channels);
}
else
{
m_input.Read (m_output, dst, count);
}
dst += count;
}
}
}
#region IDisposable Members
public void Dispose ()
{

View File

@@ -2,7 +2,7 @@
//! \date Wed Jul 16 13:58:17 2014
//! \brief KiriKiri engine archive implementation.
//
// Copyright (C) 2014-2016 by morkt
// Copyright (C) 2014-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
@@ -344,90 +344,7 @@ NextEntry:
else
input = new Xp3Stream (arc.File, xp3_entry);
if (xp3_entry.UnpackedSize <= 5 || "audio" == entry.Type)
return input;
var header = new byte[5];
input.Read (header, 0, 5);
if (0x184D2204 == header.ToInt32 (0)) // LZ4 magic
{
// assume no scripts are compressed using LZ4, return decompressed stream right away
return DecompressLz4 (xp3_entry, header, input);
}
if (0xFE == header[0] && 0xFE == header[1] && header[2] < 3 && 0xFF == header[3] && 0xFE == header[4])
return DecryptScript (header[2], input, xp3_entry.UnpackedSize);
if (!input.CanSeek)
return new PrefixStream (header, input);
input.Position = 0;
return input;
}
Stream DecompressLz4 (Xp3Entry entry, byte[] header, Stream input)
{
if (header.Length != 5)
throw new ArgumentException ("Invalid header length for DecompressLz4", "header");
var info = new Lz4FrameInfo (header[4]);
info.SetBlockSize (input.ReadByte());
if (info.HasContentLength)
{
input.Read (header, 0, 4);
long length = header.ToUInt32 (0);
input.Read (header, 0, 4);
length |= (long)header.ToUInt32 (0) << 32;
info.OriginalLength = length;
entry.UnpackedSize = (uint)length;
entry.IsPacked = true;
}
if (info.HasDictionary)
{
input.Read (header, 0, 4);
info.DictionaryId = header.ToInt32 (0);
}
input.ReadByte(); // skip descriptor checksum
return new Lz4Stream (input, info);
}
Stream DecryptScript (int enc_type, Stream input, uint unpacked_size)
{
using (var reader = new BinaryReader (input, Encoding.Unicode, true))
{
if (2 == enc_type)
{
reader.ReadInt64(); // packed_size
reader.ReadInt64(); // unpacked_size
return new ZLibStream (input, CompressionMode.Decompress);
}
var output = new MemoryStream ((int)unpacked_size+2);
using (var writer = new BinaryWriter (output, Encoding.Unicode, true))
{
writer.Write ('\xFEFF'); // BOM
int c;
if (1 == enc_type)
{
while ((c = reader.Read()) != -1)
{
c = (c & 0xAAAA) >> 1 | (c & 0x5555) << 1;
writer.Write ((char)c);
}
}
else
{
while ((c = reader.Read()) != -1)
{
if (c >= 0x20)
{
c = c ^ (((c & 0xFE) << 8) ^ 1);
writer.Write ((char)c);
}
}
}
}
output.Position = 0;
input.Dispose();
return output;
}
return xp3_entry.Cipher.EntryReadFilter (xp3_entry, input);
}
public override ResourceOptions GetDefaultOptions ()

View File

@@ -2,7 +2,7 @@
//! \date Thu Feb 04 12:08:40 2016
//! \brief KiriKiri engine encryption algorithms.
//
// Copyright (C) 2016 by morkt
// 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
@@ -27,6 +27,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.KiriKiri
@@ -83,6 +85,96 @@ namespace GameRes.Formats.KiriKiri
else
return null;
}
/// <summary>
/// Post-process entry stream.
/// </summary>
public virtual Stream EntryReadFilter (Xp3Entry entry, Stream input)
{
if (entry.UnpackedSize <= 5 || "audio" == entry.Type)
return input;
var header = new byte[5];
input.Read (header, 0, 5);
if (0x184D2204 == header.ToInt32 (0)) // LZ4 magic
{
// assume no scripts are compressed using LZ4, return decompressed stream right away
return DecompressLz4 (entry, header, input);
}
if (0xFE == header[0] && 0xFE == header[1] && header[2] < 3 && 0xFF == header[3] && 0xFE == header[4])
return DecryptScript (header[2], input, entry.UnpackedSize);
if (!input.CanSeek)
return new PrefixStream (header, input);
input.Position = 0;
return input;
}
internal Stream DecompressLz4 (Xp3Entry entry, byte[] header, Stream input)
{
if (header.Length != 5)
throw new ArgumentException ("Invalid header length for DecompressLz4", "header");
var info = new Lz4FrameInfo (header[4]);
info.SetBlockSize (input.ReadByte());
if (info.HasContentLength)
{
input.Read (header, 0, 4);
long length = header.ToUInt32 (0);
input.Read (header, 0, 4);
length |= (long)header.ToUInt32 (0) << 32;
info.OriginalLength = length;
entry.UnpackedSize = (uint)length;
entry.IsPacked = true;
}
if (info.HasDictionary)
{
input.Read (header, 0, 4);
info.DictionaryId = header.ToInt32 (0);
}
input.ReadByte(); // skip descriptor checksum
return new Lz4Stream (input, info);
}
internal Stream DecryptScript (int enc_type, Stream input, uint unpacked_size)
{
using (var reader = new BinaryReader (input, Encoding.Unicode, true))
{
if (2 == enc_type)
{
reader.ReadInt64(); // packed_size
reader.ReadInt64(); // unpacked_size
return new ZLibStream (input, CompressionMode.Decompress);
}
var output = new MemoryStream ((int)unpacked_size+2);
using (var writer = new BinaryWriter (output, Encoding.Unicode, true))
{
writer.Write ('\xFEFF'); // BOM
int c;
if (1 == enc_type)
{
while ((c = reader.Read()) != -1)
{
c = (c & 0xAAAA) >> 1 | (c & 0x5555) << 1;
writer.Write ((char)c);
}
}
else
{
while ((c = reader.Read()) != -1)
{
if (c >= 0x20)
{
c = c ^ (((c & 0xFE) << 8) ^ 1);
writer.Write ((char)c);
}
}
}
}
output.Position = 0;
input.Dispose();
return output;
}
}
}
[Serializable]
@@ -744,7 +836,7 @@ namespace GameRes.Formats.KiriKiri
Decrypt (entry, offset, buffer, pos, count);
}
IEnumerable<byte> GetKey (uint hash)
internal IEnumerable<byte> GetKey (uint hash)
{
hash = (hash ^ m_seed) & 0x7FFFFFFF;
hash = hash << 31 | hash;
@@ -984,7 +1076,7 @@ namespace GameRes.Formats.KiriKiri
}
[Serializable]
public class KissCrypt : ICrypt
public class KissCrypt : CzCrypt
{
public override void Decrypt (Xp3Entry entry, long offset, byte[] data, int pos, int count)
{
@@ -1116,21 +1208,12 @@ namespace GameRes.Formats.KiriKiri
void ReadNames ()
{
var dir = FormatCatalog.Instance.DataDirectory;
var names_file = Path.Combine (dir, FileListName);
var names = new Dictionary<uint, string>();
try
{
using (var reader = new StreamReader (names_file))
{
for (;;)
{
var name = reader.ReadLine();
if (null == name)
break;
names[GetNameHash (name)] = name;
}
}
FormatCatalog.Instance.ReadFileList (FileListName, name => {
names[GetNameHash (name)] = name;
});
}
catch (Exception X)
{
@@ -1142,4 +1225,29 @@ namespace GameRes.Formats.KiriKiri
[NonSerialized]
Dictionary<uint, string> KnownNames = null;
}
[Serializable]
public class MadoCrypt : AkabeiCrypt
{
public MadoCrypt (uint seed) : base (seed)
{
}
public override byte Decrypt (Xp3Entry entry, long offset, byte value)
{
int key_pos = (int)offset % 0x1F;
var key = GetKey (entry.Hash).ElementAt (key_pos);
return (byte)(value ^ key);
}
public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
{
var key = GetKey (entry.Hash).ToArray();
int key_pos = (int)offset;
for (int i = 0; i < count; ++i)
{
buffer[pos+i] ^= key[key_pos++ % 0x1F];
}
}
}
}

View File

@@ -0,0 +1,133 @@
//! \file CzCrypt.cs
//! \date 2017 Sep 27
//! \brief implementation of cZLIB extraction filter for KiriKiri engine.
//
// 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.IO;
using System.Security.Cryptography;
using GameRes.Compression;
namespace GameRes.Formats.KiriKiri
{
/// <summary>
/// cZLIB entry filter.
/// </summary>
[Serializable]
public abstract class CzCrypt : ICrypt
{
const uint CzMagic = 0xA590D7FDu;
const uint CzIvSeed = 0xBFBFBFBFu;
static readonly byte[] CzHeaderKey = { 0x9D, 0x1D, 0x9A, 0xF2 };
static readonly byte[] CzDefaultKey = {
0x91, 0x10, 0xFC, 0x75, 0x45, 0x8F, 0xB5, 0xE6, 0xFE, 0xAC, 0xBA, 0x44, 0x76, 0x58, 0xC2, 0x1A
};
public override Stream EntryReadFilter (Xp3Entry entry, Stream input)
{
if (entry.UnpackedSize <= 15 || "audio" == entry.Type)
return input;
var header = new byte[15];
input.Read (header, 0, 15);
if (CzMagic == header.ToUInt32 (0))
{
var type = new char[3] {
(char)(header[4] ^ 0x11),
(char)(header[5] ^ 0x7F),
(char)(header[6] ^ 0x9A)
};
byte key = (byte)type[0];
int unpacked_size = CzDecryptInt (header, 7, key);
int packed_size = CzDecryptInt (header, 11, key);
if (packed_size < entry.UnpackedSize && 0 == ((packed_size-5) & 0xF))
{
var data = new byte[packed_size];
input.Read (data, 0, packed_size);
input.Dispose();
data = CzDecryptData (data);
input = new BinMemoryStream (data);
if ('C' == type[0])
input = new ZLibStream (input, CompressionMode.Decompress);
return input;
}
}
if (!input.CanSeek)
return new PrefixStream (header, input);
input.Position = 0;
return input;
}
static int CzDecryptInt (byte[] data, int offset, byte key)
{
for (int i = 0; i < 4; ++i)
{
data[offset+i] ^= (byte)(key ^ CzHeaderKey[i]);
}
return data.ToInt32 (offset);
}
static byte[] CzDecryptData (byte[] data)
{
int padded_size = data.Length - 5;
int original_size = padded_size - (data[padded_size+1] ^ data[padded_size]);
uint iv_seed = data.ToUInt32 (padded_size+1) ^ CzIvSeed;
using (var aes = Aes.Create())
{
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Zeros;
aes.Key = CzDefaultKey;
aes.IV = CzCreateIV (iv_seed);
using (var enc = new MemoryStream (data, 0, padded_size))
using (var dec = new InputCryptoStream (enc, aes.CreateDecryptor()))
{
var original = new byte[original_size];
dec.Read (original, 0, original_size);
return original;
}
}
}
static byte[] CzCreateIV (uint seed)
{
var state = new uint[4];
state[0] = 123456789; // field_0
state[1] = 972436830; // field_4
state[2] = 524018621; // field_8
state[3] = seed; // field_C
var iv = new byte[16];
for (int i = 0; i < 16; ++i)
{
uint a = state[3];
uint b = state[0] ^ (state[0] << 11);
state[0] = state[1];
state[1] = state[2];
state[2] = a;
state[3] = b ^ a ^ ((b ^ (a >> 11)) >> 8);
iv[i] = (byte)state[3];
}
return iv;
}
}
}

View File

@@ -28,7 +28,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using GameRes.Utility;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Lilim
{
@@ -155,14 +155,40 @@ namespace GameRes.Formats.Lilim
protected override ImageData GetImageData ()
{
if (2 == m_info.Mode)
switch (m_info.Mode)
{
m_bpp = 32;
case 8:
case -8:
m_input.Position = 0x36;
var palette = ImageFormat.ReadPalette (m_input.AsStream);
var bitmap_size = m_info.Width * m_info.Height;
m_output = new byte[bitmap_size];
m_input.Position = m_info.BaseOffset;
if (8 == m_info.Mode)
{
m_bpp = 8;
if (m_output.Length != m_input.Read (m_output, 0, m_output.Length))
throw new EndOfStreamException();
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output);
}
else
{
var alpha = new byte[bitmap_size];
UnpackStream8 (m_input, m_output, alpha);
m_output = ApplyAlpha (m_output, alpha, palette);
m_bpp = 32;
}
break;
case 2:
m_bpp = 32;
m_input.Position = m_info.FrameOffset;
m_output = UnpackV2 (m_input);
}
else if (1 == m_info.Mode || 32 == m_info.Mode || 24 == m_info.Mode)
{
break;
case 1:
case 24:
case 32:
if (1 == m_info.Mode)
m_bpp = 24;
else
@@ -180,9 +206,11 @@ namespace GameRes.Formats.Lilim
UnpackStream24 (m_input, m_output, total_length);
else
UnpackStream32 (m_input, m_output, total_length);
}
else
break;
default:
throw new NotImplementedException();
}
if (0 != m_info.FrameOffset)
m_output = Unpack();
PixelFormat format = 24 == m_bpp ? PixelFormats.Bgr24 : PixelFormats.Bgra32;
@@ -319,5 +347,50 @@ namespace GameRes.Formats.Lilim
src += frame_w * pixel_size;
}
}
void UnpackStream8 (IBinaryStream input, byte[] output, byte[] alpha)
{
int alpha_dst = 0;
int dst = 0;
while (dst < output.Length)
{
byte rle = input.ReadUInt8();
if (0 == rle)
{
int skip = input.ReadUInt8();
dst += skip;
alpha_dst += skip;
}
else if (rle != 0xFF)
{
output[dst++] = input.ReadUInt8();
alpha[alpha_dst++] = rle;
}
else
{
int count = input.ReadUInt8();
input.Read (output, dst, count);
dst += count;
for (int i = 0; i < count; ++i)
alpha[alpha_dst++] = 0xFF;
}
}
}
byte[] ApplyAlpha (byte[] input, byte[] alpha, BitmapPalette palette)
{
var colors = palette.Colors;
var pixels = new byte[input.Length * 4];
int dst = 0;
for (int i = 0; i < input.Length; ++i)
{
var color = colors[input[i]];
pixels[dst++] = color.B;
pixels[dst++] = color.G;
pixels[dst++] = color.R;
pixels[dst++] = alpha[i];
}
return pixels;
}
}
}

View File

@@ -28,15 +28,9 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using GameRes.Utility;
namespace GameRes.Formats.Lilim
{
internal class AosEntry : Entry
{
public bool IsCompressed;
}
[Export(typeof(ArchiveFormat))]
public class AosOpener : ArchiveFormat
{
@@ -56,9 +50,7 @@ namespace GameRes.Formats.Lilim
uint first_offset = file.View.ReadUInt32 (0x10);
if (first_offset >= file.MaxOffset || 0 != (first_offset & 0x1F))
return null;
var name_buf = new byte[0x10];
if (0x10 != file.View.Read (first_offset, name_buf, 0, 0x10))
return null;
var name_buf = file.View.ReadBytes (first_offset, 0x10);
if (!name_buf.SequenceEqual (IndexLink) && !name_buf.SequenceEqual (IndexEnd))
return null;
@@ -81,15 +73,14 @@ namespace GameRes.Formats.Lilim
if (-1 == name_length)
name_length = name_buf.Length;
var name = Encodings.cp932.GetString (name_buf, 0, name_length);
var entry = FormatCatalog.Instance.Create<AosEntry> (name);
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = file.View.ReadUInt32 (current_offset+0x10);
entry.Size = file.View.ReadUInt32 (current_offset+0x14);
current_offset += 0x20;
entry.Offset += current_offset;
if (!entry.CheckPlacement (file.MaxOffset))
return null;
if (name.HasExtension (".scr"))
entry.IsCompressed = true;
entry.IsPacked = name.HasExtension (".scr");
dir.Add (entry);
}
}
@@ -98,14 +89,13 @@ namespace GameRes.Formats.Lilim
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var aent = entry as AosEntry;
if (null == aent || !aent.IsCompressed)
var aent = entry as PackedEntry;
if (null == aent || !aent.IsPacked)
return base.OpenEntry (arc, entry);
uint unpacked_size = arc.File.View.ReadUInt32 (entry.Offset);
var packed = new byte[entry.Size-4];
arc.File.View.Read (entry.Offset+4, packed, 0, (uint)packed.Length);
var unpacked = new byte[unpacked_size];
aent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset);
var packed = arc.File.View.ReadBytes (entry.Offset+4, entry.Size-4);
var unpacked = new byte[aent.UnpackedSize];
var decoder = new HuffmanDecoder (packed, unpacked);
decoder.Unpack();
@@ -148,16 +138,16 @@ namespace GameRes.Formats.Lilim
var name = file.View.ReadString (index_offset, 0x20);
if (0 == name.Length)
return null;
var entry = FormatCatalog.Instance.Create<AosEntry> (name);
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = base_offset + file.View.ReadUInt32 (index_offset+0x20);
entry.Size = file.View.ReadUInt32 (index_offset+0x24);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
if (name.HasExtension (".scr"))
entry.IsCompressed = true;
entry.IsPacked = true;
else if (name.HasExtension (".cmp"))
{
entry.IsCompressed = true;
entry.IsPacked = true;
entry.Name = Path.ChangeExtension (entry.Name, ".abm");
entry.Type = "image";
}

View File

@@ -0,0 +1,83 @@
//! \file ArcFGA.cs
//! \date 2017 Sep 16
//! \brief SFA 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.Collections.Generic;
using System.ComponentModel.Composition;
using GameRes.Utility;
namespace GameRes.Formats.Lilim
{
[Export(typeof(ArchiveFormat))]
public class FgaOpener : AosOpener
{
public override string Tag { get { return "FGA"; } }
public override string Description { get { return "SFA engine 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.Name.HasExtension (".fga"))
return null;
uint index_offset = 0;
var index_buffer = file.View.ReadBytes (index_offset, 0x318);
if (0x318 != index_buffer.Length)
return null;
var dir = new List<Entry> (0x20);
int pos = 0;
while (pos < index_buffer.Length)
{
if (0 == index_buffer[pos])
break;
if (0xFF == index_buffer[pos])
{
uint next_offset = index_buffer.ToUInt32 (pos+0xC);
if (next_offset <= index_offset || next_offset >= file.MaxOffset)
return null;
index_offset = next_offset;
if (0x318 != file.View.Read (index_offset, index_buffer, 0, 0x318))
return null;
pos = 0;
}
else
{
var name = Binary.GetCString (index_buffer, pos, 0xC);
var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = index_buffer.ToUInt32 (pos+0xC);
entry.Size = index_buffer.ToUInt32 (pos+0x10);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.IsPacked = name.HasExtension (".scr");
dir.Add (entry);
pos += 0x18;
}
}
if (0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
}
}
}

View File

@@ -43,8 +43,9 @@ namespace GameRes.Formats.Lilim
var header = stream.ReadHeader (0x46);
if ('B' != header[0] || 'M' != header[1])
return null;
int type = (sbyte)header[0x1C];
int type = header.ToInt16 (0x1C);
uint frame_offset;
int bpp = 24;
if (1 == type || 2 == type)
{
int count = header.ToUInt16 (0x3A);
@@ -52,12 +53,14 @@ namespace GameRes.Formats.Lilim
return null;
frame_offset = header.ToUInt32 (0x42);
}
else if (32 == type || 24 == type)
else if (32 == type || 24 == type || 8 == type || -8 == type)
{
uint unpacked_size = header.ToUInt32 (2);
if (0 == unpacked_size || unpacked_size == stream.Length) // probably an ordinary bmp file
return null;
frame_offset = header.ToUInt32 (0xA);
if (8 == type)
bpp = 8;
}
else
return null;
@@ -67,7 +70,7 @@ namespace GameRes.Formats.Lilim
{
Width = header.ToUInt32 (0x12),
Height = header.ToUInt32 (0x16),
BPP = 24,
BPP = bpp,
Mode = type,
BaseOffset = frame_offset,
};

View File

@@ -0,0 +1,267 @@
//! \file ArcDAT.cs
//! \date 2017 Oct 27
//! \brief RepiPack 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.Linq;
using System.Text;
using GameRes.Compression;
using GameRes.Cryptography;
using GameRes.Utility;
namespace GameRes.Formats.Littlewitch
{
[Serializable]
public class RepiScheme : ResourceScheme
{
public IDictionary<string, uint[]> KnownSchemes;
}
internal class RepiEntry : PackedEntry
{
public bool HasEncryptionKey;
public byte[] CreateKey ()
{
var name_bytes = DatOpener.ToLowerAscii (Name);
int name_length = name_bytes.Length;
var md5 = new MD5();
Array.Reverse (name_bytes);
var key = new byte[1024];
int key_pos = 0;
for (int i = 0; i < 64; ++i)
{
int name_pos = i % name_length;
md5.Update (name_bytes, name_pos, name_length - name_pos);
md5.Final();
Buffer.BlockCopy (md5.State, 0, key, key_pos, 16);
key_pos += 16;
}
return key;
}
}
[Export(typeof(ArchiveFormat))]
public class DatOpener : ArchiveFormat
{
public override string Tag { get { return "DAT/RepiPack"; } }
public override string Description { get { return "Littlewitch engine resource archive"; } }
public override uint Signature { get { return 0x69706552; } } // 'Repi'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
static readonly string ListFileName = "littlewitch.lst";
public override ArcFile TryOpen (ArcView file)
{
if (!file.View.AsciiEqual (4, "Pack"))
return null;
int version = file.View.ReadInt32 (8);
if (version != 5)
return null;
uint name_length = file.View.ReadUInt32 (0xC);
if (name_length < 4)
return null;
uint name_key = file.View.ReadUInt32 (0x10);
var key = FindKey (file.Name, name_key);
if (null == key)
return null;
int count = file.View.ReadInt32 (0x10 + name_length);
if (!IsSaneCount (count))
return null;
var index = file.View.ReadBytes (0x14 + name_length, (uint)count * 0x20);
int pos = 0;
var dir = new List<Entry> (count);
var name_builder = new StringBuilder();
for (int i = 0; i < count; ++i)
{
DecryptData (index, pos, 0x20, key[2], key[1]);
var entry = EntryFromMd5 (index, pos, name_builder);
entry.Offset = index.ToUInt32 (pos+0x10);
entry.Size = index.ToUInt32 (pos+0x14);
entry.UnpackedSize = index.ToUInt32 (pos+0x18);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.IsPacked = entry.Size != entry.UnpackedSize;
dir.Add (entry);
pos += 0x20;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var rent = entry as RepiEntry;
if (null == rent || !rent.HasEncryptionKey)
return arc.File.CreateStream (entry.Offset, entry.Size);
var key = rent.CreateKey();
uint enc_length = Math.Min ((uint)key.Length, entry.Size);
byte[] encrypted = arc.File.View.ReadBytes (entry.Offset, enc_length);
DecryptEntry (encrypted, key);
Stream input;
if (enc_length == entry.Size)
{
input = new BinMemoryStream (encrypted, entry.Name);
}
else
{
input = arc.File.CreateStream (entry.Offset + enc_length, entry.Size - enc_length);
input = new PrefixStream (encrypted, input);
}
if (rent.IsPacked)
{
input = new LzssStream (input);
}
return input;
}
static void DecryptEntry (byte[] data, byte[] key)
{
for (int i = 0; i < data.Length; ++i)
{
data[i] ^= key[i];
}
}
static unsafe void DecryptData (byte[] data, int pos, int length, uint key, uint seed)
{
if (pos < 0 || pos + length > data.Length)
throw new ArgumentOutOfRangeException ("pos", "Invalid byte array index.");
fixed (byte* data8 = &data[pos])
{
uint* data32 = (uint*)data8;
for (int count = length / 4; count > 0; --count)
{
*data32 ^= key;
key += Binary.RotL (*data32, 16) ^ seed;
data32++;
}
}
}
RepiEntry EntryFromMd5 (byte[] data, int pos, StringBuilder builder)
{
var key = new CowArray<byte> (data, pos, 16).ToArray();
string name;
if (KnownNames.TryGetValue (key, out name))
{
var entry = FormatCatalog.Instance.Create<RepiEntry> (name);
entry.HasEncryptionKey = true;
return entry;
}
builder.Clear();
for (int i = 0; i < 16; ++i)
{
builder.AppendFormat ("{0:x2}", key[i]);
}
return new RepiEntry { Name = builder.ToString() };
}
static uint[] FindKey (string arc_name, uint arc_key)
{
arc_name = Path.GetFileName (arc_name);
var name_bytes = Encodings.cp932.GetBytes (arc_name);
arc_key ^= name_bytes.ToUInt32 (0);
return DatScheme.KnownSchemes.Values.FirstOrDefault (k => k[0] == arc_key);
}
static RepiScheme DatScheme = new RepiScheme { KnownSchemes = new Dictionary<string, uint[]>() };
public override ResourceScheme Scheme
{
get { return DatScheme; }
set { DatScheme = (RepiScheme)value; }
}
internal Dictionary<byte[], string> KnownNames { get { return s_known_file_names.Value; } }
static Lazy<Dictionary<byte[], string>> s_known_file_names = new Lazy<Dictionary<byte[], string>> (ReadFileList);
static Dictionary<byte[], string> ReadFileList ()
{
var dict = new Dictionary<byte[], string> (new Md5Comparer());
try
{
var md5 = new MD5();
FormatCatalog.Instance.ReadFileList (ListFileName, name => {
var name_bytes = ToLowerAscii (name);
var hash = md5.ComputeHash (name_bytes);
dict[hash] = name;
});
}
catch (Exception X)
{
System.Diagnostics.Trace.WriteLine (X.Message, "[RepiPack]");
}
return dict;
}
internal static byte[] ToLowerAscii (string text)
{
var text_bytes = Encodings.cp932.GetBytes (text);
for (int i = 0; i < text_bytes.Length; ++i)
{
byte c = text_bytes[i];
if (c >= 'A' && c <= 'Z')
text_bytes[i] += 0x20;
else if (c > 0x7F && c < 0xA1 || c > 0xDF)
++i;
}
return text_bytes;
}
}
internal class Md5Comparer : IEqualityComparer<byte[]>
{
public bool Equals (byte[] left, byte[] right)
{
if (left == null || right == null)
return left == right;
if (left.Length != right.Length)
return false;
for (int i = 0; i < left.Length; ++i)
{
if (left[i] != right[i])
return false;
}
return true;
}
public int GetHashCode (byte[] key)
{
if (null == key)
throw new ArgumentNullException ("key");
if (key.Length < 16)
throw new ArgumentException ("Invalid key length.", "key");
var hash = key.ToInt32 (0);
hash ^= key.ToInt32 (4);
hash ^= key.ToInt32 (8);
hash ^= key.ToInt32 (12);
return hash;
}
}
}

View File

@@ -0,0 +1,67 @@
//! \file ArcPAC.cs
//! \date 2017 Aug 17
//! \brief LunaSoft 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;
namespace GameRes.Formats.LunaSoft
{
[Export(typeof(ArchiveFormat))]
public class PacOpener : ArchiveFormat
{
public override string Tag { get { return "PAC/LUNA"; } }
public override string Description { get { return "LunaSoft resource archive"; } }
public override uint Signature { get { return 0xAD82CF82; } } // ぱく
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
if (!IsSaneCount (count))
return null;
long base_offset = file.View.ReadUInt32 (8);
uint current_offset = 0x10;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint name_length = file.View.ReadUInt32 (current_offset+0x108);
var name = file.View.ReadString (current_offset, name_length);
var entry = FormatCatalog.Instance.Create<Entry> (name);
current_offset += 0x100;
entry.Offset = base_offset + file.View.ReadUInt32 (current_offset);
entry.Size = file.View.ReadUInt32 (current_offset+4);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
current_offset += 0xC;
}
return new ArcFile (file, this, dir);
}
}
}

View File

@@ -103,6 +103,7 @@ namespace GameRes.Cryptography
public class MD5 : MD5Base
{
long m_bit_count;
int m_buf_pos;
public uint[] State { get { return m_state; } }
@@ -118,25 +119,42 @@ namespace GameRes.Cryptography
m_state[2] = 0x98BADCFE;
m_state[3] = 0x10325476;
m_bit_count = 0;
m_buf_pos = 0;
}
public byte[] ComputeHash (byte[] data)
{
return ComputeHash (data, 0, data.Length);
}
public byte[] ComputeHash (byte[] data, int pos, int count)
{
Initialize();
Update (data, pos, count);
Final();
var hash = new byte[16];
Buffer.BlockCopy (m_state, 0, hash, 0, 16);
return hash;
}
public void Update (byte[] data, int pos, int count)
{
int buf_pos = (int)(m_bit_count >> 3) & 0x3F;
m_bit_count += (long)count << 3;
if (buf_pos != 0)
if (m_buf_pos != 0)
{
int buf_count = 64 - buf_pos;
int buf_count = 64 - m_buf_pos;
if (count < buf_count)
{
Buffer.BlockCopy (data, pos, m_buffer, buf_pos, count);
Buffer.BlockCopy (data, pos, m_buffer, m_buf_pos, count);
m_buf_pos += count;
return;
}
Buffer.BlockCopy (data, pos, m_buffer, buf_pos, buf_count);
Buffer.BlockCopy (data, pos, m_buffer, m_buf_pos, buf_count);
Transform();
pos += buf_count;
count -= buf_count;
m_buf_pos = 0;
}
// data is processed in 64-byte chunks
while (count >= 64)
@@ -149,7 +167,31 @@ namespace GameRes.Cryptography
if (count > 0)
{
Buffer.BlockCopy (data, pos, m_buffer, 0, count);
m_buf_pos += count;
}
}
public void Final ()
{
Buffer.BlockCopy (Terminator, 0, m_buffer, m_buf_pos++, 1);
int buf_count = 64 - m_buf_pos;
if (buf_count < 8)
{
Buffer.BlockCopy (ZeroBytes, 0, m_buffer, m_buf_pos, buf_count);
Transform();
m_buf_pos = 0;
buf_count = 64;
}
Buffer.BlockCopy (ZeroBytes, 0, m_buffer, m_buf_pos, buf_count-8);
m_buffer[14] = (uint)m_bit_count;
m_buffer[15] = (uint)(m_bit_count >> 32);
Transform();
m_buf_pos = 0;
// XXX m_bit_count is not reset -- this is intentional.
}
static readonly byte[] Terminator = new byte[1] { 0x80 };
static readonly byte[] ZeroBytes = new byte[56];
}
}

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.35.1414")]
[assembly: AssemblyFileVersion ("1.2.35.1414")]
[assembly: AssemblyVersion ("1.2.36.1443")]
[assembly: AssemblyFileVersion ("1.2.36.1443")]

View File

@@ -29,6 +29,7 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Globalization;
using System.Numerics;
using System.IO;
using System.Text;
using GameRes.Compression;
@@ -99,14 +100,17 @@ namespace GameRes.Formats.RenPy
return null;
}
var entry = FormatCatalog.Instance.Create<RpaEntry> (name);
entry.Offset = (uint)((int)tuple[0] ^ key);
entry.UnpackedSize = (uint)((int)tuple[1] ^ key);
entry.Offset = (long)(Convert.ToInt64 (tuple[0]) ^ key);
entry.UnpackedSize = (uint)(Convert.ToInt32 (tuple[1]) ^ key);
entry.Size = entry.UnpackedSize;
if (tuple.Count > 2)
{
entry.Header = tuple[2] as byte[];
if (null != entry.Header)
if (null != entry.Header && entry.Header.Length > 0)
{
entry.Size -= (uint)entry.Header.Length;
entry.IsPacked = true;
}
}
dir.Add (entry);
}
@@ -123,7 +127,7 @@ namespace GameRes.Formats.RenPy
else
input = Stream.Null;
var rpa_entry = entry as RpaEntry;
if (null == rpa_entry || null == rpa_entry.Header)
if (null == rpa_entry || null == rpa_entry.Header || 0 == rpa_entry.Header.Length)
return input;
return new PrefixStream (rpa_entry.Header, input);
}
@@ -205,6 +209,8 @@ namespace GameRes.Formats.RenPy
const byte PROTO = 0x80; /* identify pickle protocol */
const byte TUPLE2 = 0x86; /* build 2-tuple from two topmost stack items */
const byte TUPLE3 = 0x87; /* build 3-tuple from three topmost stack items */
const byte LONG1 = 0x8A; /* push long from < 256 bytes */
const byte LONG4 = 0x8B; /* push really big long */
const byte MARK = (byte)'(';
const byte STOP = (byte)'.';
const byte INT = (byte)'I';
@@ -527,6 +533,16 @@ namespace GameRes.Formats.RenPy
break;
continue;
case LONG1:
if (!LoadLong())
break;
continue;
case LONG4:
if (!LoadLong4())
break;
continue;
case APPEND:
if (!LoadAppend())
break;
@@ -675,6 +691,47 @@ namespace GameRes.Formats.RenPy
return true;
}
bool LoadLong ()
{
int count = m_stream.ReadByte();
if (-1 == count)
return false;
m_stack.Push (DecodeLong (count));
return true;
}
bool LoadLong4 ()
{
int count = 0;
if (!ReadInt (4, out count) || count < 0)
return false;
m_stack.Push (DecodeLong (count));
return true;
}
object DecodeLong (int count)
{
if (count <= 0)
return 0L;
else if (count > 8)
{
var bytes = new byte[count];
m_stream.Read (bytes, 0, count);
return new BigInteger (bytes);
}
else
{
var bytes = new byte[8];
m_stream.Read (bytes, 0, count);
if (0 != (bytes[count-1] & 0x80)) // sign bit is set
{
for (int i = count; i < bytes.Length; ++i)
bytes[i] = 0xFF;
}
return bytes.ToInt64 (0);
}
}
bool LoadCountedTuple (int count)
{
if (m_stack.Count < count)

View File

Binary file not shown.

View File

@@ -84,6 +84,15 @@ namespace GameRes.Formats.Riddle
stream.Position = 12;
var reader = new CmpReader (stream.AsStream, meta.PackedSize, meta.DataSize);
reader.Unpack();
// 24bpp bitmaps have non-standard stride
if (24 == meta.BPP && 0 != (meta.Width & 3)
&& (meta.Height * meta.Width * 3 + 54) == meta.DataSize)
{
int stride = (int)meta.Width * 3;
var pixels = new byte[stride * (int)meta.Height];
Buffer.BlockCopy (reader.Data, 54, pixels, 0, pixels.Length);
return ImageData.CreateFlipped (meta, PixelFormats.Bgr24, null, pixels, stride);
}
using (var bmp = new MemoryStream (reader.Data))
{
var decoder = new BmpBitmapDecoder (bmp,

View File

@@ -210,21 +210,9 @@ namespace GameRes.Formats.Tanuki
{
try
{
var dir = FormatCatalog.Instance.DataDirectory;
var lst_file = Path.Combine (dir, ListFileName);
if (!File.Exists (lst_file))
return new string[0];
using (var input = new StreamReader (lst_file))
{
var names = new List<string>();
string line;
while ((line = input.ReadLine()) != null)
{
if (line.Length > 0)
names.Add (line);
}
return names.ToArray();
}
var names = new List<string>();
FormatCatalog.Instance.ReadFileList (ListFileName, name => names.Add (name));
return names.ToArray();
}
catch (Exception X)
{

View File

@@ -11,7 +11,7 @@
<AssemblyName>ArcExtra</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>9af0b529</NuGetPackageImportStamp>
<NuGetPackageImportStamp>97deb393</NuGetPackageImportStamp>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@@ -46,8 +46,8 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Concentus, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Concentus.1.1.3\lib\portable-net45+win+wpa81+wp80\Concentus.dll</HintPath>
<Reference Include="Concentus, Version=1.1.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Concentus.1.1.7\lib\portable-net45+win+wpa81+wp80\Concentus.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Concentus.Oggfile, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -66,8 +66,8 @@
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SQLite, Version=1.0.105.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<HintPath>..\packages\System.Data.SQLite.Core.1.0.105.0\lib\net45\System.Data.SQLite.dll</HintPath>
<Reference Include="System.Data.SQLite, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<HintPath>..\packages\System.Data.SQLite.Core.1.0.106.0\lib\net45\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
@@ -111,6 +111,15 @@
<PreBuildEvent>perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName)
exit 0</PreBuildEvent>
</PropertyGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\NETStandard.Library.2.0.1\build\NETStandard.Library.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NETStandard.Library.2.0.1\build\NETStandard.Library.targets'))" />
<Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets'))" />
</Target>
<Import Project="..\packages\NETStandard.Library.2.0.1\build\NETStandard.Library.targets" Condition="Exists('..\packages\NETStandard.Library.2.0.1\build\NETStandard.Library.targets')" />
<Import Project="..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

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.5.23")]
[assembly: AssemblyFileVersion ("1.0.5.23")]
[assembly: AssemblyVersion ("1.0.6.26")]
[assembly: AssemblyFileVersion ("1.0.6.26")]

View File

@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Concentus" version="1.1.3" targetFramework="net45" />
<package id="Concentus" version="1.1.7" targetFramework="net45" />
<package id="Concentus.OggFile" version="1.0.1" targetFramework="net45" />
<package id="Microsoft.NETCore.Platforms" version="2.0.0" targetFramework="net45" />
<package id="MSFTCompressionCab" version="1.0.0" targetFramework="net45" />
<package id="System.Data.SQLite.Core" version="1.0.105.0" targetFramework="net45" />
<package id="NETStandard.Library" version="2.0.1" targetFramework="net45" />
<package id="System.Data.SQLite.Core" version="1.0.106.0" targetFramework="net45" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net45" />
</packages>

View File

@@ -92,8 +92,8 @@
<Reference Include="Microsoft.WindowsAPICodePack.Shell">
<HintPath>..\packages\WindowsAPICodePack-Shell.1.1.1\lib\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
</Reference>
<Reference Include="NAudio, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.0\lib\net35\NAudio.dll</HintPath>
<Reference Include="NAudio, Version=1.8.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.3\lib\net35\NAudio.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />

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.4.31.1908")]
[assembly: AssemblyFileVersion ("1.4.31.1908")]
[assembly: AssemblyVersion ("1.4.32.1939")]
[assembly: AssemblyFileVersion ("1.4.32.1939")]

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NAudio" version="1.8.0" targetFramework="net45" />
<package id="NAudio" version="1.8.3" targetFramework="net45" />
<package id="WindowsAPICodePack-Core" version="1.1.2" targetFramework="net45" />
<package id="WindowsAPICodePack-Shell" version="1.1.1" targetFramework="net45" />
<package id="WPFToolkit" version="3.5.50211.1" targetFramework="net45" />

View File

@@ -313,6 +313,27 @@ namespace GameRes
return reader.ReadInt32();
}
}
/// <summary>
/// Read text file <paramref name="filename"/> from data directory, performing <paramref name="process_line"/> action on each line.
/// </summary>
public void ReadFileList (string filename, Action<string> process_line)
{
var lst_file = Path.Combine (DataDirectory, filename);
if (!File.Exists (lst_file))
return;
using (var input = new StreamReader (lst_file))
{
string line;
while ((line = input.ReadLine()) != null)
{
if (line.Length > 0)
{
process_line (line);
}
}
}
}
}
/// <summary>

View File

@@ -45,8 +45,8 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="NAudio, Version=1.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.0\lib\net35\NAudio.dll</HintPath>
<Reference Include="NAudio, Version=1.8.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.8.3\lib\net35\NAudio.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />

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.32.257")]
[assembly: AssemblyFileVersion ("1.4.32.257")]
[assembly: AssemblyVersion ("1.4.33.260")]
[assembly: AssemblyFileVersion ("1.4.33.260")]

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NAudio" version="1.8.0" targetFramework="net45" />
<package id="NAudio" version="1.8.3" targetFramework="net45" />
</packages>

View File

@@ -230,6 +230,7 @@ Iinari<br/>
Kyrie ~Blood Royal 3~<br/>
Shiosai no Himei<br/>
Tender Breeze<br/>
Zoku Hitou Meguri<br/>
</td></tr>
<tr class="odd last"><td>*.gcp</td><td><tt>CMP1</tt></td><td></td></tr>
<tr><td>*.pd</td><td><tt>PackOnly</tt><br/><tt>PackPlus</tt><br/><tt>FlyingShinePDFile</tt></td><td><span class="supported"></span></td><td>Flying Shine</td><td>
@@ -237,7 +238,10 @@ Akarui Mirai ~Wet And Messy 2nd time~<br/>
Cross†Channel<br/>
Crime Rhyme<br/>
</td></tr>
<tr class="odd"><td>*.rpa</td><td><tt>RPA-3.0</tt></td><td><span class="supported"></span></td><td>Ren'Py</td><td>Katawa Shoujo</td></tr>
<tr class="odd"><td>*.rpa</td><td><tt>RPA-3.0</tt></td><td><span class="supported"></span></td><td>Ren'Py</td><td>
Katawa Shoujo<br/>
Sakura Halloween<br/>
</td></tr>
<tr><td>*.arc</td><td>-</td><td><span class="supported"></span></td><td rowspan="3">Will</td><td rowspan="3">
Chaste ☆ Chase!<br/>
Cynthia ~Sincerely to You~<br/>
@@ -245,6 +249,7 @@ Folklore Jam<br/>
Hapihosu! ~Osewasaremasu Nyuuin Seikatsu~<br/>
I/O<br/>
Kenseiki Alpha Ride<br/>
Mirai Kanojo<br/>
Natsuiro Kouen ~Denpatou no Shita de Ai wo Kataru~<br/>
Omoi o Sasageru Otome no Melody<br/>
Onegai O-Hoshi-sama<br/>
@@ -314,6 +319,7 @@ Kagachi-sama Onagusame Tatematsurimasu<br/>
Karakara<br/>
Karenai Sekai to Owaru Hana<br/>
Kicking Horse Rhapsody<br/>
Koi ga Saku Koro Sakura Doki<br/>
Kourin no Machi, Lavender no Shoujo<br/>
Kozukuri Onsen ~Ippai Tsukutte Ichizoku Hanei~<br/>
Kurenai no Tsuki<br/>
@@ -324,6 +330,7 @@ Maitetsu<br/>
Mizukoi<br/>
Mizu no Miyako no Patisserie<br/>
Nakadashi Hara Maid series<br/>
Namaiki Delation<br/>
Natsupochi<br/>
Natsuzora Kanata<br/>
Nidaime wa ☆ Mahou Shoujo<br/>
@@ -361,6 +368,8 @@ Teakamamire no Tenshi<br/>
Toaru Jukujo no Hentai Choukyou<br/>
Towazugatari ~Shoujo Ryoujoku Hishou~<br/>
Ushinawareta Mirai o Motomete<br/>
Wagamama High Spec<br/>
Walpurgis<br/>
With Ribbon<br/>
Yome Sagashi ga Hakadori Sugite Yabai.<br/>
Yomibito Shirazu ~Amai Meshibe no Seichoushi~<br/>
@@ -379,6 +388,7 @@ Haramase Taiken Gakushuu ~Shizen no Naka de Seikyouiku!?~<br/>
Koi Mekuri Clover<br/>
Mamono Musume-tachi to no Rakuen ~Slime &amp; Scylla~<br/>
Mashou no Nie 3 ~Hakudaku no Umi ni Shizumu Injoku no Reiki~<br/>
M.C. Saimin Kenkyuu<br/>
Ryuuyoku no Melodia -Diva with the blessed dragonol-<br/>
Sei Monmusu Festival!!<br/>
Shin Chikan Ou<br/>
@@ -690,6 +700,7 @@ Koishiki Manual<br/>
Nanairo Kouro<br/>
Natsu no Ame<br/>
Nerawareta Megami Tenshi Angeltia<br/>
Ouchi ni Kaeru made ga Mashimaro Desu<br/>
Parallel Harmony<br/>
re-Laive<br/>
Sakura Musubi<br/>
@@ -781,6 +792,7 @@ Double<br/>
Exile ~Blood Royal 2~<br/>
Gakuen ~Nerawareta Chitai~<br/>
Kimon Youitan<br/>
Onna Kyoushi Saeko ~Biniku Jugyou~<br/>
Unbalance<br/>
</td></tr>
<tr class="odd"><td>*.lib<br/>*.dat</td><td><tt>LIB</tt><br/><tt>LIBP</tt><br/><tt>LIBU</tt><span class="footnote">encrypted</span></td><td></td><td rowspan="3">Malie</td><td rowspan="3">
@@ -836,6 +848,7 @@ Yuukyou Gangu 2<br/>
<tr class="odd last"><td>*.wwa</td><td><tt>WPX\x1AWAV</tt></td><td></td></tr>
<tr><td>*.mrg</td><td><tt>MRG</tt></td><td></td><td rowspan="3">F&amp;C</td><td rowspan="3">
Asa no Konai Yoru ni Dakarete -Eternal Night-<br/>
Beside ~Shiawase wa Katawara ni~<br/>
Guren ni Somaru Gin no Rozario<br/>
Konata yori Kanata made<br/>
Natural ~Mi mo Kokoro mo~<br/>
@@ -876,7 +889,7 @@ Vampire Crusaders<br/>
<tr class="last"><td>*.cgf</td><td>-</td><td></td></tr>
<tr class="odd"><td>*.bin+*.pak</td><td><tt>hed</tt></td><td></td><td rowspan="2">elf</td><td rowspan="2">Adult Video King</td></tr>
<tr class="odd last"><td>*.hip<br/>*.hiz<br/></td><td><tt>hip</tt><br/><tt>hiz</tt></td><td></td></tr>
<tr><td>*.gpk+*.gtb<br/>*.vpk+*.vtb</td><td>-</td><td></td><td rowspan="3">Black Cyc</td><td rowspan="3">
<tr><td>*.gpk+*.gtb<br/>*.vpk+*.vtb</td><td>-</td><td></td><td rowspan="3">System-NNN</td><td rowspan="3">
Before Dawn Daybreak ~Shinen no Utahime~<br/>
Extravaganza ~Mushi Mederu Shoujo~<br/>
Gokuin no Sentou<br/>
@@ -885,6 +898,7 @@ Hana Goyomi<br/>
Jishou Seirei Majutsushi vs Shinsei Daiikkyuu Akuma<br/>
Kurogane no Tsubasa ~The Alchemist's Story~<br/>
Mushitsukai<br/>
Ningyou-hime no Shiro ~Anata wa Mou, Atashi no Omocha~<br/>
Yami no Koe series<br/>
</td></tr>
<tr><td>*.dwq</td><td><tt>BMP</tt><br/><tt>JPEG</tt><br/><tt>PNG</tt><br/><tt>JPEG+MASK</tt><br/><tt>PACKBMP+MASK</tt><br/></td><td></td></tr>
@@ -904,6 +918,7 @@ Kikouyoku Senki Gin no Toki no Corona<br/>
Kikouyoku Senki Tenkuu no Yumina<br/>
Nega0<br/>
Seinarukana -The Spirit of Eternity Sword 2-<br/>
Ten no Hikari wa Koi no Hoshi<br/>
</td></tr>
<tr class="last"><td>*.wag<br/>*.4ag<br/>*.004</td><td><tt>WAG@</tt><br/><tt>GAF4</tt></td><td></td></tr>
<tr class="odd"><td>*.ykc</td><td><tt>YKC001</tt></td><td><span class="supported"></span></td><td rowspan="2">Yuka</td><td rowspan="2">
@@ -1000,6 +1015,7 @@ Moshimo Ashita ga Harenaraba<br/>
<tr class="odd"><td>*.pga</td><td><tt>PGAPGAH</tt></td><td><span class="supported"></span></td></tr>
<tr class="odd last"><td>*.chr</td><td><tt>char</tt></td><td></td></tr>
<tr><td>*.gyu</td><td><tt>GYU\x1a</tt></td><td></td><td rowspan="2">ExHIBIT</td><td rowspan="2">
Ane Ane Double Saimin 2<br/>
Eve ~New Generation X~<br/>
Fuyu no Rondo<br/>
Imouto Paradise! 2<br/>
@@ -1081,6 +1097,7 @@ Haruiro Ouse<br/>
Memoria<br/>
Miharu - Alto Another Story<br/>
Natsu ni Kanaderu Bokura no Uta<br/>
Shiawase Kazoku-bu<br/>
</td></tr>
<tr><td>*.pb2</td><td><tt>PB2A</tt></td><td></td></tr>
<tr><td>*.pb3</td><td><tt>PB3B</tt></td><td></td></tr>
@@ -1111,15 +1128,17 @@ Paimega<br/>
</td></tr>
<tr class="odd"><td>*.snn+*.inx</td><td>-</td><td></td><td rowspan="3">BlueGale</td><td rowspan="3">
Bifronte ~Kugaitou Kitan~<br/>
Bikini Junkie<br/>
Cafe Junkie<br/>
Immoral<br/>
Joi Okkusu<br/>
Majidashi! Royale ~Finish wa Watashi no Naka de~<br/>
MILK Junkies<br/>
Oppai Life<br/>
</td></tr>
<tr class="odd"><td>*.zbm</td><td><tt>amp_</tt></td><td></td></tr>
<tr class="odd last"><td>*.amv</td><td><tt>ampV</tt></td><td></td></tr>
<tr><td>*.vfs</td><td><tt>VF</tt></td><td></td><td rowspan="5">Aoi</td><td rowspan="5">
<tr><td>*.vfs</td><td><tt>VF</tt></td><td></td><td rowspan="6">Aoi</td><td rowspan="6">
Alfred Gakuen Mamono Daitai<br/>
Brown Doori Sanbanme<br/>
Daisounan<br/>
@@ -1253,6 +1272,7 @@ Uso x Mote ~Usonko Motemote-shon~<br/>
Chijoku Yuugi<br/>
Kakure Ecchi<br/>
OL Gari ~Nureta Office~<br/>
Machina no Kiseki ~Ogeretsu Daihyakka Gaiden~<br/>
Taiiku Souko ~Shoujotachi no Sange~<br/>
</td></tr>
<tr><td>*.cmp</td><td><tt>SBI</tt></td><td></td><td rowspan="2">Vitamin</td><td rowspan="2">
@@ -1308,6 +1328,7 @@ Etsuraku no Tane<br/>
Fit Shichao! ~Toshiue Josei to Asedaku Lesson Hatsutaiken~<br/>
Fuka no Jugoku<br/>
Gakkou Yarashii Kaidan<br/>
Gibo no Shizuku ~Shimetta Hada kara Kaori Tatsu Amai Iroka~<br/>
Gibo no Toiki ~Haitoku Kokoro ni tadayou Haha no Iroka~<br/>
Gohoushi★Princess<br/>
Gokkun♪ Vampire☆Princess<br/>
@@ -1435,6 +1456,7 @@ Stray Sheep ~Chijoku no Zangeshitsu~<br/>
</td></tr>
<tr class="last"><td>*.i</td><td><tt>IMG2</tt></td><td></td></tr>
<tr class="odd"><td>*.pk</td><td><tt>fPK</tt></td><td></td><td rowspan="3">Ivory</td><td rowspan="3">
JK Free Hall<br/>
Triangle Heart 1-2-3<br/>
</td></tr>
<tr class ="odd"><td>*.sg</td><td><tt>fSG</tt></td><td></td></tr>
@@ -1621,6 +1643,25 @@ Sengoku Hime 7<br/>
<tr class="odd"><td>*.dat</td><td>-</td><td></td><td>YaneSDK?</td><td>
Niizuma Lovely x Cation<br/>
</td></tr>
<tr><td>*.pac</td><td><tt>ぱく</tt></td><td></td><td>LunaSoft</td><td>
Mahou Shoujo Lyiene<br/>
</td></tr>
<tr class="odd"><td>*.fga</td><td>-</td><td></td><td rowspan="2">SFA</td><td rowspan="2">
Maokko Crisis<br/>
<tr class="odd last"><td>*.abm</td><td><tt>BM</tt></td><td></td></tr>
</td></tr>
<tr><td>*.alo</td><td>-</td><td><span class="supported"></span></td><td rowspan="2">BeF</td><td rowspan="2">
Manatsu no Yoru no Yume<br/>
</td></tr>
<tr class="last"><td>*.vzy</td><td>-</td><td></td></tr>
<tr class="odd"><td>*.dat</td><td><tt>RepiPack</tt></td><td></td><td>Littlewitch</td><td>
Eiyu*Senki - The World Conquest<br/>
</td></tr>
<tr class="last"><td>arc*.dat</td><td>-</td><td></td><td>non color</td><td>
Hana Hime * Absolute!<br/>
Yuuki Yuuna wa Yuusha de Aru<br/>
Yuuki Yuuna wa Yuusha de Aru S<br/>
</td></tr>
</table>
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
</body>

View File

@@ -1,19 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<GARbro>
<Release>
<Version>1.4.31</Version>
<Version>1.4.32</Version>
<Url>https://github.com/morkt/GARbro/releases/latest</Url>
<Notes>Changes:
- updated INT, QLIE, YPF, PCF, GBC, MG2 formats support
- added more KiriKiri and Malie encryption schemes
- added CP936 encoding option to text viewer
<Notes>New formats:
- LunaSoft PAC archives
- FGA archives
- 'RepiPack' archives
- ALO images and VZY audio
- 'fPK2' archives
- updated RPA archives support
- more KiriKiri and Csystem encryption schemes
</Notes>
</Release>
<FormatsData>
<FileVersion>78</FileVersion>
<FileVersion>86</FileVersion>
<Url>https://github.com/morkt/GARbro/raw/master/ArcFormats/Resources/Formats.dat</Url>
<Requires>
<Assembly Name="ArcFormats" Version="1.2.35.1414"/>
<Assembly Name="ArcFormats" Version="1.2.35.1417"/>
<Assembly Name="GameRes" Version="1.4.26.238"/>
</Requires>
</FormatsData>