Compare commits

..

37 Commits

Author SHA1 Message Date
morkt
df66b70a38 released v1.1.3 2015-06-15 09:04:51 +04:00
morkt
bf2acd076e implemented QLIE engine archives version 2. 2015-06-15 08:52:58 +04:00
morkt
c43368150e (MpxOpener): assign "script" type to *.isf files. 2015-06-15 08:23:56 +04:00
morkt
0bc57de734 (DrgIndexedFormat): changed Tag property. 2015-06-14 14:30:01 +04:00
morkt
30e7a470d3 added "ignore errors" option to media conversion. 2015-06-14 13:54:03 +04:00
morkt
9d88975f13 (Wa1Reader): adjust RIFF block length. 2015-06-14 13:53:04 +04:00
morkt
b334222997 implemented GGP images. 2015-06-14 10:21:19 +04:00
morkt
0955c15181 (StreamRegion): added leave_open argument to constructor. 2015-06-14 10:19:16 +04:00
morkt
ab70e8b9f8 (PngFormat.ReadMetaData): don't check stream against Signature. 2015-06-14 10:17:55 +04:00
morkt
4c089120d1 image conversion generalized into media conversion. 2015-06-14 09:05:09 +04:00
morkt
227eae0dbe convert OGV format into Ogg propery. 2015-06-14 04:10:00 +04:00
morkt
d7030ecbdc implemented Crowd CWL images. 2015-06-13 12:54:23 +04:00
morkt
230f8b8e9f updated supported formats. 2015-06-12 22:53:55 +04:00
morkt
104e32a150 assembly version update. 2015-06-12 22:53:11 +04:00
morkt
df9193bf11 removed unused namespaces. 2015-06-12 22:52:37 +04:00
morkt
cf721eab0d implemented CwpFormat.Write method. 2015-06-12 22:52:22 +04:00
morkt
10a52b7f5a (ReadIndex): prioritize PRS images detection. 2015-06-12 22:51:43 +04:00
morkt
d7899fa852 assembly version update. 2015-06-12 22:50:22 +04:00
morkt
490bf8eaff (PngFormat.FindChunk): method made static and public. 2015-06-12 22:49:21 +04:00
morkt
1200a610f4 added message. 2015-06-12 22:46:11 +04:00
morkt
b945a20642 added offset adjustment functionality (disabled for now). 2015-06-12 22:45:50 +04:00
morkt
dfcf169f61 implemented Crowd engine ('Main Program Hoep') formats. 2015-06-12 01:48:28 +04:00
morkt
29bd97fca9 (StreamRange): new wrapper around IO.Stream.
(LzssReader): allow compressed format customization.
2015-06-12 01:47:00 +04:00
morkt
fc27ece34d (S25Format.Read): use ImageData.Create shortcut. 2015-06-11 12:39:26 +04:00
morkt
1f9e4d7b33 ShiinaRio S25 image sets. 2015-06-11 12:38:00 +04:00
morkt
e7672ceb2c added predefined encryption schemes. 2015-06-11 12:32:52 +04:00
morkt
e20853967f (ArcFile.TryOpen): abort on OperationCanceledException. 2015-06-11 12:31:24 +04:00
morkt
6195ab25e3 display audio duration. 2015-06-09 02:38:08 +04:00
morkt
6e9f5f7378 (SubDirEntry): class is defined as part of GameRes filesystem layer now. 2015-06-09 02:36:35 +04:00
morkt
460f6b4447 dispose audio input when playback is stopped. 2015-06-08 20:07:09 +04:00
morkt
50eb805cd2 added FileSystem infrastructure. 2015-06-08 19:58:51 +04:00
morkt
7f8b091d05 (ArcView): added constructor from IO.Stream. 2015-06-08 19:21:31 +04:00
morkt
34a2391811 some additions.
- added "Konneko" passphrase to known encryption schemes.
- added attached context to ErisaDecodeContext class.
2015-06-08 19:19:47 +04:00
morkt
eb5392a16b (MblOpener): manually assign "image" type to *.prs entries. 2015-06-04 02:06:34 +04:00
morkt
2da5ed1961 implemented Marble scripts decryption. 2015-06-04 01:13:25 +04:00
morkt
8849508e41 (OpenPreviewStream): use Archive.OpenSeekableEntry method. 2015-06-04 01:10:38 +04:00
morkt
41c76ae50e removed unused namespace declaration. 2015-06-04 01:10:02 +04:00
52 changed files with 2165 additions and 239 deletions

View File

@@ -85,6 +85,9 @@
<setting name="appExtractAudio" serializeAs="String">
<value>True</value>
</setting>
<setting name="appIgnoreConversionErrors" serializeAs="String">
<value>False</value>
</setting>
</GARbro.GUI.Properties.Settings>
</userSettings>
</configuration>

View File

@@ -187,26 +187,137 @@ namespace GameRes.Formats
}
}
/// <summary>
/// Represents a region within existing stream.
/// Underlying stream should allow seeking (CanSeek == true).
/// </summary>
public class StreamRegion : Stream
{
private Stream m_stream;
private long m_begin;
private long m_end;
private bool m_should_dispose;
public StreamRegion (Stream main, long offset, long length, bool leave_open = false)
{
m_stream = main;
m_begin = offset;
m_end = Math.Min (offset + length, m_stream.Length);
m_stream.Position = m_begin;
m_should_dispose = !leave_open;
}
public StreamRegion (Stream main, long offset) : this (main, offset, main.Length-offset)
{
}
public override bool CanRead { get { return m_stream.CanRead; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { return m_end - m_begin; } }
public override long Position
{
get { return m_stream.Position - m_begin; }
set { m_stream.Position = Math.Max (m_begin + value, m_begin); }
}
public override void Flush()
{
m_stream.Flush();
}
public override long Seek (long offset, SeekOrigin origin)
{
if (SeekOrigin.Begin == origin)
offset += m_begin;
else if (SeekOrigin.Current == origin)
offset += m_stream.Position;
else
offset += m_end;
offset = Math.Max (offset, m_begin);
m_stream.Position = offset;
return offset - m_begin;
}
public override int Read (byte[] buffer, int offset, int count)
{
int read = 0;
long available = m_end - m_stream.Position;
if (available > 0)
{
read = m_stream.Read (buffer, offset, (int)Math.Min (count, available));
}
return read;
}
public override int ReadByte ()
{
if (m_stream.Position < m_end)
return m_stream.ReadByte();
else
return -1;
}
public override void SetLength (long length)
{
throw new NotSupportedException ("StreamRegion.SetLength method is not supported");
}
public override void Write (byte[] buffer, int offset, int count)
{
throw new NotSupportedException ("StreamRegion.Write method is not supported");
}
public override void WriteByte (byte value)
{
throw new NotSupportedException ("StreamRegion.WriteByte method is not supported");
}
bool m_disposed = false;
protected override void Dispose (bool disposing)
{
if (!m_disposed)
{
if (m_should_dispose)
m_stream.Dispose();
m_disposed = true;
base.Dispose (disposing);
}
}
}
public class LzssReader : IDisposable
{
BinaryReader m_input;
byte[] m_output;
int m_size;
public byte[] Data { get { return m_output; } }
public BinaryReader Input { get { return m_input; } }
public byte[] Data { get { return m_output; } }
public int FrameSize { get; set; }
public byte FrameFill { get; set; }
public int FrameInitPos { get; set; }
public LzssReader (Stream input, int input_length, int output_length)
{
m_input = new BinaryReader (input, Encoding.ASCII, true);
m_output = new byte[output_length];
m_size = input_length;
FrameSize = 0x1000;
FrameFill = 0;
FrameInitPos = 0xfee;
}
public void Unpack ()
{
int dst = 0;
var frame = new byte[0x1000];
int frame_pos = 0xfee;
var frame = new byte[FrameSize];
if (FrameFill != 0)
for (int i = 0; i < frame.Length; ++i)
frame[i] = FrameFill;
int frame_pos = FrameInitPos;
int frame_mask = FrameSize-1;
int remaining = (int)m_size;
while (remaining > 0)
{
@@ -221,7 +332,7 @@ namespace GameRes.Formats
byte b = m_input.ReadByte();
--remaining;
frame[frame_pos++] = b;
frame_pos &= 0xfff;
frame_pos &= frame_mask;
m_output[dst++] = b;
}
else
@@ -237,9 +348,9 @@ namespace GameRes.Formats
if (dst >= m_output.Length)
break;
byte v = frame[offset++];
offset &= 0xfff;
offset &= frame_mask;
frame[frame_pos++] = v;
frame_pos &= 0xfff;
frame_pos &= frame_mask;
m_output[dst++] = v;
}
}

View File

@@ -128,7 +128,11 @@ namespace GameRes.Formats.DRS
if (0 == name_length)
return null;
string name = encoding.GetString (name_raw, 0, name_length).ToLowerInvariant();
var entry = FormatCatalog.Instance.CreateEntry (name);
Entry entry;
if (name.EndsWith (".isf", System.StringComparison.InvariantCultureIgnoreCase))
entry = new Entry { Name = name, Type = "script" };
else
entry = FormatCatalog.Instance.CreateEntry (name);
entry.Offset = file.View.ReadUInt32 (dir_offset+12);
entry.Size = file.View.ReadUInt32 (dir_offset+16);
if (!entry.CheckPlacement (file.MaxOffset))

View File

@@ -108,8 +108,11 @@
<Compile Include="ArcNPA.cs" />
<Compile Include="ArcNSA.cs" />
<Compile Include="ArcPAC.cs" />
<Compile Include="ArcPCK.cs" />
<Compile Include="ArcPD.cs" />
<Compile Include="ArcQLIE.cs" />
<Compile Include="ArcRPA.cs" />
<Compile Include="ArcS25.cs" />
<Compile Include="ArcSAF.cs" />
<Compile Include="ArcSPack.cs" />
<Compile Include="ArcSteinsGate.cs" />
@@ -118,6 +121,7 @@
<Compile Include="ArcXFL.cs" />
<Compile Include="ArcXP3.cs" />
<Compile Include="ArcYPF.cs" />
<Compile Include="AudioEOG.cs" />
<Compile Include="AudioMIO.cs" />
<Compile Include="AudioMP3.cs" />
<Compile Include="AudioOGV.cs" />
@@ -163,6 +167,8 @@
<Compile Include="ImageBMD.cs" />
<Compile Include="ImageBMZ.cs" />
<Compile Include="ImageCPB.cs" />
<Compile Include="ImageCWL.cs" />
<Compile Include="ImageCWP.cs" />
<Compile Include="ImageDGC.cs" />
<Compile Include="ImageDRG.cs" />
<Compile Include="ImageEDT.cs" />
@@ -170,6 +176,7 @@
<Compile Include="ImageEPA.cs" />
<Compile Include="ImageERI.cs" />
<Compile Include="ImageGCP.cs" />
<Compile Include="ImageGGP.cs" />
<Compile Include="ImageGR.cs" />
<Compile Include="ImageHG3.cs" />
<Compile Include="ImageIAF.cs" />
@@ -188,6 +195,7 @@
<Compile Include="ImageTLG.cs" />
<Compile Include="ImageWCG.cs" />
<Compile Include="ImageWIP.cs" />
<Compile Include="ImageZBM.cs" />
<Compile Include="KiriKiriCx.cs" />
<Compile Include="KogadoCocotte.cs" />
<Compile Include="AudioOGG.cs" />
@@ -216,6 +224,9 @@
<SubType>Code</SubType>
<DependentUpon>WidgetLPK.xaml</DependentUpon>
</Compile>
<Compile Include="WidgetMBL.xaml.cs">
<DependentUpon>WidgetMBL.xaml</DependentUpon>
</Compile>
<Compile Include="WidgetNOA.xaml.cs">
<DependentUpon>WidgetNOA.xaml</DependentUpon>
</Compile>
@@ -320,6 +331,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="WidgetMBL.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WidgetNOA.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@@ -28,10 +28,28 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
using GameRes.Utility;
namespace GameRes.Formats.Marble
{
public class MblOptions : ResourceOptions
{
public string PassPhrase;
}
public class MblArchive : ArcFile
{
public readonly byte[] Key;
public MblArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, string password)
: base (arc, impl, dir)
{
Key = Encodings.cp932.GetBytes (password);
}
}
[Export(typeof(ArchiveFormat))]
public class MblOpener : ArchiveFormat
{
@@ -64,6 +82,7 @@ namespace GameRes.Formats.Marble
return null;
try
{
bool contains_scripts = false;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
@@ -79,13 +98,25 @@ namespace GameRes.Formats.Marble
name = name.ToLowerInvariant();
index_offset += (uint)filename_len;
uint offset = file.View.ReadUInt32 (index_offset);
var entry = new AutoEntry (name, () => {
uint signature = file.View.ReadUInt32 (offset);
var res = FormatCatalog.Instance.LookupSignature (signature);
if (!res.Any() && 0x4259 == (0xffff & signature))
res = FormatCatalog.Instance.ImageFormats.Where (x => x.Tag == "PRS");
return res.FirstOrDefault();
});
Entry entry;
if (name.EndsWith (".s"))
{
entry = new Entry { Name = name, Type = "script" };
contains_scripts = true;
}
else if (name.EndsWith (".prs"))
{
entry = new Entry { Name = name, Type = "image" };
}
else
{
entry = new AutoEntry (name, () => {
uint signature = file.View.ReadUInt32 (offset);
if (0x4259 == (0xffff & signature))
return FormatCatalog.Instance.ImageFormats.FirstOrDefault (x => x.Tag == "PRS");
return FormatCatalog.Instance.LookupSignature (signature).FirstOrDefault();
});
}
entry.Offset = offset;
entry.Size = file.View.ReadUInt32 (index_offset+4);
if (offset < index_size || !entry.CheckPlacement (file.MaxOffset))
@@ -95,6 +126,12 @@ namespace GameRes.Formats.Marble
}
if (0 == dir.Count)
return null;
if (contains_scripts)
{
var options = Query<MblOptions> (arcStrings.MBLNotice);
if (options.PassPhrase.Length > 0)
return new MblArchive (file, this, dir, options.PassPhrase);
}
return new ArcFile (file, this, dir);
}
catch
@@ -102,5 +139,44 @@ namespace GameRes.Formats.Marble
return null;
}
}
public static Dictionary<string, string> KnownKeys = new Dictionary<string, string> {
{ arcStrings.ArcDefault, "" },
{ "Chikatetsu Fuusa Jiken", "naze" }
};
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (entry.Type != "script" || !entry.Name.EndsWith (".s"))
return arc.File.CreateStream (entry.Offset, entry.Size);
var marc = arc as MblArchive;
var data = new byte[entry.Size];
arc.File.View.Read (entry.Offset, data, 0, entry.Size);
if (null == marc || null == marc.Key)
{
for (int i = 0; i < data.Length; ++i)
{
data[i] = (byte)-data[i];
}
}
else
{
for (int i = 0; i < data.Length; ++i)
{
data[i] ^= marc.Key[i % marc.Key.Length];
}
}
return new MemoryStream (data);
}
public override ResourceOptions GetDefaultOptions ()
{
return new MblOptions { PassPhrase = Settings.Default.MBLPassPhrase };
}
public override object GetAccessWidget ()
{
return new GUI.WidgetMBL();
}
}
}

