mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-07 06:08:47 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afb13290f5 | ||
|
|
d6e441fe64 | ||
|
|
651aad92a2 | ||
|
|
0b80548a81 | ||
|
|
fd55204172 | ||
|
|
532f58625c | ||
|
|
4b0506c9d0 | ||
|
|
f40226866c | ||
|
|
8724e8131c | ||
|
|
c499d4c707 | ||
|
|
b574d2fb96 | ||
|
|
c00747a38b | ||
|
|
e1bfe9cb09 | ||
|
|
40575baacb | ||
|
|
ad16b754ae | ||
|
|
3ad1c60dde | ||
|
|
b4ec3d229e | ||
|
|
cbab8b200a | ||
|
|
37539cc083 | ||
|
|
eb53552bbc | ||
|
|
0c8b3e3e62 | ||
|
|
2aa6f92b1a | ||
|
|
8c37a7e107 | ||
|
|
525c2ff29b | ||
|
|
1aeb6cb791 | ||
|
|
5bd63c01cb | ||
|
|
37a038b090 | ||
|
|
96ebe2bd85 | ||
|
|
5097a98cb3 | ||
|
|
241cb95f8b | ||
|
|
8a74eb1c77 | ||
|
|
360664343f | ||
|
|
da3403339e | ||
|
|
2d186edb39 |
@@ -107,6 +107,7 @@
|
||||
<Compile Include="Foster\ImageC24.cs" />
|
||||
<Compile Include="GameSystem\ArcPureMail.cs" />
|
||||
<Compile Include="GameSystem\AudioADP4.cs" />
|
||||
<Compile Include="ImagePSM.cs" />
|
||||
<Compile Include="Kaas\ArcPB.cs" />
|
||||
<Compile Include="Kaas\AudioKAAS.cs" />
|
||||
<Compile Include="Kaguya\ArcAN21.cs" />
|
||||
@@ -114,6 +115,8 @@
|
||||
<Compile Include="Kurumi\ImageGRA.cs" />
|
||||
<Compile Include="Leaf\ArcPX.cs" />
|
||||
<Compile Include="Malie\ArcLIBU.cs" />
|
||||
<Compile Include="Malie\LibScheme.cs" />
|
||||
<Compile Include="Malie\MalieEncryption.cs" />
|
||||
<Compile Include="MicroVision\ArcARC.cs" />
|
||||
<Compile Include="MicroVision\ArcGSD.cs" />
|
||||
<Compile Include="MicroVision\AudioIKM.cs" />
|
||||
@@ -122,6 +125,7 @@
|
||||
<Compile Include="Qlie\Encryption.cs" />
|
||||
<Compile Include="RealLive\ArcKOE.cs" />
|
||||
<Compile Include="rUGP\AudioRHA.cs" />
|
||||
<Compile Include="Slg\ArcSPD.cs" />
|
||||
<Compile Include="Software House Parsley\ArcCG.cs" />
|
||||
<Compile Include="Artemis\ArcPFS.cs" />
|
||||
<Compile Include="AudioWMA.cs" />
|
||||
|
||||
@@ -52,13 +52,14 @@ namespace GameRes.Formats.Artemis
|
||||
int version = file.View.ReadByte (2) - '0';
|
||||
switch (version)
|
||||
{
|
||||
case 8: return OpenPf8 (file);
|
||||
case 6:
|
||||
case 8: return OpenPf (file, version);
|
||||
case 2: return OpenPf2 (file);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
ArcFile OpenPf8 (ArcView file)
|
||||
ArcFile OpenPf (ArcView file, int version)
|
||||
{
|
||||
uint index_size = file.View.ReadUInt32 (3);
|
||||
int count = file.View.ReadInt32 (7);
|
||||
@@ -81,9 +82,13 @@ namespace GameRes.Formats.Artemis
|
||||
index_offset += 8;
|
||||
dir.Add (entry);
|
||||
}
|
||||
if (version != 8 && version != 9 && version != 4 && version != 5)
|
||||
return new ArcFile (file, this, dir);
|
||||
|
||||
// key calculated for archive versions 4, 5, 8 and 9
|
||||
using (var sha1 = SHA1.Create())
|
||||
{
|
||||
var key = sha1.ComputeHash (index); // calculated for archive versions 4, 5, 8 and 9
|
||||
var key = sha1.ComputeHash (index);
|
||||
return new PfsArchive (file, this, dir, key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace GameRes.Formats.CatSystem
|
||||
return null;
|
||||
if (file.View.AsciiEqual (8, "__key__.dat\x00"))
|
||||
{
|
||||
uint? key = QueryEncryptionInfo();
|
||||
uint? key = QueryEncryptionInfo (file.Name);
|
||||
if (null == key)
|
||||
throw new UnknownEncryptionScheme();
|
||||
return OpenEncrypted (file, entry_count, key.Value);
|
||||
@@ -274,8 +274,11 @@ namespace GameRes.Formats.CatSystem
|
||||
return new GUI.CreateINTWidget();
|
||||
}
|
||||
|
||||
uint? QueryEncryptionInfo ()
|
||||
uint? QueryEncryptionInfo (string arc_name)
|
||||
{
|
||||
var title = FormatCatalog.Instance.LookupGame (arc_name);
|
||||
if (!string.IsNullOrEmpty (title) && KnownSchemes.ContainsKey (title))
|
||||
return KnownSchemes[title].Key;
|
||||
var options = Query<IntOptions> (arcStrings.INTNotice);
|
||||
return options.EncryptionInfo.GetKey();
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace GameRes.Formats.GUI
|
||||
CheckPathExists = true,
|
||||
Multiselect = false,
|
||||
Title = arcStrings.INTChooseExe,
|
||||
Filter = arcStrings.INTExeFiles+"|*.exe",
|
||||
Filter = arcStrings.INTExeFiles+"|*.exe;*.bin",
|
||||
FilterIndex = 1,
|
||||
InitialDirectory = Directory.GetCurrentDirectory(),
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! \date Thu Jun 16 13:48:04 2016
|
||||
//! \brief Tinker Bell 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
|
||||
@@ -272,8 +272,12 @@ namespace GameRes.Formats.Cyberworks
|
||||
try
|
||||
{
|
||||
var reader = DecryptImage (input, barc.Scheme);
|
||||
if (BlendOverlayImages && reader is AImageReader)
|
||||
reader = BlendAImage (barc, entry, reader as AImageReader);
|
||||
if (BlendOverlayImages)
|
||||
{
|
||||
var overlay = reader as AImageReader;
|
||||
if (overlay != null)
|
||||
overlay.ReadBaseline (barc, entry);
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
catch
|
||||
@@ -294,55 +298,18 @@ namespace GameRes.Formats.Cyberworks
|
||||
input = BinaryStream.FromStream (new StreamRegion (input.AsStream, 5, img_size), input.Name);
|
||||
}
|
||||
}
|
||||
else if (scheme != null && 'a' == type && input.Length > 21)
|
||||
else if (scheme != null && ('a' == type || 'd' == type) && input.Length > 21)
|
||||
{
|
||||
int id = input.ReadByte();
|
||||
if (id == scheme.Value2)
|
||||
{
|
||||
return new AImageReader (input, scheme);
|
||||
return new AImageReader (input, scheme, type);
|
||||
}
|
||||
}
|
||||
input.Position = 0;
|
||||
return new ImageFormatDecoder (input);
|
||||
}
|
||||
|
||||
IImageDecoder BlendAImage (BellArchive arc, Entry entry, AImageReader overlay)
|
||||
{
|
||||
var header = overlay.ReadHeader();
|
||||
if (header[0] != 1)
|
||||
return overlay;
|
||||
var scheme = arc.Scheme;
|
||||
var dir = (List<Entry>)arc.Dir;
|
||||
int i = dir.IndexOf (entry);
|
||||
while (--i >= 0 && "image" == dir[i].Type)
|
||||
{
|
||||
using (var input = OpenEntry (arc, dir[i]))
|
||||
{
|
||||
int type = input.ReadByte();
|
||||
if (type != 'a')
|
||||
break;
|
||||
int id = input.ReadByte();
|
||||
if (id != scheme.Value2)
|
||||
break;
|
||||
using (var bin = new BinaryStream (input, dir[i].Name))
|
||||
using (var base_image = new AImageReader (bin, scheme))
|
||||
{
|
||||
var base_header = base_image.ReadHeader();
|
||||
if (1 == base_header[0])
|
||||
continue;
|
||||
// check if image width/height are the same
|
||||
if (base_header[3] == header[3] && base_header[4] == header[4])
|
||||
{
|
||||
base_image.Unpack();
|
||||
overlay.Baseline = base_image.Data;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return overlay;
|
||||
}
|
||||
|
||||
internal AImageScheme QueryScheme (string arc_name)
|
||||
{
|
||||
var title = FormatCatalog.Instance.LookupGame (arc_name);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! \date Fri Jun 17 18:49:04 2016
|
||||
//! \brief Tinker Bell encrypted image file.
|
||||
//
|
||||
// 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
|
||||
@@ -24,9 +24,10 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace GameRes.Formats.Cyberworks
|
||||
{
|
||||
@@ -52,11 +53,13 @@ namespace GameRes.Formats.Cyberworks
|
||||
AImageScheme m_scheme;
|
||||
ImageData m_image;
|
||||
int[] m_header;
|
||||
int m_type;
|
||||
|
||||
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 byte[] Baseline { get; set; }
|
||||
public int Type { get { return m_type; } }
|
||||
|
||||
public ImageData Image
|
||||
{
|
||||
@@ -77,10 +80,11 @@ namespace GameRes.Formats.Cyberworks
|
||||
|
||||
public byte[] Data { get { return m_output; } }
|
||||
|
||||
public AImageReader (IBinaryStream input, AImageScheme scheme)
|
||||
public AImageReader (IBinaryStream input, AImageScheme scheme, int type = 'a')
|
||||
{
|
||||
m_input = input;
|
||||
m_scheme = scheme;
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
internal int[] ReadHeader ()
|
||||
@@ -99,6 +103,65 @@ namespace GameRes.Formats.Cyberworks
|
||||
return m_header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search archive <paramref name="arc"/> for baseline image.
|
||||
/// </summary>
|
||||
internal void ReadBaseline (BellArchive arc, Entry entry)
|
||||
{
|
||||
var header = ReadHeader();
|
||||
if (!((header[0] & 1) == 1 && 'd' == this.Type
|
||||
|| header[0] == 1 && 'a' == this.Type))
|
||||
return;
|
||||
var scheme = arc.Scheme;
|
||||
var dir = (List<Entry>)arc.Dir;
|
||||
int i = dir.IndexOf (entry);
|
||||
while (--i >= 0 && "image" == dir[i].Type)
|
||||
{
|
||||
using (var input = arc.OpenEntry (dir[i]))
|
||||
{
|
||||
int type = input.ReadByte();
|
||||
if ('d' == type)
|
||||
continue;
|
||||
if ('a' == type)
|
||||
{
|
||||
int id = input.ReadByte();
|
||||
if (id != scheme.Value2)
|
||||
break;
|
||||
using (var bin = new BinaryStream (input, dir[i].Name))
|
||||
using (var base_image = new AImageReader (bin, scheme))
|
||||
{
|
||||
var base_header = base_image.ReadHeader();
|
||||
if (1 == base_header[0])
|
||||
continue;
|
||||
// check if image width/height are the same
|
||||
if (base_header[3] == header[3] && base_header[4] == header[4])
|
||||
{
|
||||
base_image.Unpack();
|
||||
Baseline = base_image.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ('b' == type || 'c' == type)
|
||||
{
|
||||
var size_buf = new byte[4];
|
||||
input.Read (size_buf, 0 , 4);
|
||||
var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None,
|
||||
BitmapCacheOption.OnLoad);
|
||||
BitmapSource frame = decoder.Frames[0];
|
||||
Info.Width = (uint)frame.PixelWidth;
|
||||
Info.Height = (uint)frame.PixelHeight;
|
||||
if (frame.Format.BitsPerPixel != 32)
|
||||
frame = new FormatConvertedBitmap (frame, PixelFormats.Bgra32, null, 0);
|
||||
int stride = frame.PixelWidth * 4;
|
||||
var pixels = new byte[stride * frame.PixelHeight];
|
||||
frame.CopyPixels (pixels, stride, 0);
|
||||
Baseline = pixels;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Unpack ()
|
||||
{
|
||||
var header = ReadHeader();
|
||||
@@ -118,6 +181,8 @@ namespace GameRes.Formats.Cyberworks
|
||||
{
|
||||
if (0 == bits_size)
|
||||
CopyV6 (unpacked_size, header[6]);
|
||||
else if (1 == (flags & 1) && 'd' == m_type && Baseline != null)
|
||||
UnpackV6d (bits_size, bits_size + header[6]);
|
||||
else
|
||||
UnpackV6 (bits_size, data_offset, data_offset + header[6]);
|
||||
}
|
||||
@@ -306,6 +371,34 @@ namespace GameRes.Formats.Cyberworks
|
||||
}
|
||||
}
|
||||
|
||||
void UnpackV6d (int bits_size, int rgb_offset)
|
||||
{
|
||||
Info.BPP = 32;
|
||||
var rgb_map = m_input.ReadBytes (bits_size);
|
||||
var alpha = m_input.ReadBytes (rgb_offset - bits_size);
|
||||
int plane_size = Math.Min (Baseline.Length, bits_size*8);
|
||||
m_output = Baseline;
|
||||
int bit = 1;
|
||||
int bit_src = 0;
|
||||
int alpha_src = 0;
|
||||
int dst = 0;
|
||||
for (int i = 0; i < plane_size; ++i)
|
||||
{
|
||||
if ((bit & rgb_map[bit_src]) != 0)
|
||||
{
|
||||
m_input.Read (m_output, dst, 3);
|
||||
m_output[dst+3] = alpha[alpha_src++];
|
||||
}
|
||||
dst += 4;
|
||||
bit <<= 1;
|
||||
if (0x100 == bit)
|
||||
{
|
||||
++bit_src;
|
||||
bit = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GetInt ()
|
||||
{
|
||||
byte a = m_input.ReadUInt8();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! \date Thu Oct 08 00:18:56 2015
|
||||
//! \brief DxLib engine archives with 'DX' signature.
|
||||
//
|
||||
// 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
|
||||
@@ -28,6 +28,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.DxLib
|
||||
@@ -35,11 +36,13 @@ namespace GameRes.Formats.DxLib
|
||||
internal class DxArchive : ArcFile
|
||||
{
|
||||
public readonly byte[] Key;
|
||||
public readonly int Version;
|
||||
|
||||
public DxArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, byte[] key)
|
||||
public DxArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, byte[] key, int version)
|
||||
: base (arc, impl, dir)
|
||||
{
|
||||
Key = key;
|
||||
Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +63,8 @@ namespace GameRes.Formats.DxLib
|
||||
|
||||
public DxOpener ()
|
||||
{
|
||||
Extensions = new string[] { "dxa", "hud", "usi", "med", "dat" };
|
||||
Signatures = new uint[] { 0x19EF8ED4, 0xA9FCCEDD, 0x0AEE0FD3, 0x5523F211, 0x5524F211, 0 };
|
||||
Extensions = new string[] { "dxa", "hud", "usi", "med", "dat", "bin" };
|
||||
Signatures = new uint[] { 0x19EF8ED4, 0xA9FCCEDD, 0x0AEE0FD3, 0x5523F211, 0x5524F211, 0x69FC5FE4, 0 };
|
||||
}
|
||||
|
||||
public static IList<byte[]> KnownKeys = new List<byte[]>();
|
||||
@@ -76,7 +79,7 @@ namespace GameRes.Formats.DxLib
|
||||
uint sig_key = LittleEndian.ToUInt32 (key, 0);
|
||||
uint sig_test = signature ^ sig_key;
|
||||
int version = (int)(sig_test >> 16);
|
||||
if (0x5844 == (sig_test & 0xFFFF) && version <= 4) // 'DX'
|
||||
if (0x5844 == (sig_test & 0xFFFF) && version <= 6) // 'DX'
|
||||
{
|
||||
var dir = ReadIndex (file, version, key);
|
||||
if (null != dir)
|
||||
@@ -87,7 +90,7 @@ namespace GameRes.Formats.DxLib
|
||||
KnownKeys.Remove (key);
|
||||
KnownKeys.Insert (0, key);
|
||||
}
|
||||
return new DxArchive (file, this, dir, key);
|
||||
return new DxArchive (file, this, dir, key, version);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -124,7 +127,7 @@ namespace GameRes.Formats.DxLib
|
||||
if (null != dir)
|
||||
{
|
||||
KnownKeys.Insert (0, key);
|
||||
return new DxArchive (file, this, dir, key);
|
||||
return new DxArchive (file, this, dir, key, version);
|
||||
}
|
||||
}
|
||||
catch { /* ignore parse errors */ }
|
||||
@@ -138,9 +141,14 @@ namespace GameRes.Formats.DxLib
|
||||
var dx_arc = arc as DxArchive;
|
||||
if (null == dx_arc)
|
||||
return input;
|
||||
input = new EncryptedStream (input, entry.Offset, dx_arc.Key);
|
||||
var dx_ent = entry as PackedEntry;
|
||||
if (null == dx_ent || !dx_ent.IsPacked)
|
||||
var dx_ent = (PackedEntry)entry;
|
||||
long dec_offset = entry.Offset;
|
||||
if (dx_arc.Version > 5)
|
||||
{
|
||||
dec_offset = dx_ent.UnpackedSize;
|
||||
}
|
||||
input = new EncryptedStream (input, dec_offset, dx_arc.Key);
|
||||
if (!dx_ent.IsPacked)
|
||||
return input;
|
||||
using (input)
|
||||
{
|
||||
@@ -214,26 +222,52 @@ namespace GameRes.Formats.DxLib
|
||||
}
|
||||
|
||||
List<Entry> ReadIndex (ArcView file, int version, byte[] key)
|
||||
{
|
||||
DxHeader dx = null;
|
||||
if (version <= 4)
|
||||
dx = ReadArcHeaderV4 (file, version, key);
|
||||
else if (6 == version)
|
||||
dx = ReadArcHeaderV6 (file, version, key);
|
||||
if (null == dx || dx.DirTable >= dx.IndexSize || dx.FileTable >= dx.IndexSize)
|
||||
return null;
|
||||
using (var encrypted = file.CreateStream (dx.IndexOffset, dx.IndexSize))
|
||||
using (var index = new EncryptedStream (encrypted, 6 == version ? 0 : dx.IndexOffset, key))
|
||||
using (var reader = IndexReader.Create (dx, version, index))
|
||||
{
|
||||
return reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
DxHeader ReadArcHeaderV4 (ArcView file, int version, byte[] key)
|
||||
{
|
||||
var header = file.View.ReadBytes (4, 0x18);
|
||||
if (0x18 != header.Length)
|
||||
return null;
|
||||
Decrypt (header, 0, header.Length, 4, key);
|
||||
var dx = new DxHeader {
|
||||
return new DxHeader {
|
||||
IndexSize = LittleEndian.ToUInt32 (header, 0),
|
||||
BaseOffset = LittleEndian.ToUInt32 (header, 4),
|
||||
IndexOffset = LittleEndian.ToUInt32 (header, 8),
|
||||
FileTable = LittleEndian.ToUInt32 (header, 0x0c),
|
||||
FileTable = LittleEndian.ToUInt32 (header, 0x0C),
|
||||
DirTable = LittleEndian.ToUInt32 (header, 0x10),
|
||||
CodePage = 932,
|
||||
};
|
||||
if (dx.DirTable >= dx.IndexSize || dx.FileTable >= dx.IndexSize)
|
||||
}
|
||||
|
||||
DxHeader ReadArcHeaderV6 (ArcView file, int version, byte[] key)
|
||||
{
|
||||
var header = file.View.ReadBytes (4, 0x2C);
|
||||
if (0x2C != header.Length)
|
||||
return null;
|
||||
using (var encrypted = file.CreateStream (dx.IndexOffset, dx.IndexSize))
|
||||
using (var index = new EncryptedStream (encrypted, dx.IndexOffset, key))
|
||||
using (var reader = new IndexReader (dx, version, index))
|
||||
{
|
||||
return reader.Read();
|
||||
}
|
||||
Decrypt (header, 0, header.Length, 4, key);
|
||||
return new DxHeader {
|
||||
IndexSize = LittleEndian.ToUInt32 (header, 0),
|
||||
BaseOffset = LittleEndian.ToInt64 (header, 4),
|
||||
IndexOffset = LittleEndian.ToInt64 (header, 0x0C),
|
||||
FileTable = (uint)LittleEndian.ToInt64 (header, 0x14),
|
||||
DirTable = (uint)LittleEndian.ToInt64 (header, 0x1C),
|
||||
CodePage = LittleEndian.ToInt32 (header, 0x24),
|
||||
};
|
||||
}
|
||||
|
||||
internal static void Decrypt (byte[] data, int index, int count, long offset, byte[] key)
|
||||
@@ -291,32 +325,33 @@ namespace GameRes.Formats.DxLib
|
||||
public uint IndexSize;
|
||||
public uint FileTable;
|
||||
public uint DirTable;
|
||||
public int CodePage;
|
||||
}
|
||||
|
||||
internal class DxDirectory
|
||||
internal abstract class IndexReader : IDisposable
|
||||
{
|
||||
public int DirOffset;
|
||||
public int ParentDirOffset;
|
||||
public int FileCount;
|
||||
public int FileTable;
|
||||
}
|
||||
protected readonly int m_version;
|
||||
protected DxHeader m_header;
|
||||
protected BinaryStream m_input;
|
||||
protected Encoding m_encoding;
|
||||
protected List<Entry> m_dir = new List<Entry>();
|
||||
|
||||
internal sealed class IndexReader : IDisposable
|
||||
{
|
||||
readonly int m_version;
|
||||
readonly int m_entry_size;
|
||||
DxHeader m_header;
|
||||
BinaryReader m_input;
|
||||
List<Entry> m_dir = new List<Entry>();
|
||||
|
||||
public List<Entry> Dir { get { return m_dir; } }
|
||||
|
||||
public IndexReader (DxHeader header, int version, Stream input)
|
||||
protected IndexReader (DxHeader header, int version, Stream input)
|
||||
{
|
||||
m_version = version;
|
||||
m_entry_size = m_version >= 2 ? 0x2C : 0x28;
|
||||
m_header = header;
|
||||
m_input = new ArcView.Reader (input);
|
||||
m_version = version;
|
||||
m_input = new BinaryStream (input, "");
|
||||
m_encoding = Encoding.GetEncoding (header.CodePage);
|
||||
}
|
||||
|
||||
public static IndexReader Create (DxHeader header, int version, Stream input)
|
||||
{
|
||||
if (version <= 4)
|
||||
return new IndexReaderV2 (header, version, input);
|
||||
else if (6 == version)
|
||||
return new IndexReaderV6 (header, version, input);
|
||||
else
|
||||
throw new InvalidFormatException ("Not supported DX archive version.");
|
||||
}
|
||||
|
||||
public List<Entry> Read ()
|
||||
@@ -325,6 +360,46 @@ namespace GameRes.Formats.DxLib
|
||||
return m_dir;
|
||||
}
|
||||
|
||||
protected abstract void ReadFileTable (string root, long table_offset);
|
||||
|
||||
protected string ExtractFileName (long table_offset)
|
||||
{
|
||||
m_input.Position = table_offset;
|
||||
int name_offset = m_input.ReadUInt16() * 4 + 4;
|
||||
m_input.Position = table_offset + name_offset;
|
||||
return m_input.ReadCString (m_encoding);
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
m_input.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class IndexReaderV2 : IndexReader
|
||||
{
|
||||
readonly int m_entry_size;
|
||||
|
||||
public IndexReaderV2 (DxHeader header, int version, Stream input) : base (header, version, input)
|
||||
{
|
||||
m_entry_size = m_version >= 2 ? 0x2C : 0x28;
|
||||
}
|
||||
|
||||
private class DxDirectory
|
||||
{
|
||||
public int DirOffset;
|
||||
public int ParentDirOffset;
|
||||
public int FileCount;
|
||||
public int FileTable;
|
||||
}
|
||||
|
||||
DxDirectory ReadDirEntry ()
|
||||
{
|
||||
var dir = new DxDirectory();
|
||||
@@ -335,22 +410,22 @@ namespace GameRes.Formats.DxLib
|
||||
return dir;
|
||||
}
|
||||
|
||||
void ReadFileTable (string root, uint table_offset)
|
||||
protected override void ReadFileTable (string root, long table_offset)
|
||||
{
|
||||
m_input.BaseStream.Position = m_header.DirTable + table_offset;
|
||||
m_input.Position = m_header.DirTable + table_offset;
|
||||
var dir = ReadDirEntry();
|
||||
if (dir.DirOffset != -1 && dir.ParentDirOffset != -1)
|
||||
{
|
||||
m_input.BaseStream.Position = m_header.FileTable + dir.DirOffset;
|
||||
m_input.Position = m_header.FileTable + dir.DirOffset;
|
||||
root = Path.Combine (root, ExtractFileName (m_input.ReadUInt32()));
|
||||
}
|
||||
long current_pos = m_header.FileTable + dir.FileTable;
|
||||
for (int i = 0; i < dir.FileCount; ++i)
|
||||
{
|
||||
m_input.BaseStream.Position = current_pos;
|
||||
m_input.Position = current_pos;
|
||||
uint name_offset = m_input.ReadUInt32();
|
||||
uint attr = m_input.ReadUInt32();
|
||||
m_input.BaseStream.Seek (0x18, SeekOrigin.Current);
|
||||
m_input.Seek (0x18, SeekOrigin.Current);
|
||||
uint offset = m_input.ReadUInt32();
|
||||
if (0 != (attr & 0x10)) // FILE_ATTRIBUTE_DIRECTORY
|
||||
{
|
||||
@@ -377,26 +452,75 @@ namespace GameRes.Formats.DxLib
|
||||
current_pos += m_entry_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string ExtractFileName (uint table_offset)
|
||||
internal sealed class IndexReaderV6 : IndexReader
|
||||
{
|
||||
readonly int m_entry_size;
|
||||
|
||||
public IndexReaderV6 (DxHeader header, int version, Stream input) : base (header, version, input)
|
||||
{
|
||||
m_input.BaseStream.Position = table_offset;
|
||||
int name_offset = m_input.ReadUInt16() * 4 + 4;
|
||||
m_input.BaseStream.Position = table_offset + name_offset;
|
||||
return m_input.BaseStream.ReadCString();
|
||||
m_entry_size = 0x40;
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool disposed = false;
|
||||
public void Dispose ()
|
||||
private class DxDirectory
|
||||
{
|
||||
if (!disposed)
|
||||
public long DirOffset;
|
||||
public long ParentDirOffset;
|
||||
public int FileCount;
|
||||
public long FileTable;
|
||||
}
|
||||
|
||||
DxDirectory ReadDirEntry ()
|
||||
{
|
||||
var dir = new DxDirectory();
|
||||
dir.DirOffset = m_input.ReadInt64();
|
||||
dir.ParentDirOffset = m_input.ReadInt64();
|
||||
dir.FileCount = (int)m_input.ReadInt64();
|
||||
dir.FileTable = m_input.ReadInt64();
|
||||
return dir;
|
||||
}
|
||||
|
||||
protected override void ReadFileTable (string root, long table_offset)
|
||||
{
|
||||
m_input.Position = m_header.DirTable + table_offset;
|
||||
var dir = ReadDirEntry();
|
||||
if (dir.DirOffset != -1 && dir.ParentDirOffset != -1)
|
||||
{
|
||||
m_input.Dispose();
|
||||
disposed = true;
|
||||
m_input.Position = m_header.FileTable + dir.DirOffset;
|
||||
root = Path.Combine (root, ExtractFileName (m_input.ReadInt64()));
|
||||
}
|
||||
long current_pos = m_header.FileTable + dir.FileTable;
|
||||
for (int i = 0; i < dir.FileCount; ++i)
|
||||
{
|
||||
m_input.Position = current_pos;
|
||||
var name_offset = m_input.ReadInt64();
|
||||
uint attr = (uint)m_input.ReadInt64();
|
||||
m_input.Seek (0x18, SeekOrigin.Current);
|
||||
var offset = m_input.ReadInt64();
|
||||
if (0 != (attr & 0x10)) // FILE_ATTRIBUTE_DIRECTORY
|
||||
{
|
||||
if (0 == offset || table_offset == offset)
|
||||
throw new InvalidFormatException ("Infinite recursion in DXA directory index");
|
||||
ReadFileTable (root, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = m_input.ReadInt64();
|
||||
var packed_size = m_input.ReadInt64();
|
||||
var entry = FormatCatalog.Instance.Create<PackedEntry> (Path.Combine (root, ExtractFileName (name_offset)));
|
||||
entry.Offset = m_header.BaseOffset + offset;
|
||||
entry.UnpackedSize = (uint)size;
|
||||
entry.IsPacked = -1 != packed_size;
|
||||
if (entry.IsPacked)
|
||||
entry.Size = (uint)packed_size;
|
||||
else
|
||||
entry.Size = (uint)size;
|
||||
m_dir.Add (entry);
|
||||
}
|
||||
current_pos += m_entry_size;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class EncryptedStream : ProxyStream
|
||||
|
||||
63
ArcFormats/ImagePSM.cs
Normal file
63
ArcFormats/ImagePSM.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! \file ImagePSM.cs
|
||||
//! \date Fri Jul 07 19:16:57 2017
|
||||
//! \brief Obfuscated PNG 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.Misc
|
||||
{
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class PsmFormat : PngFormat
|
||||
{
|
||||
public override string Tag { get { return "PSM"; } }
|
||||
public override string Description { get { return "Obfuscated PNG image"; } }
|
||||
public override uint Signature { get { return 0x474E50ED; } }
|
||||
|
||||
public override ImageData Read (IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
using (var png = DeobfuscateStream (file))
|
||||
return base.Read (png, info);
|
||||
}
|
||||
|
||||
IBinaryStream DeobfuscateStream (IBinaryStream file)
|
||||
{
|
||||
var header = file.ReadHeader (4).ToArray();
|
||||
header[0] = 0x89;
|
||||
var body = new StreamRegion (file.AsStream, 4, true);
|
||||
var png = new PrefixStream (header, body);
|
||||
return new BinaryStream (png, file.Name);
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData image)
|
||||
{
|
||||
var start_pos = file.Position;
|
||||
base.Write (file, image);
|
||||
var end_pos = file.Position;
|
||||
file.Position = start_pos;
|
||||
file.WriteByte (0xED);
|
||||
file.Position = end_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,17 +184,16 @@ namespace GameRes.Formats.KiriKiri
|
||||
entry.Size = (uint)packed_size;
|
||||
entry.UnpackedSize = (uint)file_size;
|
||||
|
||||
int name_size = header.ReadInt16();
|
||||
if (name_size > 0x100 || name_size <= 0)
|
||||
{
|
||||
goto NextEntry;
|
||||
}
|
||||
if (entry.IsEncrypted || ForceEncryptionQuery)
|
||||
entry.Cipher = crypt_algorithm.Value;
|
||||
else
|
||||
entry.Cipher = NoCryptAlgorithm;
|
||||
|
||||
var name = new string (header.ReadChars (name_size));
|
||||
var name = entry.Cipher.ReadName (header);
|
||||
if (null == name)
|
||||
{
|
||||
goto NextEntry;
|
||||
}
|
||||
if (entry.Cipher.ObfuscatedIndex && ObfuscatedPathRe.IsMatch (name))
|
||||
{
|
||||
goto NextEntry;
|
||||
|
||||
@@ -71,6 +71,18 @@ namespace GameRes.Formats.KiriKiri
|
||||
public virtual void Init (ArcFile arc)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read entry name from archive index.
|
||||
/// </summary>
|
||||
public virtual string ReadName (BinaryReader header)
|
||||
{
|
||||
int name_size = header.ReadInt16();
|
||||
if (name_size > 0 && name_size <= 0x100)
|
||||
return new string (header.ReadChars (name_size));
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -955,6 +967,22 @@ namespace GameRes.Formats.KiriKiri
|
||||
Dictionary<string, string> KnownNames = null;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class NanaCxCrypt : SenrenCxCrypt
|
||||
{
|
||||
uint m_random_seed;
|
||||
|
||||
public NanaCxCrypt (CxScheme scheme, uint seed) : base (scheme)
|
||||
{
|
||||
m_random_seed = seed;
|
||||
}
|
||||
|
||||
internal override CxProgram NewProgram (uint seed)
|
||||
{
|
||||
return new CxProgramNana (seed, m_random_seed, ControlBlock);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class KissCrypt : ICrypt
|
||||
{
|
||||
@@ -976,4 +1004,142 @@ namespace GameRes.Formats.KiriKiri
|
||||
Decrypt (entry, offset, data, pos, count);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class PuCaCrypt : ICrypt
|
||||
{
|
||||
public uint[] HashTable;
|
||||
public byte[] KeyTable;
|
||||
|
||||
public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
|
||||
{
|
||||
if (HashTable != null)
|
||||
{
|
||||
int i = Array.IndexOf (HashTable, entry.Hash);
|
||||
if (i != -1)
|
||||
{
|
||||
for (int j = 0; j < count; ++j)
|
||||
buffer[pos+j] ^= KeyTable[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
var hash_table = new byte[32];
|
||||
uint hash = entry.Hash;
|
||||
for (int k = 0; k < 32; k += 4)
|
||||
{
|
||||
if (0 != (hash & 1))
|
||||
hash |= 0x80000000;
|
||||
else
|
||||
hash &= 0x7FFFFFFF;
|
||||
LittleEndian.Pack (hash, hash_table, k);
|
||||
hash >>= 1;
|
||||
}
|
||||
var key_table = new byte[0x400];
|
||||
for (int l = 0; l < 32; ++l)
|
||||
{
|
||||
for (int m = 0; m < 32; ++m)
|
||||
key_table[32 * l + m] = (byte)(~hash_table[l] ^ hash_table[m]);
|
||||
}
|
||||
for (int n = 0; n < count; ++n)
|
||||
buffer[pos+n] ^= key_table[(offset + n) & 0x3FF];
|
||||
}
|
||||
|
||||
public override void Encrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
|
||||
{
|
||||
Decrypt (entry, offset, buffer, pos, count);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RhapsodyCrypt : ICrypt
|
||||
{
|
||||
public string FileListName { get; set; }
|
||||
|
||||
public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
|
||||
{
|
||||
var key = new byte[12];
|
||||
LittleEndian.Pack (entry.Hash, key, 0);
|
||||
LittleEndian.Pack (0x6E1DA9B2u, key, 4);
|
||||
LittleEndian.Pack (0x0040C800u, key, 8);
|
||||
int k = (int)(offset % 12);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
buffer[pos+i] ^= key[k++];
|
||||
if (12 == k)
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Encrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
|
||||
{
|
||||
Decrypt (entry, offset, buffer, pos, count);
|
||||
}
|
||||
|
||||
public override string ReadName (BinaryReader header)
|
||||
{
|
||||
if (null == KnownNames)
|
||||
ReadNames();
|
||||
uint key = header.ReadUInt32();
|
||||
uint name_hash = header.ReadUInt32() ^ key;
|
||||
string name;
|
||||
if (KnownNames.TryGetValue (name_hash, out name))
|
||||
return name;
|
||||
uint ext_hash = header.ReadUInt32() ^ key;
|
||||
name = name_hash.ToString ("X8");
|
||||
switch (ext_hash)
|
||||
{
|
||||
case 0x01854675: name += ".png"; break; // GetNameHash (".png")
|
||||
case 0x03D435DE: name += ".map"; break; // GetNameHash (".map")
|
||||
case 0x2D1F13E0: name += ".asd"; break; // GetNameHash (".asd")
|
||||
case 0x482F4319: name += ".tjs"; break; // GetNameHash (".tjs")
|
||||
case 0x58924012: name += ".txt"; break; // GetNameHash (".txt")
|
||||
case 0xB01C48CA: name += ".ks"; break; // GetNameHash (".ks")
|
||||
case 0xC0F7DFB2: name += ".wav"; break; // GetNameHash (".wav")
|
||||
case 0xE3A31D19: name += ".jpg"; break; // GetNameHash (".jpg")
|
||||
case 0xE7F3FEEB: name += ".ogg"; break; // GetNameHash (".ogg")
|
||||
default: name += ext_hash.ToString ("X8"); break;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
static uint GetNameHash (string name)
|
||||
{
|
||||
uint hash = 0;
|
||||
for (int i = 0; i < name.Length; ++i)
|
||||
{
|
||||
int c = char.ToLowerInvariant (name[i]);
|
||||
hash = 0x1000193u * hash ^ (byte)c;
|
||||
hash = 0x1000193u * hash ^ (byte)(c >> 8);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception X)
|
||||
{
|
||||
System.Diagnostics.Trace.WriteLine (X.Message, "[RhapsodyCrypt]");
|
||||
}
|
||||
KnownNames = names;
|
||||
}
|
||||
|
||||
[NonSerialized]
|
||||
Dictionary<uint, string> KnownNames = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ namespace GameRes.Formats.KiriKiri
|
||||
|
||||
CxProgram GenerateProgram (uint seed)
|
||||
{
|
||||
var program = new CxProgram (seed, ControlBlock);
|
||||
var program = NewProgram (seed);
|
||||
for (int stage = 5; stage > 0; --stage)
|
||||
{
|
||||
if (EmitCode (program, stage))
|
||||
@@ -228,6 +228,11 @@ namespace GameRes.Formats.KiriKiri
|
||||
throw new CxProgramException ("Overly large CxEncryption bytecode");
|
||||
}
|
||||
|
||||
internal virtual CxProgram NewProgram (uint seed)
|
||||
{
|
||||
return new CxProgram (seed, ControlBlock);
|
||||
}
|
||||
|
||||
bool EmitCode (CxProgram program, int stage)
|
||||
{
|
||||
return program.EmitNop (5) // 0x57 0x56 0x53 0x51 0x52
|
||||
@@ -437,7 +442,7 @@ namespace GameRes.Formats.KiriKiri
|
||||
private List<uint> m_code = new List<uint> (LengthLimit);
|
||||
private uint[] m_ControlBlock;
|
||||
private int m_length;
|
||||
private uint m_seed;
|
||||
protected uint m_seed;
|
||||
|
||||
class Context
|
||||
{
|
||||
@@ -562,7 +567,7 @@ namespace GameRes.Formats.KiriKiri
|
||||
return EmitUInt32 (GetRandom());
|
||||
}
|
||||
|
||||
public uint GetRandom ()
|
||||
public virtual uint GetRandom ()
|
||||
{
|
||||
uint seed = m_seed;
|
||||
m_seed = 1103515245 * seed + 12345;
|
||||
@@ -570,6 +575,27 @@ namespace GameRes.Formats.KiriKiri
|
||||
}
|
||||
}
|
||||
|
||||
internal class CxProgramNana : CxProgram
|
||||
{
|
||||
protected uint m_random_seed;
|
||||
|
||||
public CxProgramNana (uint seed, uint random_seed, uint[] control_block) : base (seed, control_block)
|
||||
{
|
||||
m_random_seed = random_seed;
|
||||
}
|
||||
|
||||
public override uint GetRandom ()
|
||||
{
|
||||
uint s = m_seed ^ (m_seed << 17);
|
||||
s ^= (s << 18) | (s >> 15);
|
||||
m_seed = ~s;
|
||||
uint r = m_random_seed ^ (m_random_seed << 13);
|
||||
r ^= r >> 17;
|
||||
m_random_seed = r ^ (r << 5);
|
||||
return m_seed ^ m_random_seed;
|
||||
}
|
||||
}
|
||||
|
||||
/* CxEncryption base branch order
|
||||
OddBranchOrder
|
||||
{
|
||||
|
||||
@@ -27,8 +27,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GameRes.Cryptography;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Malie
|
||||
@@ -115,46 +113,15 @@ namespace GameRes.Formats.Malie
|
||||
|
||||
public class MalieArchive : ArcFile
|
||||
{
|
||||
public readonly Camellia Encryption;
|
||||
public readonly IMalieDecryptor Decryptor;
|
||||
|
||||
public MalieArchive (ArcView file, ArchiveFormat format, ICollection<Entry> dir, Camellia encryption)
|
||||
public MalieArchive (ArcView file, ArchiveFormat format, ICollection<Entry> dir, IMalieDecryptor decr)
|
||||
: base (file, format, dir)
|
||||
{
|
||||
Encryption = encryption;
|
||||
Decryptor = decr;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LibScheme
|
||||
{
|
||||
public uint DataAlign;
|
||||
public uint[] Key;
|
||||
|
||||
public LibScheme (uint[] key) : this (0x1000, key)
|
||||
{
|
||||
}
|
||||
|
||||
public LibScheme (uint align, uint[] key)
|
||||
{
|
||||
DataAlign = align;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public LibScheme (string key) : this (Camellia.GenerateKey (key))
|
||||
{
|
||||
}
|
||||
|
||||
public LibScheme (uint align, string key) : this (align, Camellia.GenerateKey (key))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class MalieScheme : ResourceScheme
|
||||
{
|
||||
public Dictionary<string, LibScheme> KnownSchemes;
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class DatOpener : ArchiveFormat
|
||||
{
|
||||
@@ -167,6 +134,7 @@ namespace GameRes.Formats.Malie
|
||||
public DatOpener ()
|
||||
{
|
||||
Extensions = new string[] { "lib", "dat" };
|
||||
Signatures = new uint[] { 0, 0x3F503FB1 };
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
@@ -176,19 +144,19 @@ namespace GameRes.Formats.Malie
|
||||
var header = new byte[0x10];
|
||||
foreach (var scheme in KnownSchemes.Values)
|
||||
{
|
||||
var encryption = new Camellia (scheme.Key);
|
||||
ReadEncrypted (file.View, encryption, 0, header, 0, 0x10);
|
||||
var decryptor = scheme.CreateDecryptor();
|
||||
ReadEncrypted (file.View, decryptor, 0, header, 0, 0x10);
|
||||
ILibIndexReader reader;
|
||||
if (Binary.AsciiEqual (header, 0, "LIBP"))
|
||||
reader = new LibPReader (file, encryption, header, scheme);
|
||||
reader = new LibPReader (file, decryptor, header, scheme);
|
||||
else if (Binary.AsciiEqual (header, 0, "LIBU"))
|
||||
reader = LibUReader.Create (file, encryption);
|
||||
reader = LibUReader.Create (file, decryptor);
|
||||
else
|
||||
continue;
|
||||
using (reader)
|
||||
{
|
||||
if (reader.ReadIndex())
|
||||
return new MalieArchive (file, this, reader.Dir, encryption);
|
||||
return new MalieArchive (file, this, reader.Dir, decryptor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -199,25 +167,25 @@ namespace GameRes.Formats.Malie
|
||||
var march = arc as MalieArchive;
|
||||
if (null == march)
|
||||
return arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
var input = new EncryptedStream (march.File, march.Encryption);
|
||||
var input = new EncryptedStream (march.File, march.Decryptor);
|
||||
return new StreamRegion (input, entry.Offset, entry.Size);
|
||||
}
|
||||
|
||||
internal abstract class LibIndexReader : ILibIndexReader
|
||||
{
|
||||
protected ArcView.Frame m_view;
|
||||
protected readonly long m_max_offset;
|
||||
protected Camellia m_enc;
|
||||
protected List<Entry> m_dir = new List<Entry>();
|
||||
protected byte[] m_header;
|
||||
protected ArcView.Frame m_view;
|
||||
protected readonly long m_max_offset;
|
||||
protected IMalieDecryptor m_dec;
|
||||
protected List<Entry> m_dir = new List<Entry>();
|
||||
protected byte[] m_header;
|
||||
|
||||
public List<Entry> Dir { get { return m_dir; } }
|
||||
|
||||
protected LibIndexReader (ArcView file, Camellia encryption, byte[] header)
|
||||
protected LibIndexReader (ArcView file, IMalieDecryptor decryptor, byte[] header)
|
||||
{
|
||||
m_view = file.View;
|
||||
m_max_offset = file.MaxOffset;
|
||||
m_enc = encryption;
|
||||
m_dec = decryptor;
|
||||
m_header = header;
|
||||
}
|
||||
|
||||
@@ -238,16 +206,16 @@ namespace GameRes.Formats.Malie
|
||||
|
||||
internal class LibPReader : LibIndexReader
|
||||
{
|
||||
byte[] m_index;
|
||||
long m_base_offset;
|
||||
long m_data_align;
|
||||
uint[] m_offset_table;
|
||||
byte[] m_index;
|
||||
long m_base_offset;
|
||||
uint[] m_offset_table;
|
||||
LibScheme m_scheme;
|
||||
|
||||
public LibPReader (ArcView file, Camellia encryption, byte[] header, LibScheme scheme)
|
||||
: base (file, encryption, header)
|
||||
public LibPReader (ArcView file, IMalieDecryptor decryptor, byte[] header, LibScheme scheme)
|
||||
: base (file, decryptor, header)
|
||||
{
|
||||
m_base_offset = 0;
|
||||
m_data_align = scheme.DataAlign - 1;
|
||||
m_scheme = scheme;
|
||||
}
|
||||
|
||||
public override bool ReadIndex ()
|
||||
@@ -261,16 +229,16 @@ namespace GameRes.Formats.Malie
|
||||
var offsets = new byte[4 * offset_count];
|
||||
|
||||
m_base_offset += 0x10;
|
||||
if (m_index.Length != ReadEncrypted (m_view, m_enc, m_base_offset, m_index, 0, m_index.Length))
|
||||
if (m_index.Length != ReadEncrypted (m_view, m_dec, m_base_offset, m_index, 0, m_index.Length))
|
||||
return false;
|
||||
m_base_offset += m_index.Length;
|
||||
if (offsets.Length != ReadEncrypted (m_view, m_enc, m_base_offset, offsets, 0, offsets.Length))
|
||||
if (offsets.Length != ReadEncrypted (m_view, m_dec, m_base_offset, offsets, 0, offsets.Length))
|
||||
return false;
|
||||
m_offset_table = new uint[offset_count];
|
||||
Buffer.BlockCopy (offsets, 0, m_offset_table, 0, offsets.Length);
|
||||
|
||||
m_base_offset += offsets.Length;
|
||||
m_base_offset = (m_base_offset + m_data_align) & ~m_data_align;
|
||||
m_base_offset = m_scheme.GetAlignedOffset (m_base_offset);
|
||||
|
||||
m_dir.Capacity = offset_count;
|
||||
ReadDir ("", 0, 1);
|
||||
@@ -306,7 +274,7 @@ namespace GameRes.Formats.Malie
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReadEncrypted (ArcView.Frame view, Camellia enc, long offset, byte[] buffer, int index, int length)
|
||||
private static int ReadEncrypted (ArcView.Frame view, IMalieDecryptor dec, long offset, byte[] buffer, int index, int length)
|
||||
{
|
||||
int offset_pad = (int)offset & 0xF;
|
||||
int aligned_len = (offset_pad + length + 0xF) & ~0xF;
|
||||
@@ -328,7 +296,7 @@ namespace GameRes.Formats.Malie
|
||||
|
||||
for (int block_count = aligned_len / 0x10; block_count > 0; --block_count)
|
||||
{
|
||||
enc.DecryptBlock (offset, aligned_buf, block);
|
||||
dec.DecryptBlock (offset, aligned_buf, block);
|
||||
block += 0x10;
|
||||
offset += 0x10;
|
||||
}
|
||||
@@ -345,110 +313,4 @@ namespace GameRes.Formats.Malie
|
||||
set { KnownSchemes = ((MalieScheme)value).KnownSchemes; }
|
||||
}
|
||||
}
|
||||
|
||||
internal class EncryptedStream : Stream
|
||||
{
|
||||
ArcView.Frame m_view;
|
||||
Camellia m_enc;
|
||||
long m_max_offset;
|
||||
long m_position = 0;
|
||||
byte[] m_current_block = new byte[BlockLength];
|
||||
int m_current_block_length = 0;
|
||||
long m_current_block_position = 0;
|
||||
|
||||
public const int BlockLength = 0x1000;
|
||||
|
||||
public Camellia Encryption { get { return m_enc; } }
|
||||
|
||||
public EncryptedStream (ArcView mmap, Camellia encryption)
|
||||
{
|
||||
m_view = mmap.CreateFrame();
|
||||
m_enc = encryption;
|
||||
m_max_offset = mmap.MaxOffset;
|
||||
}
|
||||
|
||||
public override int Read (byte[] buf, int index, int count)
|
||||
{
|
||||
int total_read = 0;
|
||||
bool refill_buffer = !(m_position >= m_current_block_position && m_position < m_current_block_position + m_current_block_length);
|
||||
while (count > 0 && m_position < m_max_offset)
|
||||
{
|
||||
if (refill_buffer)
|
||||
{
|
||||
m_current_block_position = m_position & ~((long)BlockLength-1);
|
||||
FillBuffer();
|
||||
}
|
||||
int src_offset = (int)m_position & (BlockLength-1);
|
||||
int available = Math.Min (count, m_current_block_length - src_offset);
|
||||
Buffer.BlockCopy (m_current_block, src_offset, buf, index, available);
|
||||
m_position += available;
|
||||
total_read += available;
|
||||
index += available;
|
||||
count -= available;
|
||||
refill_buffer = true;
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
private void FillBuffer ()
|
||||
{
|
||||
m_current_block_length = m_view.Read (m_current_block_position, m_current_block, 0, (uint)BlockLength);
|
||||
for (int offset = 0; offset < m_current_block_length; offset += 0x10)
|
||||
{
|
||||
m_enc.DecryptBlock (m_current_block_position+offset, m_current_block, offset);
|
||||
}
|
||||
}
|
||||
|
||||
#region IO.Stream methods
|
||||
public override bool CanRead { get { return !m_disposed; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
public override bool CanSeek { get { return !m_disposed; } }
|
||||
|
||||
public override long Length { get { return m_max_offset; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_position; }
|
||||
set { m_position = value; }
|
||||
}
|
||||
|
||||
public override long Seek (long pos, SeekOrigin whence)
|
||||
{
|
||||
if (SeekOrigin.Current == whence)
|
||||
m_position += pos;
|
||||
else if (SeekOrigin.End == whence)
|
||||
m_position = m_max_offset + pos;
|
||||
else
|
||||
m_position = pos;
|
||||
return m_position;
|
||||
}
|
||||
|
||||
public override void Write (byte[] buf, int index, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength (long length)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush ()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable methods
|
||||
bool m_disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!m_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
m_view.Dispose();
|
||||
m_disposed = true;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ namespace GameRes.Formats.Malie
|
||||
return new LibUReader (input);
|
||||
}
|
||||
|
||||
public static LibUReader Create (ArcView file, Camellia encryption)
|
||||
public static LibUReader Create (ArcView file, IMalieDecryptor decryptor)
|
||||
{
|
||||
var input = new EncryptedStream (file, encryption);
|
||||
var input = new EncryptedStream (file, decryptor);
|
||||
return new LibUReader (input);
|
||||
}
|
||||
|
||||
|
||||
102
ArcFormats/Malie/LibScheme.cs
Normal file
102
ArcFormats/Malie/LibScheme.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! \file LibScheme.cs
|
||||
//! \date Tue Jun 06 22:47:22 2017
|
||||
//! \brief Malie encryption schemes.
|
||||
//
|
||||
// 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 GameRes.Cryptography;
|
||||
|
||||
namespace GameRes.Formats.Malie
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class LibScheme
|
||||
{
|
||||
uint DataAlign;
|
||||
|
||||
public LibScheme (uint align)
|
||||
{
|
||||
DataAlign = align;
|
||||
}
|
||||
|
||||
public abstract IMalieDecryptor CreateDecryptor ();
|
||||
|
||||
public virtual long GetAlignedOffset (long offset)
|
||||
{
|
||||
long align = DataAlign - 1;
|
||||
return (offset + align) & ~align;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LibCamelliaScheme : LibScheme
|
||||
{
|
||||
public uint[] Key { get; set; }
|
||||
|
||||
public LibCamelliaScheme (uint[] key) : this (0x1000, key)
|
||||
{
|
||||
}
|
||||
|
||||
public LibCamelliaScheme (uint align, uint[] key) : base (align)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public LibCamelliaScheme (string key) : this (Camellia.GenerateKey (key))
|
||||
{
|
||||
}
|
||||
|
||||
public LibCamelliaScheme (uint align, string key) : this (align, Camellia.GenerateKey (key))
|
||||
{
|
||||
}
|
||||
|
||||
public override IMalieDecryptor CreateDecryptor ()
|
||||
{
|
||||
return new CamelliaDecryptor (Key);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LibCfiScheme : LibScheme
|
||||
{
|
||||
public byte[] Key { get; set; }
|
||||
public uint[] RotateKey { get; set; }
|
||||
|
||||
public LibCfiScheme (uint align, byte[] key, uint[] rot_key) : base (align)
|
||||
{
|
||||
Key = key;
|
||||
RotateKey = rot_key;
|
||||
}
|
||||
|
||||
public override IMalieDecryptor CreateDecryptor ()
|
||||
{
|
||||
return new CfiDecryptor (Key, RotateKey);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class MalieScheme : ResourceScheme
|
||||
{
|
||||
public Dictionary<string, LibScheme> KnownSchemes;
|
||||
}
|
||||
}
|
||||
202
ArcFormats/Malie/MalieEncryption.cs
Normal file
202
ArcFormats/Malie/MalieEncryption.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
//! \file MalieEncryption.cs
|
||||
//! \date Tue Jun 06 20:38:57 2017
|
||||
//! \brief Malie System encryption implementation.
|
||||
//
|
||||
// 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 GameRes.Cryptography;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Malie
|
||||
{
|
||||
public interface IMalieDecryptor
|
||||
{
|
||||
void DecryptBlock (long block_offset, byte[] buffer, int index);
|
||||
}
|
||||
|
||||
public class CamelliaDecryptor : IMalieDecryptor
|
||||
{
|
||||
Camellia m_enc;
|
||||
|
||||
public CamelliaDecryptor (uint[] key)
|
||||
{
|
||||
m_enc = new Camellia (key);
|
||||
}
|
||||
|
||||
public void DecryptBlock (long block_offset, byte[] buffer, int index)
|
||||
{
|
||||
m_enc.DecryptBlock (block_offset, buffer, index);
|
||||
}
|
||||
}
|
||||
|
||||
public class CfiDecryptor : IMalieDecryptor
|
||||
{
|
||||
byte[] m_key;
|
||||
uint[] m_rotate_key;
|
||||
|
||||
public CfiDecryptor (byte[] key, uint[] rotate_key)
|
||||
{
|
||||
m_key = key;
|
||||
m_rotate_key = rotate_key;
|
||||
}
|
||||
|
||||
public void DecryptBlock (long block_offset, byte[] data, int index)
|
||||
{
|
||||
if (null == data)
|
||||
throw new ArgumentNullException ("data");
|
||||
if (index < 0 || index + 0x10 > data.Length)
|
||||
throw new ArgumentOutOfRangeException ("index");
|
||||
int offset = (int)block_offset;
|
||||
int o = offset & 0xF;
|
||||
byte first = data[index+o];
|
||||
for (int i = 0; i < 0x10; ++i)
|
||||
{
|
||||
if (o != i)
|
||||
data[index+i] ^= first;
|
||||
}
|
||||
offset >>= 4;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* data8 = &data[index])
|
||||
{
|
||||
uint* data32 = (uint*)data8;
|
||||
uint k = Binary.RotR (m_rotate_key[0], m_key[offset & 0x1F] ^ 0xA5);
|
||||
data32[0] = Binary.RotR (data32[0] ^ k, m_key[(offset + 12) & 0x1F] ^ 0xA5);
|
||||
k = Binary.RotL (m_rotate_key[1], m_key[(offset + 3) & 0x1F] ^ 0xA5);
|
||||
data32[1] = Binary.RotL (data32[1] ^ k, m_key[(offset + 15) & 0x1F] ^ 0xA5);
|
||||
k = Binary.RotR (m_rotate_key[2], m_key[(offset + 6) & 0x1F] ^ 0xA5);
|
||||
data32[2] = Binary.RotR (data32[2] ^ k, m_key[(offset - 14) & 0x1F] ^ 0xA5);
|
||||
k = Binary.RotL (m_rotate_key[3], m_key[(offset + 9) & 0x1F] ^ 0xA5);
|
||||
data32[3] = Binary.RotL (data32[3] ^ k, m_key[(offset - 11) & 0x1F] ^ 0xA5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class EncryptedStream : Stream
|
||||
{
|
||||
ArcView.Frame m_view;
|
||||
IMalieDecryptor m_dec;
|
||||
long m_max_offset;
|
||||
long m_position = 0;
|
||||
byte[] m_current_block = new byte[BlockLength];
|
||||
int m_current_block_length = 0;
|
||||
long m_current_block_position = 0;
|
||||
|
||||
public const int BlockLength = 0x1000;
|
||||
|
||||
public IMalieDecryptor Decryptor { get { return m_dec; } }
|
||||
|
||||
public EncryptedStream (ArcView mmap, IMalieDecryptor decryptor)
|
||||
{
|
||||
m_view = mmap.CreateFrame();
|
||||
m_dec = decryptor;
|
||||
m_max_offset = mmap.MaxOffset;
|
||||
}
|
||||
|
||||
public override int Read (byte[] buf, int index, int count)
|
||||
{
|
||||
int total_read = 0;
|
||||
bool refill_buffer = !(m_position >= m_current_block_position && m_position < m_current_block_position + m_current_block_length);
|
||||
while (count > 0 && m_position < m_max_offset)
|
||||
{
|
||||
if (refill_buffer)
|
||||
{
|
||||
m_current_block_position = m_position & ~((long)BlockLength-1);
|
||||
FillBuffer();
|
||||
}
|
||||
int src_offset = (int)m_position & (BlockLength-1);
|
||||
int available = Math.Min (count, m_current_block_length - src_offset);
|
||||
Buffer.BlockCopy (m_current_block, src_offset, buf, index, available);
|
||||
m_position += available;
|
||||
total_read += available;
|
||||
index += available;
|
||||
count -= available;
|
||||
refill_buffer = true;
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
private void FillBuffer ()
|
||||
{
|
||||
m_current_block_length = m_view.Read (m_current_block_position, m_current_block, 0, (uint)BlockLength);
|
||||
for (int offset = 0; offset < m_current_block_length; offset += 0x10)
|
||||
{
|
||||
m_dec.DecryptBlock (m_current_block_position+offset, m_current_block, offset);
|
||||
}
|
||||
}
|
||||
|
||||
#region IO.Stream methods
|
||||
public override bool CanRead { get { return !m_disposed; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
public override bool CanSeek { get { return !m_disposed; } }
|
||||
|
||||
public override long Length { get { return m_max_offset; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_position; }
|
||||
set { m_position = value; }
|
||||
}
|
||||
|
||||
public override long Seek (long pos, SeekOrigin whence)
|
||||
{
|
||||
if (SeekOrigin.Current == whence)
|
||||
m_position += pos;
|
||||
else if (SeekOrigin.End == whence)
|
||||
m_position = m_max_offset + pos;
|
||||
else
|
||||
m_position = pos;
|
||||
return m_position;
|
||||
}
|
||||
|
||||
public override void Write (byte[] buf, int index, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength (long length)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush ()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable methods
|
||||
bool m_disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!m_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
m_view.Dispose();
|
||||
m_disposed = true;
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ namespace GameRes.Formats.Pajamas
|
||||
|
||||
public override ImageData Read (IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
var meta = (EpaMetaData)info as EpaMetaData;
|
||||
var meta = (EpaMetaData)info;
|
||||
file.Position = 2 == meta.Mode ? 0x18 : 0x10;
|
||||
var reader = new Reader (file.AsStream, meta);
|
||||
reader.Unpack();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! \date Fri Sep 30 10:37:28 2016
|
||||
//! \brief Primel the Adventure System 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
|
||||
@@ -35,8 +35,19 @@ namespace GameRes.Formats.Primel
|
||||
{
|
||||
internal class PcfEntry : PackedEntry
|
||||
{
|
||||
public uint Flags;
|
||||
public IEnumerable<byte> Key;
|
||||
public uint Flags;
|
||||
public byte[] Key;
|
||||
}
|
||||
|
||||
internal class PcfArchive : ArcFile
|
||||
{
|
||||
public readonly PrimelScheme Scheme;
|
||||
|
||||
public PcfArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, PrimelScheme scheme)
|
||||
: base (arc, impl, dir)
|
||||
{
|
||||
Scheme = scheme;
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
@@ -55,49 +66,23 @@ namespace GameRes.Formats.Primel
|
||||
int count = file.View.ReadInt32 (8);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
long data_size = file.View.ReadInt64 (0x10);
|
||||
long index_offset = file.View.ReadInt64 (0x28);
|
||||
if (data_size >= file.MaxOffset || index_offset >= file.MaxOffset)
|
||||
var reader = new PcfIndexReader (file, count);
|
||||
var dir = reader.Read();
|
||||
if (null == dir)
|
||||
return null;
|
||||
uint index_size = file.View.ReadUInt32 (0x30);
|
||||
uint flags = file.View.ReadUInt32 (0x38);
|
||||
var key = file.View.ReadBytes (0x58, 8);
|
||||
long base_offset = file.MaxOffset - data_size;
|
||||
|
||||
using (var stream = file.CreateStream (base_offset + index_offset, index_size))
|
||||
using (var index = ReadFile (stream, key, flags))
|
||||
{
|
||||
var buffer = new byte[0x80];
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (buffer.Length != index.Read (buffer, 0, buffer.Length))
|
||||
break;
|
||||
var name = Binary.GetCString (buffer, 0, 0x50);
|
||||
var entry = FormatCatalog.Instance.Create<PcfEntry> (name);
|
||||
entry.Offset = LittleEndian.ToInt64 (buffer, 0x50) + base_offset;
|
||||
entry.UnpackedSize = LittleEndian.ToUInt32 (buffer, 0x58);
|
||||
entry.Size = LittleEndian.ToUInt32 (buffer, 0x60);
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
entry.Flags = LittleEndian.ToUInt32 (buffer, 0x68);
|
||||
entry.Key = new ArraySegment<byte> (buffer, 0x78, 8).ToArray();
|
||||
entry.IsPacked = entry.UnpackedSize != entry.Size;
|
||||
dir.Add (entry);
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
return new PcfArchive (file, this, dir, reader.Scheme);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var parc = arc as PcfArchive;
|
||||
var pent = entry as PcfEntry;
|
||||
if (null == pent)
|
||||
if (null == pent || null == parc)
|
||||
return base.OpenEntry (arc, entry);
|
||||
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
try
|
||||
{
|
||||
input = ReadFile (input, pent.Key, pent.Flags);
|
||||
input = parc.Scheme.TransformStream (input, pent.Key, pent.Flags);
|
||||
if (pent.IsPacked)
|
||||
input = new LimitStream (input, pent.UnpackedSize);
|
||||
return input;
|
||||
@@ -108,8 +93,85 @@ namespace GameRes.Formats.Primel
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream ReadFile (Stream input, IEnumerable<byte> key, uint flags)
|
||||
internal sealed class PcfIndexReader
|
||||
{
|
||||
ArcView m_file;
|
||||
int m_count;
|
||||
long m_base_offset;
|
||||
List<Entry> m_dir;
|
||||
|
||||
public PrimelScheme Scheme { get; set; }
|
||||
|
||||
public PcfIndexReader (ArcView file, int count)
|
||||
{
|
||||
m_file = file;
|
||||
m_count = count;
|
||||
m_dir = new List<Entry> (m_count);
|
||||
}
|
||||
|
||||
static readonly PrimelScheme[] KnownSchemes = {
|
||||
new PrimelScheme(), new PrimelSchemeV2()
|
||||
};
|
||||
|
||||
public List<Entry> Read ()
|
||||
{
|
||||
long data_size = m_file.View.ReadInt64 (0x10);
|
||||
long index_offset = m_file.View.ReadInt64 (0x28);
|
||||
if (data_size >= m_file.MaxOffset || index_offset >= m_file.MaxOffset)
|
||||
return null;
|
||||
uint index_size = m_file.View.ReadUInt32 (0x30);
|
||||
uint flags = m_file.View.ReadUInt32 (0x38);
|
||||
var key = m_file.View.ReadBytes (0x58, 8);
|
||||
m_base_offset = m_file.MaxOffset - data_size;
|
||||
foreach (var scheme in KnownSchemes)
|
||||
{
|
||||
m_dir.Clear();
|
||||
try
|
||||
{
|
||||
using (var stream = m_file.CreateStream (m_base_offset + index_offset, index_size))
|
||||
using (var index = scheme.TransformStream (stream, key, flags))
|
||||
{
|
||||
if (ReadIndex (index))
|
||||
{
|
||||
this.Scheme = scheme;
|
||||
return m_dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* invalid scheme, retry */ }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] m_buffer = new byte[0x80];
|
||||
|
||||
bool ReadIndex (Stream index)
|
||||
{
|
||||
for (int i = 0; i < m_count; ++i)
|
||||
{
|
||||
if (m_buffer.Length != index.Read (m_buffer, 0, m_buffer.Length))
|
||||
break;
|
||||
var name = Binary.GetCString (m_buffer, 0, 0x50);
|
||||
var entry = FormatCatalog.Instance.Create<PcfEntry> (name);
|
||||
entry.Offset = LittleEndian.ToInt64 (m_buffer, 0x50) + m_base_offset;
|
||||
entry.UnpackedSize = LittleEndian.ToUInt32 (m_buffer, 0x58);
|
||||
entry.Size = LittleEndian.ToUInt32 (m_buffer, 0x60);
|
||||
if (!entry.CheckPlacement (m_file.MaxOffset))
|
||||
return false;
|
||||
entry.Flags = LittleEndian.ToUInt32 (m_buffer, 0x68);
|
||||
entry.Key = new ArraySegment<byte> (m_buffer, 0x78, 8).ToArray();
|
||||
entry.IsPacked = entry.UnpackedSize != entry.Size;
|
||||
m_dir.Add (entry);
|
||||
}
|
||||
return m_dir.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class PrimelScheme
|
||||
{
|
||||
public Stream TransformStream (Stream input, byte[] key, uint flags)
|
||||
{
|
||||
var key1 = GenerateKey (key);
|
||||
var iv = GenerateKey (key1);
|
||||
@@ -168,10 +230,9 @@ namespace GameRes.Formats.Primel
|
||||
}
|
||||
}
|
||||
|
||||
byte[] GenerateKey (IEnumerable<byte> seed)
|
||||
byte[] GenerateKey (byte[] seed)
|
||||
{
|
||||
var sha = new Primel.SHA256();
|
||||
var hash = sha.ComputeHash (seed.ToArray());
|
||||
var hash = ComputeHash (seed);
|
||||
var key = new byte[0x10];
|
||||
for (int i = 0; i < hash.Length; ++i)
|
||||
{
|
||||
@@ -179,5 +240,20 @@ namespace GameRes.Formats.Primel
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
protected virtual byte[] ComputeHash (byte[] seed)
|
||||
{
|
||||
var sha = new Primel.SHA256();
|
||||
return sha.ComputeHash (seed);
|
||||
}
|
||||
}
|
||||
|
||||
internal class PrimelSchemeV2 : PrimelScheme
|
||||
{
|
||||
protected override byte[] ComputeHash (byte[] seed)
|
||||
{
|
||||
using (var sha = System.Security.Cryptography.SHA256.Create())
|
||||
return sha.ComputeHash (seed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! \date Mon Oct 03 04:16:11 2016
|
||||
//! \brief Primel the Adventure System 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
|
||||
@@ -97,6 +97,14 @@ namespace GameRes.Formats.Primel
|
||||
}
|
||||
|
||||
public void Unpack ()
|
||||
{
|
||||
if (0x800 == (m_info.Flags & 0xFF00))
|
||||
UnpackV2();
|
||||
else
|
||||
UnpackV1();
|
||||
}
|
||||
|
||||
void UnpackV1 ()
|
||||
{
|
||||
m_input.Input.Position = 0x30;
|
||||
int pixel_size = m_info.BPP / 8;
|
||||
@@ -148,6 +156,63 @@ namespace GameRes.Formats.Primel
|
||||
}
|
||||
}
|
||||
|
||||
void UnpackV2 ()
|
||||
{
|
||||
m_input.Input.Position = 0x30;
|
||||
int pixel_size = m_info.BPP / 8;
|
||||
int blocks_w = (int)(m_info.Width + 7) / 8;
|
||||
int blocks_h = (int)(m_info.Height + 7) / 8;
|
||||
short[,] block = new short[pixel_size, 64];
|
||||
|
||||
for (int by = 0; by < blocks_h; ++by)
|
||||
{
|
||||
int dst_line = by * 8 * m_stride;
|
||||
for (int bx = 0; bx < blocks_w; ++bx)
|
||||
{
|
||||
int dst_block = dst_line + bx * 8 * pixel_size;
|
||||
|
||||
for (int i = 0; i < pixel_size; ++i)
|
||||
{
|
||||
for (int j = 0; j < 64; ++j)
|
||||
block[i,j] = 0;
|
||||
RestoreBlockV2 (block, i);
|
||||
}
|
||||
|
||||
for (int y = 0; y < 8; ++y)
|
||||
{
|
||||
if (by + 1 == blocks_h && (by * 8 + y) >= m_info.Height)
|
||||
break;
|
||||
|
||||
int src = y * 8;
|
||||
int dst = dst_block + y * m_stride;
|
||||
for (int x = 0; x < 8; ++x)
|
||||
{
|
||||
if (bx + 1 == blocks_w && (bx * 8 + x) >= m_info.Width)
|
||||
break;
|
||||
if (4 == pixel_size)
|
||||
{
|
||||
m_output[dst + x * 4 + 2] = (byte)block[0, src+x];
|
||||
m_output[dst + x * 4 + 1] = (byte)block[1, src+x];
|
||||
m_output[dst + x * 4] = (byte)block[2, src+x];
|
||||
m_output[dst + x * 4 + 3] = (byte)block[3, src+x];
|
||||
}
|
||||
else if (3 == pixel_size)
|
||||
{
|
||||
m_output[dst + x * 3 + 2] = (byte)block[0, src+x];
|
||||
m_output[dst + x * 3 + 1] = (byte)block[1, src+x];
|
||||
m_output[dst + x * 3] = (byte)block[2, src+x];
|
||||
}
|
||||
else
|
||||
{
|
||||
var val = block[0, src+x];
|
||||
m_output[dst + x] = (byte)val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RestoreBlock (short[,] block, int n)
|
||||
{
|
||||
int row = 8;
|
||||
@@ -168,6 +233,31 @@ namespace GameRes.Formats.Primel
|
||||
}
|
||||
}
|
||||
|
||||
void RestoreBlockV2 (short[,] block, int plane)
|
||||
{
|
||||
int skip;
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
int n = GetIntV2 (out skip);
|
||||
if (n != 0)
|
||||
block[plane, ZigzagOrder[i]] = (short)n;
|
||||
else if (0 == skip)
|
||||
break;
|
||||
else
|
||||
i += skip - 1;
|
||||
}
|
||||
for (int row = 0; row < 64; row += 8)
|
||||
for (int x = 1; x < 8; ++x)
|
||||
{
|
||||
block[plane, row+x] += block[plane, row+x-1];
|
||||
}
|
||||
for (int row = 8; row < 64; row += 8)
|
||||
for (int x = 0; x < 8; ++x)
|
||||
{
|
||||
block[plane, row+x] += block[plane, row-8+x];
|
||||
}
|
||||
}
|
||||
|
||||
int GetInt ()
|
||||
{
|
||||
int count = m_input.GetBits (4);
|
||||
@@ -194,6 +284,40 @@ namespace GameRes.Formats.Primel
|
||||
}
|
||||
}
|
||||
|
||||
int GetIntV2 (out int repeat)
|
||||
{
|
||||
int count = m_input.GetBits (4);
|
||||
repeat = 0;
|
||||
switch (count)
|
||||
{
|
||||
case 0:
|
||||
repeat = 1;
|
||||
while (repeat < 16 && 1 == m_input.GetNextBit())
|
||||
++repeat;
|
||||
if (16 == repeat)
|
||||
repeat = 0;
|
||||
return 0;
|
||||
|
||||
case 1: return 1;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
return m_input.GetBits (count - 1) + (1 << (count - 1));
|
||||
|
||||
case 8: return -1;
|
||||
case 9: return -2;
|
||||
|
||||
default:
|
||||
return m_input.GetBits (count - 9) - (2 << (count - 9));
|
||||
|
||||
case -1: throw new EndOfStreamException();
|
||||
}
|
||||
}
|
||||
|
||||
bool _disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
|
||||
@@ -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.33.1382")]
|
||||
[assembly: AssemblyFileVersion ("1.2.33.1382")]
|
||||
[assembly: AssemblyVersion ("1.2.35.1414")]
|
||||
[assembly: AssemblyFileVersion ("1.2.35.1414")]
|
||||
|
||||
@@ -38,10 +38,12 @@ namespace GameRes.Formats.Qlie
|
||||
{
|
||||
internal class QlieEntry : PackedEntry
|
||||
{
|
||||
public bool IsEncrypted;
|
||||
public int EncryptionMethod;
|
||||
public uint Hash;
|
||||
public byte[] RawName;
|
||||
|
||||
public bool IsEncrypted { get { return EncryptionMethod != 0; } }
|
||||
|
||||
/// <summary>
|
||||
/// Data from a separate key file "key.fkey" that comes with installed game.
|
||||
/// null if not used.
|
||||
@@ -153,7 +155,7 @@ namespace GameRes.Formats.Qlie
|
||||
return null;
|
||||
entry.UnpackedSize = index.ReadUInt32(); // [+0C]
|
||||
entry.IsPacked = 0 != index.ReadInt32(); // [+10]
|
||||
entry.IsEncrypted = 0 != index.ReadInt32(); // [+14]
|
||||
entry.EncryptionMethod = index.ReadInt32(); // [+14]
|
||||
entry.Hash = index.ReadUInt32(); // [+18]
|
||||
entry.KeyFile = key_file;
|
||||
if (3 == pack_version.Major && use_pack_keyfile && entry.Name.Contains ("pack_keyfile"))
|
||||
@@ -330,6 +332,16 @@ namespace GameRes.Formats.Qlie
|
||||
return File.ReadAllBytes (name);
|
||||
}
|
||||
}
|
||||
var pattern = VFS.CombinePath (dir_name, @"..\*.exe");
|
||||
foreach (var exe_file in VFS.GetFiles (pattern))
|
||||
{
|
||||
using (var exe = new ExeFile.ResourceAccessor (exe_file.Name))
|
||||
{
|
||||
var reskey = exe.GetResource ("RESKEY", "#10");
|
||||
if (reskey != null)
|
||||
return reskey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Qlie
|
||||
{
|
||||
@@ -285,13 +286,28 @@ namespace GameRes.Formats.Qlie
|
||||
|
||||
public override void DecryptEntry (byte[] data, int offset, int length, QlieEntry entry)
|
||||
{
|
||||
if (0 == entry.EncryptionMethod)
|
||||
return;
|
||||
if (offset < 0)
|
||||
throw new ArgumentOutOfRangeException ("offset");
|
||||
if (length > data.Length || offset > data.Length - length)
|
||||
throw new ArgumentOutOfRangeException ("length");
|
||||
if (length < 8)
|
||||
return;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* raw_data = &data[offset])
|
||||
{
|
||||
if (1 == entry.EncryptionMethod)
|
||||
DecryptV1 (raw_data, length, entry);
|
||||
else
|
||||
DecryptV2 (raw_data, length, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe void DecryptV1 (byte* data, int length, QlieEntry entry)
|
||||
{
|
||||
var file_name = entry.Name;
|
||||
uint hash = 0x85F532;
|
||||
uint seed = 0x33F641;
|
||||
@@ -306,34 +322,66 @@ namespace GameRes.Formats.Qlie
|
||||
+ hash + (hash ^ (uint)length ^ 0x8F32DCu));
|
||||
seed = 9 * (seed & 0xFFFFFF);
|
||||
var table = GenerateTable (0x20, seed); // originally 0x2C, 12 dwords not used
|
||||
unsafe
|
||||
ulong* data64 = (ulong*)data;
|
||||
int qword_length = length / 8;
|
||||
uint k = 2 * (table[0xD] & 0xF);
|
||||
ulong hash64 = table[6] | (ulong)table[7] << 32;
|
||||
for (int i = 0; i < qword_length; ++i)
|
||||
{
|
||||
fixed (byte* raw_data = &data[offset])
|
||||
{
|
||||
ulong* data64 = (ulong*)raw_data;
|
||||
int qword_length = length / 8;
|
||||
uint k = 2 * (table[0xD] & 0xF);
|
||||
ulong hash64 = table[6] | (ulong)table[7] << 32;
|
||||
for (int i = 0; i < qword_length; ++i)
|
||||
{
|
||||
ulong t = table[k] | (ulong)table[k+1] << 32;
|
||||
hash64 = MMX.PAddD (hash64 ^ t, t);
|
||||
ulong t = table[k] | (ulong)table[k+1] << 32;
|
||||
hash64 = MMX.PAddD (hash64 ^ t, t);
|
||||
|
||||
ulong d = data64[i] ^ hash64;
|
||||
data64[i] = d;
|
||||
ulong d = data64[i] ^ hash64;
|
||||
data64[i] = d;
|
||||
|
||||
hash64 = MMX.PAddB (hash64, d) ^ d;
|
||||
hash64 = MMX.PAddW (MMX.PSllD (hash64, 1), d);
|
||||
hash64 = MMX.PAddB (hash64, d) ^ d;
|
||||
hash64 = MMX.PAddW (MMX.PSllD (hash64, 1), d);
|
||||
|
||||
k = (k + 2) & 0x1F;
|
||||
}
|
||||
}
|
||||
k = (k + 2) & 0x1F;
|
||||
}
|
||||
}
|
||||
|
||||
static uint[] GenerateTable (int length, uint seed)
|
||||
unsafe void DecryptV2 (byte* data, int length, QlieEntry entry)
|
||||
{
|
||||
var file_name = entry.Name;
|
||||
uint hash = 0x86F7E2;
|
||||
uint seed = 0x4437F1;
|
||||
|
||||
for (int i = 0; i < file_name.Length; i++)
|
||||
{
|
||||
hash += (uint)(file_name[i] << (i & 7));
|
||||
seed ^= hash;
|
||||
}
|
||||
|
||||
seed += ArcKey ^ (13 * ((uint)length & 0xFFFFFF) + (uint)length
|
||||
+ hash + (hash ^ (uint)length ^ 0x56E213u));
|
||||
seed = 13 * (seed & 0xFFFFFF);
|
||||
var table = GenerateTable (0x20, seed, 0x8A77F473u); // originally 0x40
|
||||
var key_data = GenerateKeyData (entry.KeyFile);
|
||||
|
||||
ulong* data64 = (ulong*)data;
|
||||
int qword_length = length / 8;
|
||||
int k = (8 * ((int)table[8] & 0xD)) & 0x7F;
|
||||
ulong hash64 = table[6] | (ulong)table[7] << 32;
|
||||
for (int i = 0; i < qword_length; ++i)
|
||||
{
|
||||
int t_index = 2 * (k & 0xF);
|
||||
ulong t = table[t_index] | (ulong)table[t_index + 1] << 32;
|
||||
t ^= LittleEndian.ToUInt64 (key_data, 8 * k);
|
||||
hash64 = MMX.PAddD (hash64 ^ t, t);
|
||||
|
||||
ulong d = data64[i] ^ hash64;
|
||||
data64[i] = d;
|
||||
|
||||
hash64 = MMX.PAddB (hash64, d) ^ d;
|
||||
hash64 = MMX.PAddW (MMX.PSllD (hash64, 1), d);
|
||||
|
||||
k = (k + 1) & 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
static uint[] GenerateTable (int length, uint seed, uint key = 0x8DF21431u)
|
||||
{
|
||||
const uint key = 0x8DF21431u;
|
||||
var table = new uint[length];
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
@@ -343,5 +391,30 @@ namespace GameRes.Formats.Qlie
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
byte[] GenerateKeyData (byte[] key_file)
|
||||
{
|
||||
var key_data = new byte[0x400];
|
||||
for (int i = 0; i < 0x100; ++i)
|
||||
{
|
||||
int hash;
|
||||
if (0 != (i % 3))
|
||||
hash = (i + 7) * -(i + 3);
|
||||
else
|
||||
hash = (i + 7) * (i + 3);
|
||||
LittleEndian.Pack (hash, key_data, i * 4);
|
||||
}
|
||||
if (key_file != null && key_file.Length >= 128)
|
||||
{
|
||||
int k = key_file[49] % 73 + 128;
|
||||
int l = key_file[79] % 7 + 7;
|
||||
for (int i = 0; i < key_data.Length; ++i)
|
||||
{
|
||||
k = (k + l) % key_file.Length;
|
||||
key_data[i] ^= key_file[k];
|
||||
}
|
||||
}
|
||||
return key_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
80
ArcFormats/Slg/ArcSPD.cs
Normal file
80
ArcFormats/Slg/ArcSPD.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! \file ArcSPD.cs
|
||||
//! \date 2017 Aug 13
|
||||
//! \brief SLG system 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.Slg
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class SpdOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "SPD/SLG"; } }
|
||||
public override string Description { get { return "SLG system audio archive"; } }
|
||||
public override uint Signature { get { return 0x504653; } } // 'SFP'
|
||||
public override bool IsHierarchic { get { return false; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (file.Name.HasExtension (".SPL"))
|
||||
return null;
|
||||
var index_name = Path.ChangeExtension (file.Name, ".SPL");
|
||||
if (!VFS.FileExists (index_name))
|
||||
return null;
|
||||
using (var idx = VFS.OpenView (index_name))
|
||||
{
|
||||
if (!idx.View.AsciiEqual (0, "SFP\0"))
|
||||
return null;
|
||||
uint align = idx.View.ReadUInt32 (0xC);
|
||||
uint index_offset = 0x20;
|
||||
uint names_offset = idx.View.ReadUInt32 (index_offset);
|
||||
if (names_offset > idx.MaxOffset || names_offset <= index_offset)
|
||||
return null;
|
||||
int count = (int)(names_offset - index_offset) / 0x10;
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
uint name_offset = idx.View.ReadUInt32 (index_offset);
|
||||
var name = idx.View.ReadString (name_offset, (uint)(idx.MaxOffset - name_offset));
|
||||
if (string.IsNullOrWhiteSpace (name))
|
||||
return null;
|
||||
var entry = FormatCatalog.Instance.Create<Entry> (name);
|
||||
entry.Size = idx.View.ReadUInt32 (index_offset+4);
|
||||
entry.Offset = (long)idx.View.ReadUInt32 (index_offset+8) * align;
|
||||
if (!entry.CheckPlacement (file.MaxOffset))
|
||||
return null;
|
||||
dir.Add (entry);
|
||||
index_offset += 0x10;
|
||||
}
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,6 +320,8 @@ namespace GameRes.Formats.TopCat
|
||||
name = Path.Combine (dir_name, name);
|
||||
name = Path.ChangeExtension (name, section.Extension);
|
||||
var entry = FormatCatalog.Instance.Create<TcdEntry> (name);
|
||||
if (name.HasExtension (".SPD"))
|
||||
entry.Type = "image";
|
||||
entry.Offset = offsets[index];
|
||||
entry.Size = offsets[index+1] - offsets[index];
|
||||
entry.Index = index;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Windows.Media;
|
||||
@@ -32,8 +33,15 @@ namespace GameRes.Formats.Valkyria
|
||||
{
|
||||
internal class Mg2MetaData : ImageMetaData
|
||||
{
|
||||
public int ImageLength;
|
||||
public int AlphaLength;
|
||||
public int ImageLength;
|
||||
public int AlphaLength;
|
||||
public IMg2Scheme Scheme;
|
||||
}
|
||||
|
||||
internal interface IMg2Scheme
|
||||
{
|
||||
Mg2EncryptedStream CreateStream (Stream main, int offset, int length);
|
||||
ImageData CreateImage (BitmapSource bitmap, ImageMetaData info);
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
@@ -43,44 +51,54 @@ namespace GameRes.Formats.Valkyria
|
||||
public override string Description { get { return "Valkyria image format"; } }
|
||||
public override uint Signature { get { return 0x4F43494D; } } // 'MICO'
|
||||
|
||||
static readonly IMg2Scheme[] KnownSchemes = { new Mg2SchemeV1(), new Mg2SchemeV2() };
|
||||
|
||||
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))
|
||||
foreach (var scheme in KnownSchemes)
|
||||
{
|
||||
var info = Png.ReadMetaData (png);
|
||||
if (null == info)
|
||||
return null;
|
||||
return new Mg2MetaData
|
||||
using (var input = scheme.CreateStream (file.AsStream, 0x10, length))
|
||||
using (var png = new BinaryStream (input, file.Name))
|
||||
{
|
||||
Width = info.Width,
|
||||
Height = info.Height,
|
||||
OffsetX = info.OffsetX,
|
||||
OffsetY = info.OffsetY,
|
||||
BPP = info.BPP,
|
||||
ImageLength = length,
|
||||
AlphaLength = header.ToInt32 (12)
|
||||
};
|
||||
var info = Png.ReadMetaData (png);
|
||||
if (null == info)
|
||||
continue;
|
||||
return new Mg2MetaData
|
||||
{
|
||||
Width = info.Width,
|
||||
Height = info.Height,
|
||||
OffsetX = info.OffsetX,
|
||||
OffsetY = info.OffsetY,
|
||||
BPP = info.BPP,
|
||||
ImageLength = length,
|
||||
AlphaLength = header.ToInt32 (12),
|
||||
Scheme = scheme,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override ImageData Read (IBinaryStream file, ImageMetaData info)
|
||||
{
|
||||
var meta = (Mg2MetaData)info;
|
||||
var frame = ReadBitmapSource (file.AsStream, meta);
|
||||
return meta.Scheme.CreateImage (frame, meta);
|
||||
}
|
||||
|
||||
BitmapSource ReadBitmapSource (Stream file, Mg2MetaData meta)
|
||||
{
|
||||
BitmapSource frame;
|
||||
using (var input = new Mg2EncryptedStream (file.AsStream, 0x10, meta.ImageLength))
|
||||
using (var input = meta.Scheme.CreateStream (file, 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);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
if (frame.Format.BitsPerPixel != 32)
|
||||
frame = new FormatConvertedBitmap (frame, PixelFormats.Bgr32, null, 0);
|
||||
@@ -88,12 +106,14 @@ namespace GameRes.Formats.Valkyria
|
||||
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))
|
||||
using (var input = meta.Scheme.CreateStream (file, 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);
|
||||
return BitmapSource.Create ((int)meta.Width, (int)meta.Height,
|
||||
ImageData.DefaultDpiX, ImageData.DefaultDpiY,
|
||||
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];
|
||||
@@ -104,7 +124,9 @@ namespace GameRes.Formats.Valkyria
|
||||
{
|
||||
pixels[dst] = alpha[src++];
|
||||
}
|
||||
return ImageData.Create (info, PixelFormats.Bgra32, null, pixels, stride);
|
||||
return BitmapSource.Create ((int)meta.Width, (int)meta.Height,
|
||||
ImageData.DefaultDpiX, ImageData.DefaultDpiY,
|
||||
PixelFormats.Bgra32, null, pixels, stride);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,11 +139,23 @@ namespace GameRes.Formats.Valkyria
|
||||
internal class Mg2EncryptedStream : StreamRegion
|
||||
{
|
||||
readonly int m_threshold;
|
||||
readonly byte m_key;
|
||||
|
||||
public Mg2EncryptedStream (Stream main, int offset, int length)
|
||||
protected Mg2EncryptedStream (Stream main, int offset, int length, int threshold, byte key)
|
||||
: base (main, offset, length, true)
|
||||
{
|
||||
m_threshold = length / 5;
|
||||
m_threshold = threshold;
|
||||
m_key = key;
|
||||
}
|
||||
|
||||
public static Mg2EncryptedStream CreateV1 (Stream main, int offset, int length)
|
||||
{
|
||||
return new Mg2EncryptedStream (main, offset, length, length / 5, 0);
|
||||
}
|
||||
|
||||
public static Mg2EncryptedStream CreateV2 (Stream main, int offset, int length)
|
||||
{
|
||||
return new Mg2EncryptedStream (main, offset, length, Math.Min (25, length), (byte)length);
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
@@ -129,7 +163,7 @@ namespace GameRes.Formats.Valkyria
|
||||
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++;
|
||||
buffer[offset+i] ^= (byte)(m_key + pos++);
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -138,8 +172,37 @@ namespace GameRes.Formats.Valkyria
|
||||
long pos = Position;
|
||||
int b = base.ReadByte();
|
||||
if (b != -1 && pos < m_threshold)
|
||||
b ^= (byte)pos;
|
||||
b ^= (byte)(m_key + pos);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Mg2SchemeV1 : IMg2Scheme
|
||||
{
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length)
|
||||
{
|
||||
return Mg2EncryptedStream.CreateV1 (main, offset, length);
|
||||
}
|
||||
|
||||
public ImageData CreateImage (BitmapSource frame, ImageMetaData info)
|
||||
{
|
||||
frame.Freeze();
|
||||
return new ImageData (frame, info);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Mg2SchemeV2 : IMg2Scheme
|
||||
{
|
||||
public Mg2EncryptedStream CreateStream (Stream main, int offset, int length)
|
||||
{
|
||||
return Mg2EncryptedStream.CreateV2 (main, offset, length);
|
||||
}
|
||||
|
||||
public ImageData CreateImage (BitmapSource frame, ImageMetaData info)
|
||||
{
|
||||
frame = new TransformedBitmap (frame, new ScaleTransform { ScaleY = -1 });
|
||||
frame.Freeze();
|
||||
return new ImageData (frame, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace GameRes.Formats.YuRis
|
||||
return null;
|
||||
var parser = new Parser (file, version, count, dir_size);
|
||||
|
||||
var scheme = QueryEncryptionScheme (version);
|
||||
var scheme = QueryEncryptionScheme (file.Name, version);
|
||||
var dir = parser.ScanDir (scheme);
|
||||
if (null == dir || 0 == dir.Count)
|
||||
return null;
|
||||
@@ -175,10 +175,13 @@ namespace GameRes.Formats.YuRis
|
||||
return new GUI.CreateYPFWidget();
|
||||
}
|
||||
|
||||
YpfScheme QueryEncryptionScheme (uint version)
|
||||
YpfScheme QueryEncryptionScheme (string arc_name, uint version)
|
||||
{
|
||||
var options = Query<YpfOptions> (arcStrings.YPFNotice);
|
||||
var title = FormatCatalog.Instance.LookupGame (arc_name);
|
||||
YpfScheme scheme;
|
||||
if (!string.IsNullOrEmpty (title) && KnownSchemes.TryGetValue (title, out scheme))
|
||||
return scheme;
|
||||
var options = Query<YpfOptions> (arcStrings.YPFNotice);
|
||||
if (!KnownSchemes.TryGetValue (options.Scheme, out scheme) || null == scheme)
|
||||
scheme = new YpfScheme {
|
||||
SwapTable = GuessSwapTable (version),
|
||||
@@ -354,7 +357,7 @@ namespace GameRes.Formats.YuRis
|
||||
uint* header = (uint*)raw;
|
||||
uint version = header[1];
|
||||
int first_item, last_item;
|
||||
if (version >= 0x1CE || 0x12C == version)
|
||||
if (version >= 0x1CE || 0x12C == version || 0x19A == version)
|
||||
{
|
||||
first_item = 3;
|
||||
last_item = 7;
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace GARbro.GUI
|
||||
var oem = CultureInfo.CurrentCulture.TextInfo.OEMCodePage;
|
||||
list.Add (Encoding.GetEncoding (oem));
|
||||
list.Add (Encoding.GetEncoding (932));
|
||||
list.Add (Encoding.GetEncoding (936));
|
||||
list.Add (Encoding.UTF8);
|
||||
list.Add (Encoding.Unicode);
|
||||
list.Add (Encoding.BigEndianUnicode);
|
||||
|
||||
@@ -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.29.1874")]
|
||||
[assembly: AssemblyFileVersion ("1.4.29.1874")]
|
||||
[assembly: AssemblyVersion ("1.4.31.1908")]
|
||||
[assembly: AssemblyFileVersion ("1.4.31.1908")]
|
||||
|
||||
@@ -129,6 +129,8 @@ namespace GameRes
|
||||
meta.Width = Binary.BigEndian (file.ReadUInt32());
|
||||
meta.Height = Binary.BigEndian (file.ReadUInt32());
|
||||
int bpp = file.ReadByte();
|
||||
if (bpp != 1 && bpp != 2 && bpp != 4 && bpp != 8 && bpp != 16)
|
||||
return null;
|
||||
int color_type = file.ReadByte();
|
||||
switch (color_type)
|
||||
{
|
||||
@@ -136,7 +138,8 @@ namespace GameRes
|
||||
case 3: meta.BPP = 24; break;
|
||||
case 4: meta.BPP = bpp*2; break;
|
||||
case 6: meta.BPP = bpp*4; break;
|
||||
default: meta.BPP = bpp; break;
|
||||
case 0: meta.BPP = bpp; break;
|
||||
default: return null;
|
||||
}
|
||||
SkipBytes (file, 7);
|
||||
|
||||
|
||||
@@ -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.30.254")]
|
||||
[assembly: AssemblyFileVersion ("1.4.30.254")]
|
||||
[assembly: AssemblyVersion ("1.4.32.257")]
|
||||
[assembly: AssemblyFileVersion ("1.4.32.257")]
|
||||
|
||||
@@ -52,6 +52,7 @@ Remember11<br/>
|
||||
Chou Dengeki Stryker<br/>
|
||||
Chou Jikuu Bakuren Monogatari ~door pi chu~<br/>
|
||||
Eiken Kikaku<br/>
|
||||
Fluorite Memories<br/>
|
||||
H2O -Footprints in the Sand-<br/>
|
||||
Kanojo*Step<br/>
|
||||
Melty Moment<br/>
|
||||
@@ -97,6 +98,7 @@ Grisaia no Kajitsu<br/>
|
||||
Happiness! Re:Lucks<br/>
|
||||
Kamikaze ☆ Explorer!<br/>
|
||||
Koko kara Natsu no Innocence!<br/>
|
||||
Labyrinth of Grisaia<br/>
|
||||
Makai Tenshi Djibril -Episode 4-<br/>
|
||||
Sakigake ⇒ Generation!<br/>
|
||||
Sengoku Tenshi Djibril<br/>
|
||||
@@ -280,6 +282,7 @@ Amairo*Islenauts<br/>
|
||||
Aozora Gakko no Sensei-kun<br/>
|
||||
Aqua<br/>
|
||||
Bishuu ~Chigyaku no Mesu Dorei~<br/>
|
||||
Boku to Koi Suru Ponkotsu Akuma.<br/>
|
||||
Boku to Koi Suru Ponkotsu Akuma. Suggoi Ecchi!<br/>
|
||||
Cafe Sourire<br/>
|
||||
Clover Day's<br/>
|
||||
@@ -294,11 +297,14 @@ Fair Child<br/>
|
||||
Fantasical<br/>
|
||||
Fate/stay night<br/>
|
||||
Fate/hollow ataraxia<br/>
|
||||
Fate/Knight Rhapsody ACT 2<br/>
|
||||
G-senjou no Maou<br/>
|
||||
Gakuen Butou no Folklore<br/>
|
||||
Grisaia: Phantom Trigger Vol.2<br/>
|
||||
Hachukano<br/>
|
||||
Hanafubuki ~Sennen no Koi o Shimashita~<br/>
|
||||
Haruiro ☆ Communication ♪<br/>
|
||||
Haruoto Alice*Gram<br/>
|
||||
Heliotrope -Sore wa Shi ni Itaru Kami no Ai-<br/>
|
||||
Hime to Majin to Koi Suru Tamashii<br/>
|
||||
Imouto Style<br/>
|
||||
@@ -330,6 +336,8 @@ Ore no Saimin Fantasia<br/>
|
||||
Otome*Domain<br/>
|
||||
Ouka Ryouran<br/>
|
||||
Oyako Ninjutsu Kunoichi PonPon!!<br/>
|
||||
PRETTY x CATION<br/>
|
||||
PURELY x CATION<br/>
|
||||
Rakugaki Overheart<br/>
|
||||
RGH ~Koi to Hero to Gakuen to~<br/>
|
||||
Riajuu Saimin<br/>
|
||||
@@ -356,6 +364,7 @@ Ushinawareta Mirai o Motomete<br/>
|
||||
With Ribbon<br/>
|
||||
Yome Sagashi ga Hakadori Sugite Yabai.<br/>
|
||||
Yomibito Shirazu ~Amai Meshibe no Seichoushi~<br/>
|
||||
Yotsunoha<br/>
|
||||
Yuugu ~Nyomitsu Gangu Adesugata~<br/>
|
||||
Yuugu 2 ~Dai Ni Kinsho no Nozomu Mono~<br/>
|
||||
Zecchou Spiral!!<br/>
|
||||
@@ -364,7 +373,9 @@ Zettai Karen! Ojou-sama<br/>
|
||||
<tr class="last"><td>*.tlg</td><td><tt>TLG0.0</tt><br/><tt>TLG5.0</tt><br/><tt>TLG6.0</tt></td><td></td></tr>
|
||||
<tr class="odd"><td>*.ypf</td><td><tt>YPF</tt></td><td><span class="supported"></span></td><td rowspan="2">YU-RIS</td><td rowspan="2">
|
||||
77 (Sevens) ~And, Two Stars Meet Again~<br/>
|
||||
Aikagi<br/>
|
||||
Eroge! ~H mo Game mo Kaihatsu Zanmai~<br/>
|
||||
Haramase Taiken Gakushuu ~Shizen no Naka de Seikyouiku!?~<br/>
|
||||
Koi Mekuri Clover<br/>
|
||||
Mamono Musume-tachi to no Rakuen ~Slime & Scylla~<br/>
|
||||
Mashou no Nie 3 ~Hakudaku no Umi ni Shizumu Injoku no Reiki~<br/>
|
||||
@@ -492,6 +503,7 @@ Bitch Nee-chan ga Seijun na Hazu ga Nai! <span class="footnote">ShiinaRio v2.50<
|
||||
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/>
|
||||
Chiccha na Hanayome ~Mada Mada Tsubomi da mon~ <span class="footnote">ShiinaRio v2.50</span><br/>
|
||||
Chikan Circle <span class="footnote">ShiinaRio v2.46</span><br/>
|
||||
Chikan Circle 2 <span class="footnote">ShiinaRio v2.47</span><br/>
|
||||
Chuuchuu Nurse <span class="footnote">ShiinaRio v2.45</span><br/>
|
||||
@@ -523,6 +535,7 @@ Jukubo Gui ~Moto Guradoru no Okaa-san~ <span class="footnote">ShiinaRio v2.37</s
|
||||
Kateinai Choukyou <span class="footnote">ShiinaRio v2.31</span><br/>
|
||||
Kichiku Nakadashi Suieibu<span class="footnote">ShiinaRio v2.41</span><br/>
|
||||
Last Waltz ~Hakudaku Mamire no Natsu Gasshuku~ <span class="footnote">ShiinaRio v2.47</span><br/>
|
||||
Libra of the Vampire Princess <span class="footnote">ShiinaRio v2.50</span><br/>
|
||||
Mahou Shoujo no Taisetsu na Koto <span class="footnote">ShiinaRio v2.47</span><br/>
|
||||
Maki Fes! <span class="footnote">ShiinaRio v2.50</span><br/>
|
||||
Mikoko <span class="footnote">ShiinaRio v2.46</span><br/>
|
||||
@@ -541,6 +554,7 @@ Ren'ai Saimin ~Tsun na Kanojo ga dereru Saimin~ <span class="footnote">ShiinaRio
|
||||
Rin×Sen <span class="footnote">ShiinaRio v2.47</span><br/>
|
||||
Ryoumaden ~Houkago no Rakuen~ <span class="footnote">ShiinaRio v2.47</span><br/>
|
||||
Sabae no Ou <span class="footnote">ShiinaRio v2.36</span><br/>
|
||||
Saimin Seikatsu ~Kousoku Dakara Shikatanai!?~ <span class="footnote">ShiinaRio v2.47</span><br/>
|
||||
Shinigami no Testament <span class="footnote">ShiinaRio v2.49</span><br/>
|
||||
Shinseki no Oba-san ~Hanare no Netsujo, Honke no Gosai~ <span class="footnote">ShiinaRio v2.36</span><br/>
|
||||
Shojo Mama<span class="footnote">ShiinaRio v2.49</span><br/>
|
||||
@@ -622,6 +636,7 @@ WW&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></td><td rowspan="2"> bootUP!<br/>Pajamas Soft<br/>Aries</td><td rowspan="2">
|
||||
Aneimo 2 ~Second Stage~<br/>
|
||||
Lilycle Rainbow Stage!!!<br/>
|
||||
Momichupa Teacher!<br/>
|
||||
Natsu no Owari no Nirvana<br/>
|
||||
Patissier na Nyanko<br/>
|
||||
@@ -733,6 +748,7 @@ Zetsuboushi<br/>
|
||||
<tr><td>*.pack</td><td><tt>FilePackVer1.0</tt><br/><tt>FilePackVer2.0</tt><br/><tt>FilePackVer3.0</tt><br/><tt>FilePackVer3.1</tt></td><td></td><td rowspan="3">QLIE</td><td rowspan="3">
|
||||
Amanatsu Adolesence Trial 2<br/>
|
||||
Bishoujo Mangekyou -Kami ga Tsukuritamouta Shoujo-tachi-<br/>
|
||||
Bishoujo Mangekyou -Tsumi to Batsu no Shoujo-<br/>
|
||||
Harem Hospital<br/>
|
||||
Hidamari Basket<br/>
|
||||
Kikan Bakumatsu Ibun Last Cavalier<br/>
|
||||
@@ -768,11 +784,13 @@ Kimon Youitan<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">
|
||||
Aki Uso -The only neat thing to do-<br/>
|
||||
Angel Crown<br/>
|
||||
Deep Love Diary<br/>
|
||||
Dies irae<br/>
|
||||
Dies irae ~Amantes amentes~<br/>
|
||||
Dokidoki Sister Paradise 2<br/>
|
||||
Fuyu Uso -Snow World End-<br/>
|
||||
Kajiri Kamui Kagura<br/>
|
||||
Paradise Lost<br/>
|
||||
Sacrifice ~Seifuku Gari~<br/>
|
||||
@@ -929,6 +947,7 @@ Soukai no Valkyria <br/>
|
||||
<tr><td>*.dxa<br/>*.usi<br/>*.hud<br/>*.dat</td><td><tt>DX</tt><span class="footnote">encrypted</span></td><td></td><td rowspan="2">DxLib</td><td rowspan="2">
|
||||
Ashita wa Kitto, Haremasu you ni<br/>
|
||||
Cross Quartz<br/>
|
||||
Detective Seven<br/>
|
||||
Hyakki Yakou<br/>
|
||||
Mahou Senshi Extra Stage 2 ~Gakuen Kangoku~<br/>
|
||||
Saikyou Goshujin-sama! -Mighty My Master-<br/>
|
||||
@@ -1077,6 +1096,7 @@ AstralAir no Shiroki Towa<br/>
|
||||
</td></tr>
|
||||
<tr class="last"><td>*.hzc</td><td><tt>hzc1</tt></td><td></td></tr>
|
||||
<tr class="odd"><td>*.bin</td><td><tt>ESC-ARC1</tt><br/><tt>ESC-ARC2</tt></td><td></td><td>Escu:de</td><td>
|
||||
Hyakki Ryouran no Yakata<br/>
|
||||
Otome Renshin Prister<br/>
|
||||
Suisei Tenshi Primaveil Zwei<br/>
|
||||
Verdia Gensoukyoku<br/>
|
||||
@@ -1164,10 +1184,12 @@ Tokyo Necro<br/>
|
||||
Thanatos no Koi ~In Ane Otouto Soukan~<br/>
|
||||
</td></tr>
|
||||
<tr class="odd last"><td>*.gps</td><td><tt>GPS</tt></td><td></td></tr>
|
||||
<tr><td>*.szs</td><td><tt>SZS100__</tt></td><td></td><td rowspan="4">SLG system</td><td rowspan="4">
|
||||
<tr><td>*.szs</td><td><tt>SZS100__</tt></td><td></td><td rowspan="5">SLG system</td><td rowspan="5">
|
||||
Sangoku Hime 2<br/>
|
||||
Sengoku Hime<br/>
|
||||
Shihen 69 ~Shinen no Messiah~<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.spd+*.spl</td><td><tt>SFP</tt></td><td></td></tr>
|
||||
<tr><td>*.tig</td><td><tt>\x7C\xF3\xC2\x8B</tt></td><td></td></tr>
|
||||
<tr><td>*.alb</td><td><tt>ALB1.21</tt></td><td></td></tr>
|
||||
<tr class="last"><td>*.voi</td><td>-</td><td></td></tr>
|
||||
@@ -1179,6 +1201,7 @@ Angenehm Platz -Kleiner Garten Sie Erstellen-<br/>
|
||||
<tr><td>*.eme</td><td><tt>RREDATA</tt></td><td></td><td>Emon Engine</td><td>
|
||||
Ase Nure Shoujo Misaki "Anata no Nioi de Icchau!"<br/>
|
||||
D-spray Biyaku de Motemote Kachou Dairi Hosa<br/>
|
||||
D-spray 2 Biyaku de Motemote Kachou Dairi Yukemuri Ryojou Hen<br/>
|
||||
Hitomi no Rakuin ~Inbaku no Mesu Dorei~<br/>
|
||||
Jokujima<br/>
|
||||
Saiin Haramase Keyword<br/>
|
||||
@@ -1327,6 +1350,7 @@ Roshutsu Hentai Yuugi<br/>
|
||||
Ryou Seibai! ~Gakuen Bishoujo Seisai Hiroku~<br/>
|
||||
Samayoi Inmu Kousha ~Konna H na Jugyou, Arienai!~<br/>
|
||||
Sarai no Me<br/>
|
||||
Shinyaku In'youchuu<br/>
|
||||
Shukubo no Uzuki ~Hitozuma Miboujin no Nareta Karada to Amai Toiki~<br/>
|
||||
Shukubo no Uzuki 2 ~Nareta Hitozuma kara Tadayou "Onna" no Iroka~<br/>
|
||||
Shukujo no Tsuyagoto<br/>
|
||||
@@ -1364,6 +1388,7 @@ Shokusou Tenshi Serika 2<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.dat</td><td>-</td><td></td><td>NekoSDK</td><td>
|
||||
Elevator Panic ~Misshitsu no Inkou~<br/>
|
||||
Yuuguu Settai #<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.dat</td><td><tt>MK2.0</tt></td><td></td><td>MAIKA</td><td>
|
||||
Inka Gakuen Taisen<br/>
|
||||
@@ -1430,8 +1455,10 @@ Eden's Ritter - Inetsu no Seima Kishi Lucifer Hen<br/>
|
||||
In Vitro Shoujo -Aragaishi Junshin Naru Shoujo-<br/>
|
||||
Kyonyuu Fantasy - Digital Novel<br/>
|
||||
Shirudaku Settai Okawari Ippaime<br/>
|
||||
Zen'aku<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.pcf</td><td><tt>PackCode</tt></td><td></td><td rowspan="2">Symphony</td><td rowspan="2">
|
||||
Fortuna Rhapsody<br/>
|
||||
Tiara<br/>
|
||||
</td></tr>
|
||||
<tr class="odd last"><td>*.gbc</td><td><tt>GBCF</tt></td><td></td></tr>
|
||||
@@ -1496,8 +1523,9 @@ Doreijou<br/>
|
||||
<tr class="odd"><td>*.kgf</td><td><tt>KGF</tt></td><td></td></tr>
|
||||
<tr class="odd"><td>*.arc</td><td><tt>DAF\x1A</tt></td><td></td></tr>
|
||||
<tr class="odd last"><td>*.cgf</td><td><tt>CGF\x1A</tt></td><td></td></tr>
|
||||
<tr><td>*.pfs</td><td><tt>pf2</tt><br/><tt>pf8</tt></td><td></td><td rowspan="2">Artemis Engine</td><td rowspan="2">
|
||||
<tr><td>*.pfs</td><td><tt>pf2</tt><br/><tt>pf6</tt><br/><tt>pf8</tt></td><td></td><td rowspan="2">Artemis Engine</td><td rowspan="2">
|
||||
Boku no Elf Onee-san<br/>
|
||||
Memory's Dogma CODE:01<br/>
|
||||
Tsugou no Ii Kanojo<br/>
|
||||
Tsugou no Ii Kazoku<br/>
|
||||
</td></tr>
|
||||
@@ -1539,6 +1567,7 @@ Sutadoru!<br/>
|
||||
</td></tr>
|
||||
<tr class="last"><td>*.pcd</td><td>-</td><td></td></tr>
|
||||
<tr class="odd"><td>*.tac<br/>*.stx</td><td><tt>TArc1.10</tt><br/><tt>TArc1.00</tt></td><td></td><td>TanukiSoft</td><td>
|
||||
Onii-chan Daisuki!<br/>
|
||||
Shoujo Kyouiku<br/>
|
||||
Shoujo Ramune<br/>
|
||||
Shoukoujo<br/>
|
||||
@@ -1576,6 +1605,7 @@ Kannagi<br/>
|
||||
</td></tr>
|
||||
<tr class="odd last"><td>*.pak</td><td><tt>CCf"</tt></td><td></td></tr>
|
||||
<tr><td>*.dat</td><td>-</td><td></td><td rowspan="3">Valkyria</td><td rowspan="3">
|
||||
Gedou Mahou Shoujo Rinne ~Akuin Akka~<br/>
|
||||
Kangokujou ~Ingyaku no Shihai~<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.mg2</td><td><tt>MICOCG01</tt></td><td></td></tr>
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<GARbro>
|
||||
<Release>
|
||||
<Version>1.4.29</Version>
|
||||
<Version>1.4.31</Version>
|
||||
<Url>https://github.com/morkt/GARbro/releases/latest</Url>
|
||||
<Notes>New formats:
|
||||
- LINK6 archives variation, AN21 animation
|
||||
- DAT archives used in Niizuma Lovely x Cation
|
||||
- 'UnityFS' archives and FSB5 audio
|
||||
- GRP audio archives
|
||||
- more KiriKiri and ShiinaRio encryption schemes
|
||||
<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>
|
||||
</Release>
|
||||
<FormatsData>
|
||||
<FileVersion>66</FileVersion>
|
||||
<FileVersion>78</FileVersion>
|
||||
<Url>https://github.com/morkt/GARbro/raw/master/ArcFormats/Resources/Formats.dat</Url>
|
||||
<Requires>
|
||||
<Assembly Name="ArcFormats" Version="1.2.31.1367"/>
|
||||
<Assembly Name="ArcFormats" Version="1.2.35.1414"/>
|
||||
<Assembly Name="GameRes" Version="1.4.26.238"/>
|
||||
</Requires>
|
||||
</FormatsData>
|
||||
|
||||
Reference in New Issue
Block a user