View File

@@ -88,6 +88,8 @@ namespace GameRes.Formats.Entis
{ "Innyuu Famiresu", new Dictionary<string, string> {
{ "d01.dat", "vdiu$43AfUCfh9aksf" },
{ "d03.dat", "gaivnwq7365e021gf" } } },
{ "Konneko", new Dictionary<string, string> {
{ "script.noa", "convini_cat" } } },
};
public override ArcFile TryOpen (ArcView file)
@@ -98,7 +100,7 @@ namespace GameRes.Formats.Entis
if (0x02000400 != id)
return null;
var reader = new IndexReader (file);
if (!reader.ParseDirEntry (0x40, ""))
if (!reader.ParseDirEntry (0x40, "") || 0 == reader.Dir.Count)
return null;
if (!reader.HasEncrypted)
return new ArcFile (file, this, reader.Dir);
@@ -243,7 +245,9 @@ namespace GameRes.Formats.Entis
entry.Offset = base_offset + m_file.View.ReadInt64 (dir_offset);
if (!entry.CheckPlacement (m_file.MaxOffset))
return false;
{
entry.Size = (uint)(m_file.MaxOffset - entry.Offset);
}
dir_offset += 0x10;
uint extra_length = m_file.View.ReadUInt32 (dir_offset);
@@ -295,7 +299,7 @@ namespace GameRes.Formats.Entis
protected int m_ptrNextBuf;
protected Stream m_pFile;
protected bool m_nFlagEOF;
protected ERISADecodeContext m_pContext;
public ERISADecodeContext (uint nBufferingSize)
{
@@ -304,23 +308,30 @@ namespace GameRes.Formats.Entis
m_nBufCount = 0;
m_ptrBuffer = new byte[nBufferingSize];
m_pFile = null;
m_nFlagEOF = false;
m_pContext = null;
}
public bool EofFlag { get { return m_nFlagEOF; } } // GetEOFFlag
public void AttachInputFile (Stream file)
{
m_pFile = file;
m_pContext = null;
}
public void AttachInputContext (ERISADecodeContext context)
{
m_pFile = null;
m_pContext = context;
}
public uint ReadNextData (byte[] ptrBuffer, uint nBytes)
{
if (m_pFile != null)
{
uint read = (uint)m_pFile.Read (ptrBuffer, 0, (int)nBytes);
m_nFlagEOF = read != nBytes;
return read;
return (uint)m_pFile.Read (ptrBuffer, 0, (int)nBytes);
}
else if (m_pContext != null)
{
return m_pContext.DecodeBytes (ptrBuffer, nBytes);
}
else
{

87
ArcFormats/ArcPCK.cs Normal file
View File

@@ -0,0 +1,87 @@
//! \file ArcPCK.cs
//! \date Thu Jun 11 12:58:00 2015
//! \brief Crowd engine resource archive.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.Crowd
{
[Export(typeof(ArchiveFormat))]
public class PckOpener : ArchiveFormat
{
public override string Tag { get { return "PCK"; } }
public override string Description { get { return "Crowd engine resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (0);
if (count <= 0 || count > 0xfffff)
return null;
long index_offset = 4;
uint index_size = (uint)(0xc * count);
if (index_size > file.View.Reserve (index_offset, index_size))
return null;
var dir = new List<Entry>();
for (int i = 0; i < count; ++i)
{
var entry = new Entry {
Offset = file.View.ReadUInt32 (index_offset+4),
Size = file.View.ReadUInt32 (index_offset+8)
};
if (entry.Offset < index_size || !entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
index_offset += 12;
}
byte[] name_buf = new byte[260];
foreach (var entry in dir)
{
uint max_len = Math.Min (260u, file.View.Reserve (index_offset, 260));
uint n;
for (n = 0; n < max_len; ++n)
{
byte b = file.View.ReadByte (index_offset+n);
if (0 == b)
break;
name_buf[n] = b;
}
if (0 == n || max_len == n)
return null;
entry.Name = Encodings.cp932.GetString (name_buf, 0, (int)n);
entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name);
index_offset += n+1;
}
return new ArcFile (file, this, dir);
}
}
}

251
ArcFormats/ArcQLIE.cs Normal file
View File

@@ -0,0 +1,251 @@
//! \file ArcQLIE.cs
//! \date Mon Jun 15 04:03:18 2015
//! \brief QLIE engine archives implementation.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System;
using GameRes.Utility;
namespace GameRes.Formats.Qlie
{
internal class QlieEntry : PackedEntry
{
public bool IsEncrypted;
public uint Hash;
public uint Key;
}
[Export(typeof(ArchiveFormat))]
public class PackOpener : ArchiveFormat
{
public override string Tag { get { return "QLIE/PACK"; } }
public override string Description { get { return "QLIE engine resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return true; } }
public override bool CanCreate { get { return false; } }
public PackOpener ()
{
Extensions = new string [] { "pack" };
}
public override ArcFile TryOpen (ArcView file)
{
if (file.MaxOffset <= 0x1c)
return null;
long index_offset = file.MaxOffset - 0x1c;
if (!file.View.AsciiEqual (index_offset, "FilePackVer")
|| !file.View.AsciiEqual (index_offset+0xC, ".0"))
return null;
int pack_version = file.View.ReadByte (index_offset+0xB) - '0';
if (pack_version != 2)
throw new NotSupportedException ("Not supported QLIE archive version");
int count = file.View.ReadInt32 (index_offset+0x10);
if (count < 0 || count > 0xfffff)
return null;
index_offset = file.View.ReadInt64 (index_offset+0x14);
if (index_offset < 0 || index_offset >= file.MaxOffset)
return null;
int pack_key = 0xC4;
var name_buffer = new byte[0x100];
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
int name_length = file.View.ReadUInt16 (index_offset);
if (name_length > name_buffer.Length)
name_buffer = new byte[name_length];
if (name_length != file.View.Read (index_offset+2, name_buffer, 0, (uint)name_length))
return null;
int key = name_length + (pack_key ^ 0x3e);
for (int k = 0; k < name_length; ++k)
name_buffer[k] ^= (byte)(((k + 1) ^ key) + k + 1);
string name = Encodings.cp932.GetString (name_buffer, 0, name_length);
var entry = new QlieEntry { Name = name };
entry.Type = FormatCatalog.Instance.GetTypeFromName (name);
index_offset += 2 + name_length;
entry.Offset = file.View.ReadInt64 (index_offset);
entry.Size = file.View.ReadUInt32 (index_offset+8);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.UnpackedSize = file.View.ReadUInt32 (index_offset+12);
entry.IsPacked = 0 != file.View.ReadInt32 (index_offset+0x10);
entry.IsEncrypted = 0 != file.View.ReadInt32 (index_offset+0x14);
entry.Hash = file.View.ReadUInt32 (index_offset+0x18);
if (3 == pack_version)
entry.Key = (uint)pack_key;
else
entry.Key = 0;
dir.Add (entry);
index_offset += 0x1c;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var qent = entry as QlieEntry;
if (null == qent || (!qent.IsEncrypted && !qent.IsPacked))
return arc.File.CreateStream (entry.Offset, entry.Size);
var data = new byte[entry.Size];
if (entry.Size != arc.File.View.Read (entry.Offset, data, 0, entry.Size))
return arc.File.CreateStream (entry.Offset, entry.Size);
if (qent.IsEncrypted)
Decrypt (data, 0, data.Length, qent.Key);
if (qent.IsPacked)
{
data = Decompress (data);
if (null == data) // probably wrong decryption
return arc.File.CreateStream (entry.Offset, entry.Size);
}
return new MemoryStream (data);
}
private void Decrypt (byte[] buffer, int offset, int length, uint key)
{
if (offset < 0)
throw new ArgumentOutOfRangeException ("offset");
if (length > buffer.Length || offset > buffer.Length - length)
throw new ArgumentOutOfRangeException ("length");
var table = new uint[2,2];
var xor = new uint[2];
for (int i = 0; i < 2; ++i)
{
table[0,i] = 0xA73C5F9Du;
table[1,i] = 0xCE24F523u;
xor[i] = ((uint)length + key) ^ 0xFEC9753Eu;
}
unsafe
{
fixed (byte* raw = buffer)
{
uint* encoded = (uint*)(raw + offset);
for (int i = 0; i < length / 8; ++i)
{
for (int k = 0; k < 2; ++k)
{
table[0,k] = (table[0,k] + table[1,k]) ^ xor[k];
*encoded ^= table[0,k];
xor[k] = *encoded++;
}
}
}
}
}
private byte[] Decompress (byte[] input)
{
if (LittleEndian.ToUInt32 (input, 0) != 0xFF435031)
return null;
bool is_16bit = 0 != (input[4] & 1);
var node = new byte[2,256];
var child_node = new byte[256];
int output_length = LittleEndian.ToInt32 (input, 8);
var output = new byte[output_length];
int src = 12;
int dst = 0;
while (src < input.Length)
{
int i, k, count, index;
for (i = 0; i < 256; i++)
node[0,i] = (byte)i;
for (i = 0; i < 256; )
{
count = input[src++];
if (count > 127)
{
int step = count - 127;
i += step;
count = 0;
}
if (i > 255)
break;
count++;
for (k = 0; k < count; k++)
{
node[0,i] = input[src++];
if (node[0,i] != i)
node[1,i] = input[src++];
i++;
}
}
if (is_16bit)
{
count = LittleEndian.ToUInt16 (input, src);
src += 2;
}
else
{
count = LittleEndian.ToInt32 (input, src);
src += 4;
}
k = 0;
for (;;)
{
if (k > 0)
index = child_node[--k];
else
{
if (0 == count)
break;
count--;
index = input[src++];
}
if (node[0,index] == index)
output[dst++] = (byte)index;
else
{
child_node[k++] = node[1,index];
child_node[k++] = node[0,index];
}
}
}
if (dst != output.Length)
return null;
return output;
}
}
}

View File

@@ -43,6 +43,11 @@ namespace GameRes.Formats.ShiinaRio
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public S25Opener ()
{
Extensions = new string[0];
}
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (4);
@@ -59,7 +64,7 @@ namespace GameRes.Formats.ShiinaRio
{
var entry = new Entry
{
Name = string.Format ("{0}@{1:D4}.s25img", base_name, i),
Name = string.Format ("{0}@{1:D4}.tga", base_name, i),
Type = "image",
Offset = offset,
};
@@ -79,5 +84,34 @@ namespace GameRes.Formats.ShiinaRio
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
// emulate TGA image
var offset = entry.Offset;
var info = new S25MetaData
{
Width = arc.File.View.ReadUInt32 (offset),
Height = arc.File.View.ReadUInt32 (offset+4),
OffsetX = arc.File.View.ReadInt32 (offset+8),
OffsetY = arc.File.View.ReadInt32 (offset+12),
BPP = 32,
FirstOffset = (uint)(offset + 0x14),
};
using (var input = arc.File.CreateStream (0, (uint)arc.File.MaxOffset))
using (var reader = new S25Format.Reader (input, info))
{
var pixels = reader.Unpack();
var header = new byte[0x12];
header[2] = 2;
LittleEndian.Pack ((short)info.OffsetX, header, 8);
LittleEndian.Pack ((short)info.OffsetY, header, 0xa);
LittleEndian.Pack ((ushort)info.Width, header, 0xc);
LittleEndian.Pack ((ushort)info.Height, header, 0xe);
header[0x10] = 32;
header[0x11] = 0x20;
return new PrefixStream (header, new MemoryStream (pixels));
}
}
}
}

View File

@@ -603,25 +603,31 @@ namespace GameRes.Formats.ShiinaRio
internal class EncryptionScheme
{
public string Name { get; set; }
public int Version;
public string Name { get; set; }
public string OriginalTitle { get; set; }
public int Version { get; set; }
public int EntryNameSize;
public byte[] CryptKey;
public uint[] HelperKey;
public uint[] HelperKey { get; set; }
public byte[] ShiinaImage;
public byte[] Region;
public byte[] DecodeBin;
private static CachedResource Resource = new CachedResource();
public static readonly string DefaultCrypt = "Crypt Type 20011002 - Copyright(C) 2000 Y.Yamada/STUDIO よしくん";
public static readonly uint[] ZeroKey = new uint[] { 0, 0, 0, 0, 0 };
public static EncryptionScheme Create (string name, int version, int entry_name_size,
string key1, uint[] key2,
string image, string region_src, string decode_bin = null)
string image, string region_src, string decode_bin = null,
string original_title = "")
{
var scheme = new EncryptionScheme
{
Name = name,
Version = version,
OriginalTitle = original_title,
EntryNameSize = entry_name_size,
CryptKey = Encodings.cp932.GetBytes (key1),
HelperKey = key2,
@@ -632,6 +638,22 @@ namespace GameRes.Formats.ShiinaRio
scheme.DecodeBin = Resource.Load (decode_bin);
return scheme;
}
public static EncryptionScheme Create (int version, string name, string original_title, uint[] key2)
{
string decode = version < 2490 ? "DecodeV1.bin" : "DecodeV249.bin";
string image = version < 2400 ? "ShiinaRio1.png"
: version < 2490 ? "ShiinaRio3.jpg"
: "ShiinaRio4.jpg";
int entry_name_size = version < 2390 ? 0x10 : 0x20;
return Create (name, version, entry_name_size, DefaultCrypt, key2,
image, "ShiinaRio2.png", decode, original_title);
}
public static EncryptionScheme Create (int version, string name)
{
return Create (version, name, "", ZeroKey);
}
}
internal class Decoder
@@ -1186,16 +1208,46 @@ namespace GameRes.Formats.ShiinaRio
public static EncryptionScheme[] KnownSchemes = new EncryptionScheme[]
{
EncryptionScheme.Create ("ShiinaRio v2.37", 2370, 0x10,
"Crypt Type 20011002 - Copyright(C) 2000 Y.Yamada/STUDIO よしくん",
new uint[] { 0xF182C682, 0xE882AA82, 0x718E5896, 0x8183CC82, 0xDAC98283 },
"ShiinaRio1.png", "ShiinaRio2.png", "DecodeV1.bin"),
EncryptionScheme.Create ("ShiinaRio v2.40", 2400, 0x20,
"Crypt Type 20011002 - Copyright(C) 2000 Y.Yamada/STUDIO よしくん",
new uint[] { 0x747C887C, 0xA47EA17C, 0xAF7CA77C, 0xA17C747C, 0x0000A47E },
"ShiinaRio3.jpg", "ShiinaRio2.png", "DecodeV1.bin"),
EncryptionScheme.Create ("ShiinaRio v2.49", 2490, 0x20,
"Crypt Type 20011002 - Copyright(C) 2000 Y.Yamada/STUDIO よしくん",
EncryptionScheme.Create (2360, "ShiinaRio v2.3x"),
EncryptionScheme.Create (2480, "Bloody Rondo", "BLOODY†RONDO",
new uint[] { 0xFBFBF8F6, 0xFBE6EDF0, 0xFBF0FA, 0, 0 }),
EncryptionScheme.Create (2450, "Chuuchuu Nurse", "ちゅうちゅうナース",
new uint[] { 0xB0D1ECD1, 0xECD1F7D1, 0xF7D1B0D1, 0x08D23AD0, 0x13D20BD0 }),
EncryptionScheme.Create (2370, "Classmate no Okaa-san", "クラスメイトのお母さん",
new uint[] { 0xF182C682, 0xE882AA82, 0x718E5896, 0x8183CC82, 0xDAC98283 }),
EncryptionScheme.Create (2470, "Damatte Watashi no Muko ni Nare!", "黙って私のムコになれ!",
new uint[] { 0x44075C13, 0x010B4107, 0x05064907, 0x4C07D706, 0x6F074D07 }),
EncryptionScheme.Create (2470, "Hana to Otome ni Shukufuku wo Royal Bouquet", "花と乙女に祝福を ロイヤルブーケ",
new uint[] { 0xE3A7F1AC, 0xB2AA96AC, 0x4FAAECA7, 0xD5A7BAB0, 0x44A754A7 }),
EncryptionScheme.Create (2400, "Helter Skelter", "ヘルタースケルター",
new uint[] { 0x747C887C, 0xA47EA17C, 0xAF7CA77C, 0xA17C747C, 0x0000A47E }),
EncryptionScheme.Create (2470, "Mahou Shoujo no Taisetsu na Koto", "魔法少女の大切なこと。",
new uint[] { 0x51879387, 0x869EBC9E, 0xF480DD93, 0xD993C981, 0xD793A093 }),
EncryptionScheme.Create (2460, "Mikoko", "みここ",
new uint[] { 0x7DAC51AC, 0x51AC7DAC, 0x7DAC7DAC, 0x7DAC51AC, 0x51AC7DAC }),
EncryptionScheme.Create (2390, "Nagagutsu wo Haita Deco", "長靴をはいたデコ",
new uint[] { 0x486D887E, 0x0F7DBC73, 0x5D7D327D, 0x997C427D, 0x877EAD7C }),
EncryptionScheme.Create (2470, "Nakadashi Trilogy", "なかだしトリロジー",
new uint[] { 0x928ADB9E, 0xB087BB87, 0x928ADB9E, 0xB087BB87, 0xB087BB87 }),
EncryptionScheme.Create (2470, "Onedari Onapet", "おねだりオナペット",
new uint[] { 0xD59CB69C, 0xF69CA09C, 0x779D579D, 0x7C9D679D, 0x0000799D }),
EncryptionScheme.Create (2480, "Onegan!", "おねガン!",
new uint[] { 0xC881AB81, 0x90804880, 0xAB814A82, 0x4880C881, 0x4A829080 }),
EncryptionScheme.Create (2470, "Oreimo Plus", "俺妹プラス",
new uint[] { 0x24371528, 0x2822D722, 0x1528F922, 0xD7222437, 0xF9222822 }),
EncryptionScheme.Create (2470, "Pure Love!", "ぴゅあらっ!",
new uint[] { 0x31500050, 0x35507250, 0x87821350, 0x9D9E9780, 0x00009784 }),
EncryptionScheme.Create (2470, "Ren'ai Saimin", "恋愛催眠",
new uint[] { 0x6E423C5D, 0x7A5C0947, 0x6E423C5D, 0x7A5C0947, 0x6E423C5D }),
EncryptionScheme.Create (2480, "Sensei! Shite Ageru", "先生っ! シてあげる",
new uint[] { 0x70562056, 0x87470744, 0x02449045, 0x76446644, 0x8F472F44 }),
EncryptionScheme.Create (2480, "Tanetsuke Mura", "種憑け村",
new uint[] { 0x7C7C7967, 0x7965574F, 0x4F69647C, 0x00005144, 0 }),
EncryptionScheme.Create (2470, "You~Gaku", "よう∽ガク",
new uint[] { 0x4DE2AB4D, 0x98AB5D46, 0x66496349, 0x485D685D, 0x5F4D4C5D }),
EncryptionScheme.Create (2410, "Zansho Omimai Moushiagemasu", "残暑お見舞い申し上げます。",
new uint[] { 0x3A123012, 0x6C3A6C36, 0x3C36323C, 0x6C360F16, 0x369DD012 }),
EncryptionScheme.Create ("ShiinaRio v2.49", 2490, 0x20, EncryptionScheme.DefaultCrypt,
new uint[] { 0x4B535453, 0xA15FA15F, 0, 0, 0 },
"ShiinaRio4.jpg", "ShiinaRio2.png", "DecodeV249.bin"),
};

58
ArcFormats/AudioEOG.cs Normal file
View File

@@ -0,0 +1,58 @@
//! \file AudioEOG.cs
//! \date Thu Jun 11 12:46:35 2015
//! \brief Crowd engine audio file.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Crowd
{
[Export(typeof(AudioFormat))]
public class EogAudio : OggAudio
{
public override string Tag { get { return "EOG"; } }
public override string Description { get { return "Crowd engine audio format (Ogg/Vorbis)"; } }
public override uint Signature { get { return 0x004D5243; } } // 'CRM'
public override SoundInput TryOpen (Stream file)
{
var ogg = new StreamRegion (file, 8);
try
{
return new OggInput (ogg);
}
catch
{
ogg.Dispose();
throw;
}
}
public override void Write (SoundInput source, Stream output)
{
throw new System.NotImplementedException ("EogFormat.Write not implemenented");
}
}
}

View File

@@ -25,6 +25,7 @@
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.ShiinaRio
{
@@ -37,21 +38,29 @@ namespace GameRes.Formats.ShiinaRio
public override SoundInput TryOpen (Stream file)
{
var input = file as MemoryStream;
if (null == input || !input.CanWrite)
file.Position = 0xc;
var header = new byte[8];
if (8 != file.Read (header, 0, 8))
return null;
if (!Binary.AsciiEqual (header, 0, "fmt "))
return null;
uint offset = LittleEndian.ToUInt32 (header, 4);
file.Seek (offset, SeekOrigin.Current);
if (8 != file.Read (header, 0, 8))
return null;
if (!Binary.AsciiEqual (header, 0, "data"))
return null;
var input = new StreamRegion (file, file.Position);
try
{
input = new MemoryStream();
file.CopyTo (input);
return new OggInput (input);
}
catch
{
input.Dispose();
throw;
}
input.Position = 1;
input.WriteByte ((byte)'g');
input.WriteByte ((byte)'g');
input.WriteByte ((byte)'S');
input.Position = 0;
var ogg = new OggInput (input);
if (file != input)
file.Dispose();
return ogg;
}
}
}

View File

@@ -109,6 +109,7 @@ namespace GameRes.Formats.Ffa
m_data_size = LittleEndian.ToInt32 (m_input, 0x2c);
m_output = new byte[m_data_size+0x2c];
Buffer.BlockCopy (m_input, 4, m_output, 0, 0x2c);
LittleEndian.Pack (m_output.Length-8, m_output, 4);
}
static ushort[] word_456CA0 = new ushort[] {

123
ArcFormats/ImageCWL.cs Normal file
View File

@@ -0,0 +1,123 @@
//! \file ImageCWL.cs
//! \date Sat Jun 13 09:36:33 2015
//! \brief Crowd compressed image format.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Media;
using GameRes.Utility;
namespace GameRes.Formats.Crowd
{
[Export(typeof(ImageFormat))]
public class CwdFormat : ImageFormat
{
public override string Tag { get { return "CWD"; } }
public override string Description { get { return "Crowd hi-color bitmap"; } }
public override uint Signature { get { return 0x20647763u; } } // 'cwd '
static readonly byte[] SignatureText = Encoding.ASCII.GetBytes ("cwd format - version 1.00 -");
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[0x38];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
if (!header.Take (SignatureText.Length).SequenceEqual (SignatureText))
return null;
uint key = header[0x34] + 0x259Au;
return new ImageMetaData
{
Width = LittleEndian.ToUInt32 (header, 0x2c) + key,
Height = LittleEndian.ToUInt32 (header, 0x30) + key,
BPP = 15,
};
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
stream.Position = 0x38;
int size = (int)info.Width * (int)info.Height * 2;
var pixels = new byte[size];
if (pixels.Length != stream.Read (pixels, 0, pixels.Length))
throw new InvalidFormatException ("Unexpected end of file");
return ImageData.Create (info, PixelFormats.Bgr555, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new NotImplementedException ("CwdFormat.Write not implemented");
}
}
[Export(typeof(ImageFormat))]
public class CwlFormat : CwdFormat
{
public override string Tag { get { return "CWL"; } }
public override string Description { get { return "LZ-compressed Crowd bitmap"; } }
public override uint Signature { get { return 0x44445A53u; } } // 'SZDD'
public override ImageMetaData ReadMetaData (Stream stream)
{
stream.Position = 0x0e;
using (var lz = new LzssReader (stream, 100, 0x38)) // extract CWD header
{
lz.FrameSize = 0x1000;
lz.FrameFill = 0x20;
lz.FrameInitPos = 0x1000 - 0x10;
lz.Unpack();
using (var cwd = new MemoryStream (lz.Data))
return base.ReadMetaData (cwd);
}
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
if (stream.Length > int.MaxValue)
throw new FileSizeException();
var header = new byte[14];
if (header.Length != stream.Read (header, 0, header.Length))
throw new InvalidFormatException();
int data_length = LittleEndian.ToInt32 (header, 10);
int input_length = (int)(stream.Length-stream.Position);
using (var lz = new LzssReader (stream, input_length, data_length))
{
lz.FrameSize = 0x1000;
lz.FrameFill = 0x20;
lz.FrameInitPos = 0x1000 - 0x10;
lz.Unpack();
using (var cwd = new MemoryStream (lz.Data))
return base.Read (cwd, info);
}
}
public override void Write (Stream file, ImageData image)
{
throw new NotImplementedException ("CwlFormat.Write not implemented");
}
}
}

188
ArcFormats/ImageCWP.cs Normal file
View File

@@ -0,0 +1,188 @@
//! \file ImageCWP.cs
//! \date Thu Jun 11 13:43:41 2015
//! \brief Crowd engine image format.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
namespace GameRes.Formats.Crowd
{
[Export(typeof(ImageFormat))]
public class CwpFormat : ImageFormat
{
public override string Tag { get { return "CWP"; } }
public override string Description { get { return "Crowd engine image format"; } }
public override uint Signature { get { return 0x50445743; } } // 'CWDP'
public override ImageMetaData ReadMetaData (Stream stream)
{
using (var input = new ArcView.Reader (stream))
{
input.ReadInt32();
uint width = Binary.BigEndian (input.ReadUInt32());
uint height = Binary.BigEndian (input.ReadUInt32());
if (0 == width || 0 == height)
return null;
int bpp = input.ReadByte();
int color_type = input.ReadByte();
switch (color_type)
{
case 2: bpp *= 3; break;
case 4: bpp *= 2; break;
case 6: bpp *= 4; break;
case 3:
case 0: break;
default: return null;
}
return new ImageMetaData
{
Width = width,
Height = height,
BPP = bpp,
};
}
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
var header = new byte[0x15];
using (var mem = new MemoryStream((int)(0x14 + stream.Length + 12)))
using (var png = new BinaryWriter (mem))
{
png.Write (0x474E5089u); // png header
png.Write (0x0A1A0A0Du);
png.Write (0x0D000000u);
png.Write (0x52444849u); // 'IHDR'
stream.Position = 4;
stream.Read (header, 0, header.Length);
png.Write (header, 0, header.Length);
png.Write (0x54414449u); // 'IDAT'
stream.CopyTo (mem);
header[1] = 0;
header[2] = 0;
header[3] = 0;
LittleEndian.Pack (0x444E4549, header, 4);
LittleEndian.Pack (0x826042AE, header, 8);
png.Write (header, 1, 11);
mem.Position = 0;
var decoder = new PngBitmapDecoder (mem, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapSource frame = decoder.Frames[0];
var pixels = new byte[info.Width*info.Height*4];
frame.CopyPixels (pixels, (int)info.Width*4, 0);
for (int i = 0; i < pixels.Length; i += 4)
{
byte t = pixels[i];
pixels[i] = pixels[i+2];
pixels[i+2] = t;
}
return ImageData.Create (info, PixelFormats.Bgr32, null, pixels);
}
}
public override void Write (Stream file, ImageData image)
{
var writer = new Writer (image);
writer.Write (file);
}
internal class Writer
{
BitmapSource m_bitmap;
byte[] m_buffer = new byte[81920];
public Writer (ImageData image)
{
m_bitmap = image.Bitmap;
if (m_bitmap.Format.BitsPerPixel < 32)
{
m_bitmap = new FormatConvertedBitmap (m_bitmap, PixelFormats.Bgr32, null, 0);
}
int stride = m_bitmap.PixelWidth * 4;
var pixels = new byte[stride*m_bitmap.PixelHeight];
m_bitmap.CopyPixels (pixels, stride, 0);
for (int i = 0; i < pixels.Length; i += 4)
{
byte t = pixels[i];
pixels[i] = pixels[i+2];
pixels[i+2] = t;
}
m_bitmap = BitmapSource.Create (m_bitmap.PixelWidth, m_bitmap.PixelHeight,
m_bitmap.DpiX, m_bitmap.DpiY, PixelFormats.Bgra32, null, pixels, stride);
}
public void Write (Stream file)
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add (BitmapFrame.Create (m_bitmap));
using (var png = new MemoryStream())
using (var cwp = new BinaryWriter (file, System.Text.Encoding.ASCII, true))
{
encoder.Save (png);
var header = new byte[0x11];
png.Position = 0x10;
png.Read (header, 0, header.Length);
cwp.Write (0x50445743u); // 'CWDP'
cwp.Write (header, 0, header.Length);
var idat = PngFormat.FindChunk (png, "IDAT");
if (-1 == idat)
throw new InvalidFormatException ("CWP conversion failed");
png.Position = idat;
png.Read (header, 0, 8);
int chunk_size = BigEndian.ToInt32 (header, 0) + 4;
cwp.Write (header, 0, 4);
for (;;)
{
CopyChunk (png, file, chunk_size);
if (8 != png.Read (header, 0, 8))
throw new InvalidFormatException ("CWP conversion failed");
if (Binary.AsciiEqual (header, 4, "IEND"))
{
cwp.Write ((byte)0);
break;
}
chunk_size = BigEndian.ToInt32 (header, 0) + 4;
cwp.Write (header, 0, 8);
}
}
}
private void CopyChunk (Stream src, Stream dst, int size)
{
while (size > 0)
{
int amount = Math.Min (size, m_buffer.Length);
int read = src.Read (m_buffer, 0, amount);
if (read != amount)
throw new InvalidFormatException ("CWP conversion failed");
dst.Write (m_buffer, 0, amount);
size -= amount;
}
}
}
}
}

View File

@@ -428,15 +428,10 @@ namespace GameRes.Formats.DRS
[Export(typeof(ImageFormat))]
public class DrgIndexedFormat : ImageFormat
{
public override string Tag { get { return "DRG"; } }
public override string Tag { get { return "GGD"; } }
public override string Description { get { return "Digital Romance System indexed image format"; } }
public override uint Signature { get { return ~0x47363532u; } } // '256G'
public DrgIndexedFormat ()
{
Extensions = new string[] { "ggd" };
}
public override ImageMetaData ReadMetaData (Stream stream)
{
using (var input = new ArcView.Reader (stream))

179
ArcFormats/ImageGGP.cs Normal file
View File

@@ -0,0 +1,179 @@
//! \file ImageGGP.cs
//! \date Sun Jun 14 09:06:26 2015
//! \brief
//
// Copyright (C) 2014-2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.DRS
{
internal class GgpMetaData : ImageMetaData
{
public byte[] Key = new byte[8];
public uint Offset;
public uint Length;
}
[Export(typeof(ImageFormat))]
public class GgpFormat : PngFormat
{
public override string Tag { get { return "GGP"; } }
public override string Description { get { return "Digital Romance System encrypted image format"; } }
public override uint Signature { get { return 0x46504747u; } } // 'GGPF'
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[0x24];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
if (!Binary.AsciiEqual (header, 0, "GGPFAIKE"))
return null;
var info = new GgpMetaData
{
Offset = LittleEndian.ToUInt32 (header, 0x14),
Length = LittleEndian.ToUInt32 (header, 0x18),
};
for (int i = 0; i < 8; ++i)
info.Key[i] = (byte)(header[i] ^ header[i+0xC]);
stream.Position = info.Offset;
using (var png = new EncryptedStream (stream, info.Key, true))
{
var png_info = base.ReadMetaData (png);
info.Width = png_info.Width;
info.Height = png_info.Height;
info.BPP = png_info.BPP;
info.OffsetX = png_info.OffsetX;
info.OffsetY = png_info.OffsetY;
return info;
}
}
public override ImageData Read (Stream file, ImageMetaData info)
{
var meta = info as GgpMetaData;
if (null == meta)
throw new ArgumentException ("GgpFormat.Read should be supplied with GgpMetaData", "info");
using (var input = new StreamRegion (file, meta.Offset, meta.Length, true))
using (var png = new EncryptedStream (input, meta.Key, true))
return base.Read (png, info);
}
public override void Write (Stream file, ImageData image)
{
throw new NotImplementedException ("GgpFormat.Write not implemented");
}
}
internal class EncryptedStream : Stream
{
private Stream m_stream;
private byte[] m_key;
private long m_position;
private bool m_should_dispose;
public EncryptedStream (Stream main, byte[] key, bool leave_open = false)
{
if (null == key)
throw new ArgumentNullException ("key");
if (key.Length < 8)
throw new ArgumentException ("key");
m_stream = main;
m_key = key;
m_position = 0;
m_should_dispose = !leave_open;
}
public override bool CanRead { get { return m_stream.CanRead; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { throw new NotSupportedException(); } }
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void Flush()
{
m_stream.Flush();
}
public override int Read (byte[] buffer, int offset, int count)
{
int read = m_stream.Read (buffer, offset, count);
if (read > 0)
{
for (int i = 0; i < read; ++i)
{
buffer[offset+i] ^= m_key[m_position++ & 7];
}
}
return read;
}
public override int ReadByte ()
{
int b = m_stream.ReadByte();
if (-1 != b)
{
b ^= m_key[m_position++ & 7];
}
return b;
}
public override void SetLength (long length)
{
throw new NotSupportedException ("EncryptedStream.SetLength method is not supported");
}
public override void Write (byte[] buffer, int offset, int count)
{
throw new NotSupportedException ("EncryptedStream.Write method is not supported");
}
public override void WriteByte (byte value)
{
throw new NotSupportedException ("EncryptedStream.WriteByte method is not supported");
}
bool m_disposed = false;
protected override void Dispose (bool disposing)
{
if (!m_disposed)
{
if (m_should_dispose)
m_stream.Dispose();
m_disposed = true;
base.Dispose (disposing);
}
}
}
}

View File

@@ -80,10 +80,7 @@ namespace GameRes.Formats.ShiinaRio
using (var reader = new Reader (stream, meta))
{
var pixels = reader.Unpack();
var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, 96, 96,
PixelFormats.Bgra32, null, pixels, (int)info.Width*4);
bitmap.Freeze();
return new ImageData (bitmap, info);
return ImageData.Create (info, PixelFormats.Bgra32, null, pixels);
}
}

86
ArcFormats/ImageZBM.cs Normal file
View File

@@ -0,0 +1,86 @@
//! \file ImageZBM.cs
//! \date Thu Jun 11 16:24:09 2015
//! \brief LZ-compressed bitmap format.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.Crowd
{
[Export(typeof(ImageFormat))]
public class ZbmFormat : BmpFormat
{
public override string Tag { get { return "ZBM"; } }
public override string Description { get { return "LZ-compressed bitmap"; } }
public override uint Signature { get { return 0x44445A53u; } } // 'SZDD'
public override ImageMetaData ReadMetaData (Stream stream)
{
stream.Position = 0x0e;
using (var lz = new LzssReader (stream, 100, 54)) // extract BMP header
{
lz.FrameSize = 0x1000;
lz.FrameFill = 0x20;
lz.FrameInitPos = 0x1000 - 0x10;
lz.Unpack();
var header = lz.Data;
for (int i = 0; i < 54; ++i)
header[i] ^= 0xff;
using (var bmp = new MemoryStream (header))
return base.ReadMetaData (bmp);
}
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
if (stream.Length > int.MaxValue)
throw new FileSizeException();
var header = new byte[14];
if (header.Length != stream.Read (header, 0, header.Length))
throw new InvalidFormatException();
int data_length = LittleEndian.ToInt32 (header, 10);
int input_length = (int)(stream.Length-stream.Position);
using (var lz = new LzssReader (stream, input_length, data_length))
{
lz.FrameSize = 0x1000;
lz.FrameFill = 0x20;
lz.FrameInitPos = 0x1000 - 0x10;
lz.Unpack();
var data = lz.Data;
int count = Math.Min (100, data.Length);
for (int i = 0; i < count; ++i)
data[i] ^= 0xff;
using (var bmp = new MemoryStream (data))
return base.Read (bmp, info);
}
}
public override void Write (Stream file, ImageData image)
{
throw new NotImplementedException ("ZbmFormat.Write not implemented");
}
}
}

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.0.6.59")]
[assembly: AssemblyFileVersion ("1.0.6.59")]
[assembly: AssemblyVersion ("1.0.7.65")]
[assembly: AssemblyFileVersion ("1.0.7.65")]

View File

@@ -357,5 +357,17 @@ namespace GameRes.Formats.Properties {
this["DPKLastScheme"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string MBLPassPhrase {
get {
return ((string)(this["MBLPassPhrase"]));
}
set {
this["MBLPassPhrase"] = value;
}
}
}
}

View File

@@ -86,5 +86,8 @@
<Setting Name="DPKLastScheme" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="MBLPassPhrase" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@@ -279,6 +279,15 @@ namespace GameRes.Formats.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Archive contains encrypted scripts.Choose encryption scheme or enter a passphrase..
/// </summary>
public static string MBLNotice {
get {
return ResourceManager.GetString("MBLNotice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Adding file.
/// </summary>

View File

@@ -318,4 +318,8 @@ Enter archive encryption key.</value>
<data name="KCAPDefault" xml:space="preserve">
<value>Default</value>
</data>
</root>
<data name="MBLNotice" xml:space="preserve">
<value>Archive contains encrypted scripts.
Choose encryption scheme or enter a passphrase.</value>
</data>
</root>

View File

@@ -174,6 +174,10 @@
<data name="LabelScheme" xml:space="preserve">
<value>Вариант</value>
</data>
<data name="MBLNotice" xml:space="preserve">
<value>Архив содержит зашифрованные скрипты.
Выберите способ шифрования или введите текстовый пароль.</value>
</data>
<data name="MsgAddingFile" xml:space="preserve">
<value>Добавляется файл</value>
</data>

View File

@@ -3,8 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:GameRes.Formats.Strings"
xmlns:p="clr-namespace:GameRes.Formats.Properties"
xmlns:dac="clr-namespace:GameRes.Formats.Dac"
xmlns:local="clr-namespace:GameRes.Formats.GUI">
xmlns:dac="clr-namespace:GameRes.Formats.Dac">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition MinWidth="130" Width="*"/>

17
ArcFormats/WidgetMBL.xaml Normal file
View File

@@ -0,0 +1,17 @@
<Grid x:Class="GameRes.Formats.GUI.WidgetMBL"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:GameRes.Formats.Strings"
xmlns:p="clr-namespace:GameRes.Formats.Properties"
xmlns:m="clr-namespace:GameRes.Formats.Marble">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Name="EncScheme" Grid.Row="0" Margin="0,3,0,0" Width="160" HorizontalAlignment="Right"
ItemsSource="{Binding Source={x:Static m:MblOpener.KnownKeys}, Mode=OneWay}"
DisplayMemberPath="Key" SelectedValuePath="Value"
SelectedValue="{Binding ElementName=PassPhrase, Path=Text, Mode=TwoWay}"/>
<TextBox Name="PassPhrase" Grid.Row="1" Margin="0,3,0,3" Width="{Binding ElementName=EncScheme, Path=ActualWidth}" HorizontalAlignment="Right"
Text="{Binding Source={x:Static p:Settings.Default}, Path=MBLPassPhrase, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace GameRes.Formats.GUI
{
/// <summary>
/// Interaction logic for WidgetMBL.xaml
/// </summary>
public partial class WidgetMBL : Grid
{
public WidgetMBL ()
{
InitializeComponent ();
}
}
}

View File

@@ -4,9 +4,16 @@
xmlns:fmt="clr-namespace:GameRes.Formats.ShiinaRio"
xmlns:p="clr-namespace:GameRes.Formats.Properties"
MaxWidth="250">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Name="Scheme" ItemsSource="{Binding Source={x:Static fmt:Decoder.KnownSchemes}, Mode=OneWay}"
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=WARCScheme, Mode=TwoWay}"
SelectedValuePath="Name"
DisplayMemberPath="Name"
Width="180"/>
Width="200" Grid.Row="0"/>
<TextBox Name="Original" Background="Transparent" BorderThickness="0" Text="{Binding Path=OriginalTitle}"
IsReadOnly="True" TextWrapping="NoWrap" Grid.Row="1" Margin="0,3,0,3"
DataContext="{Binding ElementName=Scheme, Path=SelectedItem}"/>
</Grid>

View File

@@ -88,6 +88,9 @@
<setting name="DPKLastScheme" serializeAs="String">
<value />
</setting>
<setting name="MBLPassPhrase" serializeAs="String">
<value />
</setting>
</GameRes.Formats.Properties.Settings>
</userSettings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>

View File

@@ -1,28 +1,26 @@
<Window x:Class="GARbro.GUI.ConvertImages"
<Window x:Class="GARbro.GUI.ConvertMedia"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:GARbro.GUI.Strings"
xmlns:p="clr-namespace:GARbro.GUI.Properties"
xmlns:g="clr-namespace:GameRes;assembly=GameRes"
Title="{x:Static s:guiStrings.TextConvertImages}" ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Title="{x:Static s:guiStrings.TextConvertMedia}" ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
ResizeMode="NoResize" SizeToContent="WidthAndHeight"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Margin="20,10,20,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="20,10,20,0">
<Label Content="{x:Static s:guiStrings.LabelDestinationFormat}" Target="{Binding ElementName=ImageConversionFormat}" HorizontalAlignment="Left" Padding="0,0,10,0" VerticalAlignment="Center"/>
<ComboBox Name="ImageConversionFormat" DisplayMemberPath="Tag" Width="60" HorizontalAlignment="Left"
ItemsSource="{Binding Source={x:Static g:FormatCatalog.Instance}, Path=ImageFormats, Mode=OneWay}"
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=appLastImageFormat, Mode=TwoWay}" SelectedValuePath="Tag"/>
ItemsSource="{Binding Source={x:Static g:FormatCatalog.Instance}, Path=ImageFormats, Mode=OneWay}" SelectedValuePath="Tag"
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=appLastImageFormat, Mode=TwoWay}"/>
</StackPanel>
<Separator Grid.Row="1"/>
<StackPanel Orientation="Horizontal" Margin="10,0,10,10" HorizontalAlignment="Right" Grid.Row="2">
<TextBlock Text="{x:Static s:guiStrings.TextAudioConversion}" Margin="20,10,20,0"/>
<CheckBox Name="IgnoreErrors" Content="{x:Static s:guiStrings.LabelSkipFailures}" Margin="20,10"
IsChecked="{Binding Source={x:Static p:Settings.Default}, Path=appIgnoreConversionErrors, Mode=TwoWay}"/>
<Separator/>
<StackPanel Orientation="Horizontal" Margin="10,0,10,10" HorizontalAlignment="Right">
<Button Content="{x:Static s:guiStrings.ButtonOK}" Click="ConvertButton_Click" Margin="10" Width="75" IsDefault="True" Height="25"/>
<Button Content="{x:Static s:guiStrings.ButtonCancel}" Margin="10" IsCancel="True" Width="75" Height="25"/>
</StackPanel>
</Grid>
</StackPanel>
</Window>

View File

@@ -3,11 +3,11 @@
namespace GARbro.GUI
{
/// <summary>
/// Interaction logic for ConvertImages.xaml
/// Interaction logic for ConvertMedia.xaml
/// </summary>
public partial class ConvertImages : Window
public partial class ConvertMedia : Window
{
public ConvertImages ()
public ConvertMedia ()
{
InitializeComponent ();
}

View File

@@ -126,8 +126,8 @@
<Compile Include="ArcParameters.xaml.cs">
<DependentUpon>ArcParameters.xaml</DependentUpon>
</Compile>
<Compile Include="ConvertImages.xaml.cs">
<DependentUpon>ConvertImages.xaml</DependentUpon>
<Compile Include="ConvertMedia.xaml.cs">
<DependentUpon>ConvertMedia.xaml</DependentUpon>
</Compile>
<Compile Include="CreateArchive.xaml.cs">
<DependentUpon>CreateArchive.xaml</DependentUpon>
@@ -168,7 +168,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ConvertImages.xaml">
<Page Include="ConvertMedia.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View File

@@ -88,6 +88,11 @@ namespace GameRes
return arc;
}
}
catch (OperationCanceledException X)
{
FormatCatalog.Instance.LastError = X;
return null;
}
catch (Exception X)
{
// ignore failed open attmepts
@@ -143,6 +148,16 @@ namespace GameRes
return m_interface.OpenEntry (this, entry);
}
/// <summary>
/// Open specified <paramref name="entry"/> as memory-mapped view.
/// </summary>
public ArcView OpenView (Entry entry)
{
var stream = OpenEntry (entry);
uint size = stream.CanSeek ? (uint)stream.Length : entry.Size;
return new ArcView (stream, entry.Name, size);
}
/// <summary>
/// Open specified <paramref name="entry"/> as a seekable Stream.
/// </summary>
@@ -168,6 +183,14 @@ namespace GameRes
return ArchiveFormat.CreateFile (entry.Name);
}
public IFileSystem CreateFileSystem ()
{
if (m_interface.IsHierarchic)
return new ArchiveFileSystem (this);
else
return new FlatArchiveFileSystem (this);
}
#region IDisposable Members
bool disposed = false;

View File

@@ -2,7 +2,7 @@
//! \date Mon Jul 07 10:31:10 2014
//! \brief Memory mapped view of gameres file.
//
// Copyright (C) 2014 by morkt
// Copyright (C) 2014-2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@@ -78,6 +78,8 @@ namespace GameRes
{
static public string ReadString (this MemoryMappedViewAccessor view, long offset, uint size, Encoding enc)
{
if (0 == size)
return string.Empty;
byte[] buffer = new byte[size];
uint n;
for (n = 0; n < size; ++n)
@@ -144,14 +146,70 @@ namespace GameRes
{
Name = name;
MaxOffset = fs.Length;
m_map = MemoryMappedFile.CreateFromFile (fs, null, 0,
MemoryMappedFileAccess.Read, null, HandleInheritability.None, true);
try {
View = new Frame (this);
} catch {
m_map.Dispose(); // dispose on error only
throw;
InitFromFileStream (fs, 0);
}
}
public ArcView (Stream input, string name, uint length)
{
Name = name;
MaxOffset = length;
if (input is FileStream)
InitFromFileStream (input as FileStream, length);
else
InitFromStream (input, length);
}
private void InitFromFileStream (FileStream fs, uint length)
{
m_map = MemoryMappedFile.CreateFromFile (fs, null, length,
MemoryMappedFileAccess.Read, null, HandleInheritability.None, true);
try {
View = new Frame (this);
} catch {
m_map.Dispose(); // dispose on error only
throw;
}
}
private void InitFromStream (Stream input, uint length)
{
m_map = MemoryMappedFile.CreateNew (null, length, MemoryMappedFileAccess.ReadWrite,
MemoryMappedFileOptions.None, null, HandleInheritability.None);
try
{
using (var view = m_map.CreateViewAccessor (0, length, MemoryMappedFileAccess.Write))
{
var buffer = new byte[81920];
unsafe
{
byte* ptr = view.GetPointer (0);
try
{
uint total = 0;
while (total < length)
{
int read = input.Read (buffer, 0, buffer.Length);
if (0 == read)
break;
read = (int)Math.Min (read, length-total);
Marshal.Copy (buffer, 0, (IntPtr)(ptr+total), read);
total += (uint)read;
}
MaxOffset = total;
}
finally
{
view.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}
View = new Frame (this);
}
catch
{
m_map.Dispose();
throw;
}
}
@@ -351,7 +409,7 @@ namespace GameRes
public string ReadString (long offset, uint size, Encoding enc)
{
Reserve (offset, size);
size = Math.Min (size, Reserve (offset, size));
return m_view.ReadString (offset-m_offset, size, enc);
}

303
GameRes/FileSystem.cs Normal file
View File

@@ -0,0 +1,303 @@
//! \file FileSystem.cs
//! \date Fri Jun 05 15:32:27 2015
//! \brief Gameres file system abstraction.
//
// Copyright (C) 2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace GameRes
{
public interface IFileSystem : IDisposable
{
/// <summary>
/// Open file for reading.
/// </summary>
Stream OpenStream (Entry entry);
ArcView OpenView (Entry entry);
/// <summary>
/// Enumerates subdirectories and files in current directory.
/// </summary>
IEnumerable<Entry> GetFiles ();
/// <summary>
/// Recursively enumerates files in the current directory and its subdirectories.
/// Subdirectory entries are omitted.
/// </summary>
IEnumerable<Entry> GetFilesRecursive ();
string CurrentDirectory { get; set; }
}
public class SubDirEntry : Entry
{
public override string Type { get { return "directory"; } }
public SubDirEntry (string name)
{
Name = name;
Size = 0;
}
}
public class PhysicalFileSystem : IFileSystem
{
public string CurrentDirectory
{
get { return Directory.GetCurrentDirectory(); }
set { Directory.SetCurrentDirectory (value); }
}
public IEnumerable<Entry> GetFiles ()
{
var info = new DirectoryInfo (CurrentDirectory);
foreach (var subdir in info.EnumerateDirectories())
{
if (0 != (subdir.Attributes & (FileAttributes.Hidden | FileAttributes.System)))
continue;
yield return new SubDirEntry (subdir.FullName);
}
foreach (var file in info.EnumerateFiles())
{
if (0 != (file.Attributes & (FileAttributes.Hidden | FileAttributes.System)))
continue;
yield return EntryFromFileInfo (file);
}
}
public IEnumerable<Entry> GetFilesRecursive ()
{
var info = new DirectoryInfo (CurrentDirectory);
foreach (var file in info.EnumerateFiles ("*", SearchOption.AllDirectories))
{
if (0 != (file.Attributes & (FileAttributes.Hidden | FileAttributes.System)))
continue;
yield return EntryFromFileInfo (file);
}
}
private Entry EntryFromFileInfo (FileInfo file)
{
var entry = FormatCatalog.Instance.CreateEntry (file.FullName);
entry.Size = (uint)Math.Min (file.Length, uint.MaxValue);
return entry;
}
public Stream OpenStream (Entry entry)
{
return File.OpenRead (entry.Name);
}
public ArcView OpenView (Entry entry)
{
return new ArcView (entry.Name);
}
public void Dispose ()
{
GC.SuppressFinalize (this);
}
}
public class FlatArchiveFileSystem : IFileSystem
{
protected ArcFile m_arc;
public virtual string CurrentDirectory
{
get { return ""; }
set
{
if (string.IsNullOrEmpty (value))
return;
if (".." == value || "." == value)
return;
if ("\\" == value || "/" == value)
return;
throw new DirectoryNotFoundException();
}
}
public FlatArchiveFileSystem (ArcFile arc)
{
m_arc = arc;
}
public Stream OpenStream (Entry entry)
{
return m_arc.OpenEntry (entry);
}
public ArcView OpenView (Entry entry)
{
return m_arc.OpenView (entry);
}
public virtual IEnumerable<Entry> GetFiles ()
{
return m_arc.Dir;
}
public virtual IEnumerable<Entry> GetFilesRecursive ()
{
return m_arc.Dir;
}
#region IDisposable Members
bool _arc_disposed = false;
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (!_arc_disposed)
{
if (disposing)
{
m_arc.Dispose();
}
_arc_disposed = true;
}
}
#endregion
}
public class ArchiveFileSystem : FlatArchiveFileSystem
{
private string m_cwd;
private string PathDelimiter { get; set; }
private static readonly char[] m_path_delimiters = { '/', '\\' };
public ArchiveFileSystem (ArcFile arc) : base (arc)
{
m_cwd = "";
PathDelimiter = "/";
}
public override string CurrentDirectory
{
get { return m_cwd; }
set { ChDir (value); }
}
static readonly Regex path_re = new Regex (@"\G[/\\]?([^/\\]+)([/\\])");
public override IEnumerable<Entry> GetFiles ()
{
var root_dir = "";
IEnumerable<Entry> dir = m_arc.Dir;
if (!string.IsNullOrEmpty (m_cwd))
{
root_dir = m_cwd+PathDelimiter;
dir = from entry in dir
where entry.Name.StartsWith (root_dir)
select entry;
if (!dir.Any())
{
throw new DirectoryNotFoundException();
}
}
var subdirs = new HashSet<string>();
foreach (var entry in dir)
{
var match = path_re.Match (entry.Name, root_dir.Length);
if (match.Success)
{
string name = match.Groups[1].Value;
if (subdirs.Add (name))
{
PathDelimiter = match.Groups[2].Value;
yield return new SubDirEntry (root_dir+name);
}
}
else
{
yield return entry;
}
}
}
public override IEnumerable<Entry> GetFilesRecursive ()
{
if (0 == m_cwd.Length)
return m_arc.Dir;
else
return from file in m_arc.Dir
where file.Name.StartsWith (m_cwd + PathDelimiter)
select file;
}
private void ChDir (string path)
{
if (string.IsNullOrEmpty (path))
return;
List<string> cur_dir;
if (-1 != Array.IndexOf (m_path_delimiters, path[0]))
{
path = path.TrimStart (m_path_delimiters);
cur_dir = new List<string>();
}
else
{
cur_dir = m_cwd.Split (m_path_delimiters).ToList();
}
var path_list = path.Split (m_path_delimiters);
foreach (var dir in path_list)
{
if ("." == dir)
{
continue;
}
else if (".." == dir)
{
if (0 == cur_dir.Count)
continue;
cur_dir.RemoveAt (cur_dir.Count-1);
}
else
{
cur_dir.Add (dir);
}
}
string new_path = string.Join (PathDelimiter, cur_dir);
if (0 != new_path.Length)
{
var entry = m_arc.Dir.FirstOrDefault (e => e.Name.StartsWith (new_path + PathDelimiter));
if (null == entry)
throw new DirectoryNotFoundException();
}
m_cwd = new_path;
}
}
}

View File

@@ -57,6 +57,7 @@
<Compile Include="ArcView.cs" />
<Compile Include="Audio.cs" />
<Compile Include="AudioWAV.cs" />
<Compile Include="FileSystem.cs" />
<Compile Include="GameRes.cs" />
<Compile Include="Image.cs" />
<Compile Include="ImageBMP.cs" />

View File

@@ -2,7 +2,7 @@
//! \date Sat Jul 05 00:09:15 2014
//! \brief PNG image implementation.
//
// Copyright (C) 2014 by morkt
// Copyright (C) 2014-2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@@ -120,8 +120,7 @@ namespace GameRes
var file = new ArcView.Reader (stream);
try
{
if (file.ReadUInt32() != Signature)
return null;
file.ReadUInt32();
if (file.ReadUInt32() != 0x0a1a0a0d)
return null;
uint chunk_size = Binary.BigEndian (file.ReadUInt32());
@@ -178,9 +177,8 @@ namespace GameRes
return meta;
}
long FindChunk (Stream stream, string chunk)
public static long FindChunk (Stream stream, string chunk)
{
char[] find_name = chunk.ToCharArray();
long found_offset = -1;
var file = new ArcView.Reader (stream);
try

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.0.5.6")]
[assembly: AssemblyFileVersion ("1.0.5.6")]
[assembly: AssemblyVersion ("1.1.1.8")]
[assembly: AssemblyFileVersion ("1.1.1.8")]

View File

@@ -2,6 +2,26 @@
//! \date Fri Aug 22 08:22:47 2014
//! \brief Game resources conversion methods.
//
// Copyright (C) 2014-2015 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.IO;
@@ -23,53 +43,61 @@ namespace GARbro.GUI
/// <summary>
/// Convert selected images to another format.
/// </summary>
void ConvertImageExec (object sender, ExecutedRoutedEventArgs e)
void ConvertMediaExec (object sender, ExecutedRoutedEventArgs e)
{
if (ViewModel.IsArchive)
return;
var source = CurrentDirectory.SelectedItems.Cast<EntryViewModel>()
.Where (f => f.Type == "image").Select (f => f.Name);
var convert_dialog = new ConvertImages();
.Where (f => f.Type == "image" || f.Type == "audio")
.Select (f => f.Source);
if (!source.Any())
{
PopupError (guiStrings.MsgNoMediaFiles, guiStrings.TextMediaConvertError);
return;
}
var convert_dialog = new ConvertMedia();
convert_dialog.Owner = this;
var result = convert_dialog.ShowDialog() ?? false;
if (!result)
return;
var selected = convert_dialog.ImageConversionFormat.SelectedValue as string;
var format = FormatCatalog.Instance.ImageFormats.FirstOrDefault (f => f.Tag == selected);
var format = convert_dialog.ImageConversionFormat.SelectedItem as ImageFormat;
if (null == format)
{
Trace.WriteLine ("Format is not selected", "ConvertImageExec");
Trace.WriteLine ("Format is not selected", "ConvertMediaExec");
return;
}
try
{
Directory.SetCurrentDirectory (ViewModel.Path);
var converter = new GarConvertImages (this);
var converter = new GarConvertMedia (this);
converter.IgnoreErrors = convert_dialog.IgnoreErrors.IsChecked ?? false;
converter.Convert (source, format);
}
catch (Exception X)
{
PopupError (X.Message, guiStrings.TextImageConvertError);
PopupError (X.Message, guiStrings.TextMediaConvertError);
}
}
}
internal class GarConvertImages
internal class GarConvertMedia
{
private MainWindow m_main;
private ProgressDialog m_progress_dialog;
private IEnumerable<string> m_source;
private IEnumerable<Entry> m_source;
private ImageFormat m_image_format;
private Exception m_pending_error;
private List<Tuple<string,string>> m_failed = new List<Tuple<string,string>>();
public bool IgnoreErrors { get; set; }
public IEnumerable<Tuple<string,string>> FailedFiles { get { return m_failed; } }
public GarConvertImages (MainWindow parent)
public GarConvertMedia (MainWindow parent)
{
m_main = parent;
}
public void Convert (IEnumerable<string> images, ImageFormat format)
public void Convert (IEnumerable<Entry> images, ImageFormat format)
{
m_main.StopWatchDirectoryChanges();
m_source = images;
@@ -91,46 +119,28 @@ namespace GARbro.GUI
m_pending_error = null;
try
{
string target_ext = m_image_format.Extensions.First();
int total = m_source.Count();
int i = 0;
foreach (var filename in m_source)
foreach (var entry in m_source)
{
if (m_progress_dialog.CancellationPending)
throw new OperationCanceledException();
var filename = entry.Name;
int progress = i++*100/total;
string target_name = Path.ChangeExtension (filename, target_ext);
if (filename == target_name)
continue;
string source_ext = Path.GetExtension (filename).TrimStart ('.').ToLowerInvariant();
if (m_image_format.Extensions.Any (ext => ext == source_ext))
continue;
m_progress_dialog.ReportProgress (progress, string.Format (guiStrings.MsgConvertingFile,
Path.GetFileName (filename)), null);
try
{
using (var file = File.OpenRead (filename))
{
m_progress_dialog.ReportProgress (progress, string.Format (guiStrings.MsgConvertingImage,
filename), target_name);
var image = ImageFormat.Read (file);
if (null == image)
continue;
try
{
using (var output = File.Create (target_name))
m_image_format.Write (output, image);
}
catch // delete destination file on conversion failure
{
File.Delete (target_name);
throw;
}
}
if ("image" == entry.Type)
ConvertImage (filename);
else if ("audio" == entry.Type)
ConvertAudio (filename);
}
catch (Exception X)
{
if (!IgnoreErrors)
throw;
m_pending_error = X;
m_failed.Add (Tuple.Create (Path.GetFileName (filename), X.Message));
}
}
}
@@ -140,6 +150,67 @@ namespace GARbro.GUI
}
}
public static readonly HashSet<string> CommonAudioFormats = new HashSet<string> { "wav", "mp3", "ogg" };
public static readonly AudioFormat WavFormat = FormatCatalog.Instance.AudioFormats.First (f => f.Tag == "WAV");
void ConvertAudio (string filename)
{
using (var file = File.OpenRead (filename))
using (var input = AudioFormat.Read (file))
{
if (null == input)
return;
var source_ext = Path.GetExtension (filename).TrimStart ('.').ToLowerInvariant();
string source_format = input.SourceFormat;
if (CommonAudioFormats.Contains (source_format))
{
if (source_ext == source_format)
return;
string output_name = Path.ChangeExtension (filename, source_format);
using (var output = File.Create (output_name))
{
input.Source.Position = 0;
input.Source.CopyTo (output);
}
}
else
{
if (source_ext == "wav")
return;
string output_name = Path.ChangeExtension (filename, "wav");
using (var output = File.Create (output_name))
WavFormat.Write (input, output);
}
}
}
void ConvertImage (string filename)
{
string target_ext = m_image_format.Extensions.First();
string target_name = Path.ChangeExtension (filename, target_ext);
if (filename == target_name)
return;
string source_ext = Path.GetExtension (filename).TrimStart ('.').ToLowerInvariant();
if (m_image_format.Extensions.Any (ext => ext == source_ext))
return;
using (var file = File.OpenRead (filename))
{
var image = ImageFormat.Read (file);
if (null == image)
return;
try
{
using (var output = File.Create (target_name))
m_image_format.Write (output, image);
}
catch // delete destination file on conversion failure
{
File.Delete (target_name);
throw;
}
}
}
void OnConvertComplete (object sender, RunWorkerCompletedEventArgs e)
{
m_main.ResumeWatchDirectoryChanges();
@@ -149,7 +220,7 @@ namespace GARbro.GUI
if (m_pending_error is OperationCanceledException)
m_main.SetStatusText (m_pending_error.Message);
else
m_main.PopupError (m_pending_error.Message, guiStrings.TextImageConvertError);
m_main.PopupError (m_pending_error.Message, guiStrings.TextMediaConvertError);
}
m_main.Activate();
m_main.RefreshView();

View File

@@ -31,6 +31,7 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Ookii.Dialogs.Wpf;
using GameRes;
using GARbro.GUI.Strings;
@@ -104,6 +105,7 @@ namespace GARbro.GUI
private bool m_skip_images = false;
private bool m_skip_script = false;
private bool m_skip_audio = false;
private bool m_adjust_image_offset = false;
private bool m_convert_audio;
private ImageFormat m_image_format;
private int m_extract_count;
@@ -111,8 +113,6 @@ namespace GARbro.GUI
private ProgressDialog m_progress_dialog;
private Exception m_pending_error;
public static readonly HashSet<string> CommonAudioFormats = new HashSet<string> { "wav", "mp3", "ogg" };
public bool IsActive { get { return m_extract_in_progress; } }
public GarExtract (MainWindow parent, string source)
@@ -290,13 +290,13 @@ namespace GARbro.GUI
}
}
static void ExtractImage (ArcFile arc, Entry entry, ImageFormat target_format)
void ExtractImage (ArcFile arc, Entry entry, ImageFormat target_format)
{
using (var file = arc.OpenSeekableEntry (entry))
{
var src_format = ImageFormat.FindFormat (file);
if (null == src_format)
throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpret, entry.Name));
throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpretImage, entry.Name));
file.Position = 0;
string target_ext = target_format.Extensions.First();
string outname = Path.ChangeExtension (entry.Name, target_ext);
@@ -309,6 +309,10 @@ namespace GARbro.GUI
}
ImageData image = src_format.Item1.Read (file, src_format.Item2);
Trace.WriteLine (string.Format ("{0} => {1}", entry.Name, outname), "ExtractImage");
if (m_adjust_image_offset)
{
image = AdjustImageOffset (image);
}
using (var outfile = ArchiveFormat.CreateFile (outname))
{
target_format.Write (outfile, image);
@@ -316,13 +320,42 @@ namespace GARbro.GUI
}
}
static ImageData AdjustImageOffset (ImageData image)
{
if (0 == image.OffsetX && 0 == image.OffsetY)
return image;
int width = (int)image.Width + image.OffsetX;
int height = (int)image.Height + image.OffsetY;
if (width <= 0 || height <= 0)
return image;
int x = Math.Max (image.OffsetX, 0);
int y = Math.Max (image.OffsetY, 0);
int src_x = image.OffsetX < 0 ? Math.Abs (image.OffsetX) : 0;
int src_y = image.OffsetY < 0 ? Math.Abs (image.OffsetY) : 0;
int src_stride = (int)image.Width * (image.BPP+7) / 8;
int dst_stride = width * (image.BPP+7) / 8;
var pixels = new byte[height*dst_stride];
int offset = y * dst_stride + x * image.BPP / 8;
Int32Rect rect = new Int32Rect (src_x, src_y, (int)image.Width - src_x, 1);
for (int row = src_y; row < image.Height; ++row)
{
rect.Y = row;
image.Bitmap.CopyPixels (rect, pixels, src_stride, offset);
offset += dst_stride;
}
var bitmap = BitmapSource.Create (width, height, image.Bitmap.DpiX, image.Bitmap.DpiY,
image.Bitmap.Format, image.Bitmap.Palette, pixels, dst_stride);
return new ImageData (bitmap);
}
static void ExtractAudio (ArcFile arc, Entry entry)
{
using (var file = arc.OpenEntry (entry))
using (var sound = AudioFormat.Read (file))
{
if (null == sound)
throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpret, entry.Name));
throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpretAudio, entry.Name));
ConvertAudio (entry.Name, sound);
}
}
@@ -330,7 +363,7 @@ namespace GARbro.GUI
public static void ConvertAudio (string entry_name, SoundInput input)
{
string source_format = input.SourceFormat;
if (CommonAudioFormats.Contains (source_format))
if (GarConvertMedia.CommonAudioFormats.Contains (source_format))
{
string output_name = Path.ChangeExtension (entry_name, source_format);
using (var output = ArchiveFormat.CreateFile (output_name))
@@ -341,10 +374,9 @@ namespace GARbro.GUI
}
else
{
var wav_format = FormatCatalog.Instance.AudioFormats.First (f => f.Tag == "WAV");
string output_name = Path.ChangeExtension (entry_name, "wav");
using (var output = ArchiveFormat.CreateFile (output_name))
wav_format.Write (input, output);
GarConvertMedia.WavFormat.Write (input, output);
}
}

View File

@@ -200,15 +200,7 @@ namespace GARbro.GUI
}
else
{
var file = preview.Archive.OpenEntry (preview.Entry);
if (file.CanSeek)
return file;
using (file)
{
var memory = new MemoryStream();
file.CopyTo (memory);
return memory;
}
return preview.Archive.OpenSeekableEntry (preview.Entry);
}
}

View File

@@ -94,7 +94,7 @@
Command="{x:Static local:Commands.Refresh}"/>
<MenuItem Header="{x:Static s:guiStrings.CtxMenuConvert}" InputGestureText="F6"
Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToCollapsedVisibilityConverter}}"
Command="{x:Static local:Commands.ConvertImage}" />
Command="{x:Static local:Commands.ConvertMedia}" />
</ContextMenu>
</Window.Resources>
@@ -107,7 +107,7 @@
<MenuItem Header="{x:Static s:guiStrings.CtxMenuCreate}" InputGestureText="F3"
Command="{x:Static local:Commands.CreateArchive}" />
<MenuItem Header="{x:Static s:guiStrings.CtxMenuConvert}" InputGestureText="F6"
Command="{x:Static local:Commands.ConvertImage}" />
Command="{x:Static local:Commands.ConvertMedia}" />
<MenuItem Header="{x:Static s:guiStrings.MenuRecent}" x:Name="RecentFilesMenu">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
@@ -265,7 +265,7 @@
<KeyBinding Gesture="F2" Command="{x:Static local:Commands.RenameItem}"/>
<KeyBinding Gesture="F4" Command="{x:Static local:Commands.ExtractItem}"/>
<KeyBinding Gesture="F5" Command="{x:Static local:Commands.Refresh}"/>
<KeyBinding Gesture="F6" Command="{x:Static local:Commands.ConvertImage}"/>
<KeyBinding Gesture="F6" Command="{x:Static local:Commands.ConvertMedia}"/>
<KeyBinding Gesture="Delete" Command="{x:Static local:Commands.DeleteItem}"/>
<KeyBinding Gesture="Backspace" Command="{x:Static local:Commands.GoBack}"/>
<MouseBinding Gesture="LeftDoubleClick" Command="{x:Static local:Commands.OpenItem}" />
@@ -366,7 +366,7 @@
<CommandBinding Command="{x:Static local:Commands.DeleteItem}" Executed="DeleteItemExec" CanExecute="CanExecuteOnPhysicalFile" />
<CommandBinding Command="{x:Static local:Commands.RenameItem}" Executed="RenameItemExec" CanExecute="CanExecuteInDirectory" />
<CommandBinding Command="{x:Static local:Commands.ExploreItem}" Executed="ExploreItemExec" CanExecute="CanExecuteInDirectory" />
<CommandBinding Command="{x:Static local:Commands.ConvertImage}" Executed="ConvertImageExec" CanExecute="CanExecuteConvertImage" />
<CommandBinding Command="{x:Static local:Commands.ConvertMedia}" Executed="ConvertMediaExec" CanExecute="CanExecuteConvertMedia" />
<CommandBinding Command="{x:Static local:Commands.SortBy}" Executed="SortByExec" CanExecute="CanExecuteAlways"/>
<CommandBinding Command="{x:Static local:Commands.GoBack}" Executed="GoBackExec" CanExecute="CanExecuteGoBack"/>
<CommandBinding Command="{x:Static local:Commands.GoForward}" Executed="GoForwardExec" CanExecute="CanExecuteGoForward"/>

View File

@@ -120,6 +120,7 @@ namespace GARbro.GUI
/// </summary>
protected override void OnClosing (CancelEventArgs e)
{
AudioDevice = null;
CurrentAudio = null;
SaveSettings();
base.OnClosing (e);
@@ -811,27 +812,40 @@ namespace GARbro.GUI
return File.OpenRead (Path.Combine (vm.Path, entry.Name));
}
WaveOutEvent m_audio;
WaveOutEvent CurrentAudio
WaveOutEvent m_audio_device;
WaveOutEvent AudioDevice
{
get { return m_audio; }
get { return m_audio_device; }
set
{
if (m_audio != null)
m_audio.Dispose();
m_audio = value;
if (m_audio_device != null)
m_audio_device.Dispose();
m_audio_device = value;
}
}
WaveStream m_audio_input;
WaveStream CurrentAudio
{
get { return m_audio_input; }
set
{
if (m_audio_input != null)
m_audio_input.Dispose();
m_audio_input = value;
}
}
private void PlayFile (Entry entry)
{
SoundInput sound = null;
try
{
SetBusyState();
using (var input = OpenEntry (entry))
{
FormatCatalog.Instance.LastError = null;
var sound = AudioFormat.Read (input);
sound = AudioFormat.Read (input);
if (null == sound)
{
if (null != FormatCatalog.Instance.LastError)
@@ -839,30 +853,34 @@ namespace GARbro.GUI
return;
}
if (CurrentAudio != null)
if (AudioDevice != null)
{
CurrentAudio.PlaybackStopped -= OnPlaybackStopped;
CurrentAudio = null;
AudioDevice.PlaybackStopped -= OnPlaybackStopped;
AudioDevice = null;
}
var wave_stream = new WaveStreamImpl (sound);
CurrentAudio = new WaveOutEvent();
CurrentAudio.Init (wave_stream);
CurrentAudio.PlaybackStopped += OnPlaybackStopped;
CurrentAudio.Play();
var fmt = wave_stream.WaveFormat;
SetResourceText (string.Format ("Playing {0} / {2}bps / {1}Hz", entry.Name,
fmt.SampleRate, sound.SourceBitrate / 1000));
CurrentAudio = new WaveStreamImpl (sound);
AudioDevice = new WaveOutEvent();
AudioDevice.Init (CurrentAudio);
AudioDevice.PlaybackStopped += OnPlaybackStopped;
AudioDevice.Play();
var fmt = CurrentAudio.WaveFormat;
SetResourceText (string.Format ("Playing {0} / {3} / {2}bps / {1}Hz", entry.Name,
fmt.SampleRate, sound.SourceBitrate / 1000,
CurrentAudio.TotalTime.ToString ("m':'ss")));
}
}
catch (Exception X)
{
SetStatusText (X.Message);
if (null != sound)
sound.Dispose();
}
}
private void OnPlaybackStopped (object sender, StoppedEventArgs e)
{
SetResourceText ("");
CurrentAudio = null;
}
/// <summary>
@@ -930,6 +948,7 @@ namespace GARbro.GUI
try
{
m_app.ResetCache();
ResetPreviewPane();
if (!items.Skip (1).Any()) // items.Count() == 1
{
string item_name = Path.Combine (CurrentPath, items.First().Name);
@@ -1056,16 +1075,12 @@ namespace GARbro.GUI
e.CanExecute = CurrentDirectory.SelectedIndex != -1;
}
private void CanExecuteConvertImage (object sender, CanExecuteRoutedEventArgs e)
private void CanExecuteConvertMedia (object sender, CanExecuteRoutedEventArgs e)
{
if (CurrentDirectory.SelectedItems.Count > 1)
if (CurrentDirectory.SelectedItems.Count >= 1)
{
e.CanExecute = !ViewModel.IsArchive;
}
else
{
CanExecuteOnImage (sender, e);
}
}
private void CanExecuteOnImage (object sender, CanExecuteRoutedEventArgs e)
@@ -1283,7 +1298,7 @@ namespace GARbro.GUI
public static readonly RoutedCommand DeleteItem = new RoutedCommand();
public static readonly RoutedCommand RenameItem = new RoutedCommand();
public static readonly RoutedCommand ExploreItem = new RoutedCommand();
public static readonly RoutedCommand ConvertImage = new RoutedCommand();
public static readonly RoutedCommand ConvertMedia = new RoutedCommand();
public static readonly RoutedCommand Refresh = new RoutedCommand();
public static readonly RoutedCommand Browse = new RoutedCommand();
public static readonly RoutedCommand FitWindow = new RoutedCommand();

View File

@@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.1.2.20")]
[assembly: AssemblyFileVersion ("1.1.2.20")]
[assembly: AssemblyVersion ("1.1.3.21")]
[assembly: AssemblyFileVersion ("1.1.3.21")]

View File

@@ -333,5 +333,17 @@ namespace GARbro.GUI.Properties {
this["appExtractAudio"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool appIgnoreConversionErrors {
get {
return ((bool)(this["appIgnoreConversionErrors"]));
}
set {
this["appIgnoreConversionErrors"] = value;
}
}
}
}

View File

@@ -80,5 +80,8 @@
<Setting Name="appExtractAudio" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="appIgnoreConversionErrors" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -106,7 +106,7 @@ namespace GARbro.GUI.Strings {
}
/// <summary>
/// Looks up a localized string similar to Convert images....
/// Looks up a localized string similar to Convert multimedia....
/// </summary>
public static string CtxMenuConvert {
get {
@@ -304,7 +304,7 @@ namespace GARbro.GUI.Strings {
}
/// <summary>
/// Looks up a localized string similar to Choose destination format.
/// Looks up a localized string similar to Choose destination format for images.
/// </summary>
public static string LabelDestinationFormat {
get {
@@ -339,6 +339,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Skip incovertible files..
/// </summary>
public static string LabelSkipFailures {
get {
return ResourceManager.GetString("LabelSkipFailures", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to About Game Resource browser.
/// </summary>
@@ -457,11 +466,11 @@ namespace GARbro.GUI.Strings {
}
/// <summary>
/// Looks up a localized string similar to Converting image {0}.
/// Looks up a localized string similar to Converting file {0}.
/// </summary>
public static string MsgConvertingImage {
public static string MsgConvertingFile {
get {
return ResourceManager.GetString("MsgConvertingImage", resourceCulture);
return ResourceManager.GetString("MsgConvertingFile", resourceCulture);
}
}
@@ -627,6 +636,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to No media files selected..
/// </summary>
public static string MsgNoMediaFiles {
get {
return ResourceManager.GetString("MsgNoMediaFiles", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to File {0}
///already exists.
@@ -648,12 +666,21 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to unable to interpret audio format.
/// </summary>
public static string MsgUnableInterpretAudio {
get {
return ResourceManager.GetString("MsgUnableInterpretAudio", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to unable to interpret image format.
/// </summary>
public static string MsgUnableInterpret {
public static string MsgUnableInterpretImage {
get {
return ResourceManager.GetString("MsgUnableInterpret", resourceCulture);
return ResourceManager.GetString("MsgUnableInterpretImage", resourceCulture);
}
}
@@ -747,6 +774,15 @@ namespace GARbro.GUI.Strings {
}
}
/// <summary>
/// Looks up a localized string similar to Audio will be converted to either WAV, MP3 or OGG..
/// </summary>
public static string TextAudioConversion {
get {
return ResourceManager.GetString("TextAudioConversion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Choose archive location.
/// </summary>
@@ -784,11 +820,11 @@ namespace GARbro.GUI.Strings {
}
/// <summary>
/// Looks up a localized string similar to Convert images.
/// Looks up a localized string similar to Media conversion.
/// </summary>
public static string TextConvertImages {
public static string TextConvertMedia {
get {
return ResourceManager.GetString("TextConvertImages", resourceCulture);
return ResourceManager.GetString("TextConvertMedia", resourceCulture);
}
}
@@ -865,11 +901,11 @@ namespace GARbro.GUI.Strings {
}
/// <summary>
/// Looks up a localized string similar to Image conversion error.
/// Looks up a localized string similar to Multimedia conversion error.
/// </summary>
public static string TextImageConvertError {
public static string TextMediaConvertError {
get {
return ResourceManager.GetString("TextImageConvertError", resourceCulture);
return ResourceManager.GetString("TextMediaConvertError", resourceCulture);
}
}

View File

@@ -240,7 +240,7 @@
<data name="MsgReady" xml:space="preserve">
<value>Ready</value>
</data>
<data name="MsgUnableInterpret" xml:space="preserve">
<data name="MsgUnableInterpretImage" xml:space="preserve">
<value>unable to interpret image format</value>
</data>
<data name="MsgUnknownFormat" xml:space="preserve">
@@ -379,22 +379,22 @@ Overwrite?</value>
<value>_View</value>
</data>
<data name="CtxMenuConvert" xml:space="preserve">
<value>Convert images...</value>
<value>Convert multimedia...</value>
</data>
<data name="ButtonConvert" xml:space="preserve">
<value>Convert</value>
</data>
<data name="LabelDestinationFormat" xml:space="preserve">
<value>Choose destination format</value>
<value>Choose destination format for images</value>
</data>
<data name="TextConvertImages" xml:space="preserve">
<value>Convert images</value>
<data name="TextConvertMedia" xml:space="preserve">
<value>Media conversion</value>
</data>
<data name="MsgConvertingImage" xml:space="preserve">
<value>Converting image {0}</value>
<data name="MsgConvertingFile" xml:space="preserve">
<value>Converting file {0}</value>
</data>
<data name="TextImageConvertError" xml:space="preserve">
<value>Image conversion error</value>
<data name="TextMediaConvertError" xml:space="preserve">
<value>Multimedia conversion error</value>
</data>
<data name="LabelEncoding" xml:space="preserve">
<value>Encoding</value>
@@ -414,4 +414,16 @@ Overwrite?</value>
<data name="TextAboutLicense" xml:space="preserve">
<value>License</value>
</data>
<data name="MsgUnableInterpretAudio" xml:space="preserve">
<value>unable to interpret audio format</value>
</data>
<data name="MsgNoMediaFiles" xml:space="preserve">
<value>No media files selected.</value>
</data>
<data name="TextAudioConversion" xml:space="preserve">
<value>Audio will be converted to either WAV, MP3 or OGG.</value>
</data>
<data name="LabelSkipFailures" xml:space="preserve">
<value>Skip incovertible files.</value>
</data>
</root>

View File

@@ -234,7 +234,7 @@
<data name="MsgReady" xml:space="preserve">
<value>Готов</value>
</data>
<data name="MsgUnableInterpret" xml:space="preserve">
<data name="MsgUnableInterpretImage" xml:space="preserve">
<value>не удалось интерпретировать формат изображения</value>
</data>
<data name="MsgUnknownFormat" xml:space="preserve">
@@ -394,22 +394,22 @@
<value>Просмотр</value>
</data>
<data name="CtxMenuConvert" xml:space="preserve">
<value>Конверсия изображений...</value>
<value>Конверсия мультимедиа...</value>
</data>
<data name="ButtonConvert" xml:space="preserve">
<value>Преобразовать</value>
</data>
<data name="LabelDestinationFormat" xml:space="preserve">
<value>Формат преобразования</value>
<value>Формат преобразования изображений</value>
</data>
<data name="TextConvertImages" xml:space="preserve">
<value>Преобразовать изображения</value>
<data name="TextConvertMedia" xml:space="preserve">
<value>Преобразование мультимедиа</value>
</data>
<data name="MsgConvertingImage" xml:space="preserve">
<data name="MsgConvertingFile" xml:space="preserve">
<value>Преобразование файла {0}</value>
</data>
<data name="TextImageConvertError" xml:space="preserve">
<value>Ошибка конверсии изображения</value>
<data name="TextMediaConvertError" xml:space="preserve">
<value>Ошибка конверсии мультимедиа</value>
</data>
<data name="LabelEncoding" xml:space="preserve">
<value>Кодировка</value>
@@ -432,4 +432,16 @@
<data name="Type_audio" xml:space="preserve">
<value>звук</value>
</data>
<data name="MsgUnableInterpretAudio" xml:space="preserve">
<value>не удалось интерпретировать формат аудио</value>
</data>
<data name="LabelSkipFailures" xml:space="preserve">
<value>Пропускать файлы, не поддавшиеся конверсии.</value>
</data>
<data name="MsgNoMediaFiles" xml:space="preserve">
<value>Среди выбранных файлов нет мультиемедиа.</value>
</data>
<data name="TextAudioConversion" xml:space="preserve">
<value>Аудио-файлы будут преобразованы в WAV, MP3 или OGG.</value>
</data>
</root>

View File

@@ -39,17 +39,6 @@ using GARbro.GUI.Strings;
namespace GARbro.GUI
{
public class SubDirEntry : GameRes.Entry
{
public override string Type { get { return "directory"; } }
public SubDirEntry (string name)
{
Name = name;
Size = 0;
}
}
public class DirectoryViewModel : ObservableCollection<EntryViewModel>
{
public string Path { get; private set; }

View File

@@ -19,15 +19,18 @@ tr.odd td { background-color: #eee }
<tr><td>*.bip</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>data.ami</td><td><tt>AMI</tt></td><td>Yes</td><td>-</td><td>Muv-Luv Amaterasu Translation data files</td></tr>
<tr><td>*.arc</td><td><tt>PackFile</tt></td><td>No</td><td>BGI/Ethornell</td><td>Chou Dengeki Stryker</td></tr>
<tr class="odd"><td>*</td><td>-<br/><tt>SM2MPX10</tt></td><td>No</td><td rowspan="3">DRS</td><td rowspan="3">
<tr class="odd"><td>*</td><td>-<br/><tt>SM2MPX10</tt></td><td>No</td><td rowspan="4">DRS</td><td rowspan="4">
Anata no Osanazuma<br/>
Ecchi na Bunny-san wa Kirai?<br/>
Kana ~Imouto~<br/>
Natsu no Hitoshizuku<br/>
Private Nurse<br/>
Sensei 2<br/>
Shoujo Settai<br/>
</td></tr>
<tr class="odd"><td>*.ggd</td><td><tt>\xB9\xAA\xB3\xB3</tt><br/><tt>\xAB\xAD\xAA\xBA</tt><br/><tt>\xB7\xB6\xB8\xB7</tt><br/><tt>\xCD\xCA\xC9\xB8</tt></td><td>Yes</td></tr>
<tr class="odd"><td>*.gg1<br/>*.gg2<br/>*.gg3</td><td><tt>GGA00000</tt></td><td>No</td></tr>
<tr class="odd"><td>*.gg1<br/>*.gg2<br/>*.gg3<br/>*.gg0</td><td><tt>GGA00000</tt></td><td>No</td></tr>
<tr class="odd"><td>*.ggp</td><td><tt>GGPFAIKE</tt></td><td>No</td></tr>
<tr><td>*.bin</td><td><tt>ACPXPK01</tt></td><td>No</td><td>Unison Shift</td><td>Wasurenagusa ~Forget-me-Not~</td></tr>
<tr class="odd"><td>*.gsp</td><td>-</td><td>No</td><td rowspan="2">Black Rainbow</td><td rowspan="2">Saimin Gakuen</td></tr>
<tr class="odd"><td>*.bmz</td><td><tt>ZLC3</tt></td><td>Yes</td></tr>
@@ -136,17 +139,20 @@ Nikutai Ten'i<br/>
</td></tr>
<tr class="odd"><td>*.grd</td><td><tt>CMP_</tt></td><td>No</td></tr>
<tr><td>*.dat<br/>*.arc</td><td>-<br/><tt>M2TYPE</tt></td><td>No</td><td rowspan="2">FFA System/G-SYS</td><td rowspan="2">
Onsen Kankou Yukemuri Chijou<br/>
Dokusen Kango<br/>
Inran OL Sawatari Tokiko<br/>
Onsen Kankou Yukemuri Chijou<br/>
</td></tr>
<tr><td>*.pt1</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>*.pak</td><td><tt>DataPack5</tt><br/><tt>GsPack4</tt></td><td>No</td><td rowspan="2">Root</td><td rowspan="2">Para-Sol</td></tr>
<tr class="odd"><td>*</td><td><tt>\x00\x00\x04\x00</tt></td><td>No</td></tr>
<tr><td>*.war</td><td><tt>WARC 1.7</tt></td><td>No</td><td rowspan="2">Shiina Rio</td><td rowspan="2">
Helter Skelter<br/>
<tr><td>*.war</td><td><tt>WARC 1.7</tt></td><td>No</td><td rowspan="3">Shiina Rio</td><td rowspan="3">
Classmate no Okaa-san<br/>
Helter Skelter<br/>
Mahou Shoujo no Taisetsu na Koto<br/>
</td></tr>
<tr><td>*.s25</td><td><tt>S25</tt></td><td>No</td></tr>
<tr><td>*.ogv</td><td><tt>OGV</tt></td><td>No</td></tr>
<tr class="odd"><td>*</td><td><tt>ARC2</tt><br/><tt>ARC1</tt></td><td>No</td><td>AST</td><td>Jokyoushi wo Kurau</td></tr>
<tr><td>*.dat</td><td>-</td><td>No</td><td>Ail</td><td>Ragna☆彡Science</td></tr>
<tr class="odd"><td>*.lpk</td><td><tt>LPK1</tt></td><td>No</td><td rowspan="2">Lucifen</td><td rowspan="2">
@@ -185,8 +191,20 @@ Yatohime Zankikou<br/>
Onna Kyoushi
</td></tr>
<tr class="odd"><td>*.bg_<br/>*.cg_</td><td><tt>AP</tt></td><td>Yes</td></tr>
<tr><td>*.dpk</td><td>DPK</td><td>No</td><td rowspan="2">DAC</td><td rowspan="2">Yumemiru Tsuki no Lunalutia</td></tr>
<tr><td>*.dgc</td><td>DGC</td><td>No</td></tr>
<tr><td>*.dpk</td><td><tt>DPK</tt></td><td>No</td><td rowspan="2">DAC</td><td rowspan="2">Yumemiru Tsuki no Lunalutia</td></tr>
<tr><td>*.dgc</td><td><tt>DGC</tt></td><td>No</td></tr>
<tr class="odd"><td>*.pck</td><td>-</td><td>No</td><td rowspan="5">Crowd</td><td rowspan="5">
X Change R<br/>
X Change<br/>
X Change 2<br/>
</td></tr>
<tr class="odd"><td>*.cwp</td><td><tt>CWDP</tt></td><td>Yes</td></tr>
<tr class="odd"><td>*.cwd</td><td><tt>cwd</tt></td><td>No</td></tr>
<tr class="odd"><td>*.eog</td><td><tt>CRM</tt></td><td>No</td></tr>
<tr class="odd"><td>*.zbm<br/>*.cwl</td><td><tt>SZDD</tt></td><td>No</td></tr>
<tr><td>*.pack</td><td><tt>FilePackVer2.0</tt></td><td>No</td><td>QLIE</td><td>
Mehime no Toriko<br/>
</td></tr>
</table>
<p><a name="note-1">[1]</a> Non-encrypted only</p>
</body>