Merge remote-tracking branch 'refs/remotes/origin/master' into binary-stream

This commit is contained in:
morkt 2016-10-15 05:53:19 +04:00
commit 0b96ef8f77
22 changed files with 1023 additions and 249 deletions

View File

@ -0,0 +1,97 @@
//! \file ArcAdvSys3.cs
//! \date Thu Oct 13 03:13:16 2016
//! \brief AdvSys3 resource archive.
//
// Copyright (C) 2016 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
namespace GameRes.Formats.AdvSys
{
[Export(typeof(ArchiveFormat))]
public class ArcOpener : ArchiveFormat
{
public override string Tag { get { return "ARC/ADVSYS3"; } }
public override string Description { get { return "AdvSys3 engine resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public ArcOpener ()
{
Extensions = new string[] { "dat" };
}
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)
|| !Path.GetFileName (file.Name).StartsWith ("arc", StringComparison.InvariantCultureIgnoreCase))
return null;
long current_offset = 0;
var dir = new List<Entry>();
while (current_offset < file.MaxOffset)
{
uint size = file.View.ReadUInt32 (current_offset);
if (0 == size)
break;
uint name_length = file.View.ReadUInt16 (current_offset+8);
if (0 == name_length || name_length > 0x100)
return null;
var name = file.View.ReadString (current_offset+10, name_length);
if (0 == name.Length)
return null;
current_offset += 10 + name_length;
if (current_offset + size > file.MaxOffset)
return null;
var entry = new Entry {
Name = name,
Offset = current_offset,
Size = size,
};
uint signature = file.View.ReadUInt32 (current_offset);
if (file.View.AsciiEqual (current_offset+4, "GWD"))
{
entry.Type = "image";
entry.Name = Path.ChangeExtension (entry.Name, "gwd");
}
else
{
var res = AutoEntry.DetectFileType (signature);
if (res != null)
{
entry.Type = res.Type;
entry.Name = Path.ChangeExtension (entry.Name, res.Extensions.FirstOrDefault());
}
}
dir.Add (entry);
current_offset += size;
}
if (0 == dir.Count)
return null;
return new ArcFile (file, this, dir);
}
}
}

View File

@ -0,0 +1,254 @@
//! \file ImageGWD.cs
//! \date Thu Oct 13 03:47:44 2016
//! \brief AdvSys3 engine image format.
//
// Copyright (C) 2016 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using GameRes.Utility;
namespace GameRes.Formats.AdvSys
{
internal class GwdMetaData : ImageMetaData
{
public uint DataSize;
}
[Export(typeof(ImageFormat))]
public class GwdFormat : ImageFormat
{
public override string Tag { get { return "GWD"; } }
public override string Description { get { return "AdvSys3 engine image format"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[12];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
if (!Binary.AsciiEqual (header, 4, "GWD"))
return null;
return new GwdMetaData
{
Width = BigEndian.ToUInt16 (header, 7),
Height = BigEndian.ToUInt16 (header, 9),
BPP = header[11],
DataSize = LittleEndian.ToUInt32 (header, 0),
};
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
PixelFormat format = 24 == info.BPP ? PixelFormats.Bgr24 : PixelFormats.Gray8;
byte[] image;
using (var reader = new GwdReader (stream, info))
{
image = reader.Unpack();
}
var meta = (GwdMetaData)info;
stream.Position = 4 + meta.DataSize;
if (24 == info.BPP && 1 == stream.ReadByte())
{
using (var alpha_stream = new StreamRegion (stream, stream.Position, true))
{
var alpha_info = ReadMetaData (alpha_stream) as GwdMetaData;
if (null != alpha_info && 8 == alpha_info.BPP
&& alpha_info.Width == info.Width && alpha_info.Height == info.Height)
{
alpha_stream.Position = 0;
using (var reader = new GwdReader (alpha_stream, alpha_info))
{
var alpha = reader.Unpack();
var pixels = new byte[info.Width * info.Height * 4];
int src = 0;
int dst = 0;
int a = 0;
while (dst < pixels.Length)
{
pixels[dst++] = image[src++];
pixels[dst++] = image[src++];
pixels[dst++] = image[src++];
pixels[dst++] = (byte)~alpha[a++];
}
image = pixels;
format = PixelFormats.Bgra32;
}
}
}
}
return ImageData.Create (info, format, null, image);
}
public override void Write (Stream file, ImageData image)
{
throw new NotImplementedException ("GwdFormat.Write not implemented");
}
}
internal sealed class GwdReader : IDisposable
{
MsbBitStream m_input;
int m_width;
int m_height;
int m_bpp;
int m_stride;
byte[] m_output;
byte[] m_line_buf;
public byte[] Pixels { get { return m_output; } }
public int InputSize { get; private set; }
public GwdReader (Stream input, ImageMetaData info)
{
m_bpp = info.BPP;
if (m_bpp != 8 && m_bpp != 24)
throw new InvalidFormatException();
m_input = new MsbBitStream (input, true);
m_width = (int)info.Width;
m_height = (int)info.Height;
m_stride = m_width * m_bpp / 8;
m_output = new byte[m_stride * m_height];
m_line_buf = new byte[m_width];
}
public byte[] Unpack ()
{
m_input.Input.Position = 12;
if (8 == m_bpp)
Read8bpp();
else
Read24bpp();
return m_output;
}
void Read8bpp ()
{
int dst = 0;
for (int y = 0; y < m_height; ++y)
{
FillLine();
Buffer.BlockCopy (m_line_buf, 0, m_output, dst, m_width);
dst += m_width;
}
}
void Read24bpp ()
{
int dst = 0;
for (int y = 0; y < m_height; ++y)
{
for (int c = 0; c < 3; ++c)
{
FillLine();
int p = dst + c;
for (int src = 0; src < m_width; ++src)
{
m_output[p] = m_line_buf[src];
p += 3;
}
}
dst += m_stride;
}
}
void FillLine ()
{
for (int dst = 0; dst < m_width; )
{
int length = m_input.GetBits (3);
if (-1 == length)
throw new EndOfStreamException();
int count = GetCount() + 1;
if (length != 0)
{
for (int j = 0; j < count; ++j)
m_line_buf[dst++] = (byte)m_input.GetBits (length+1);
}
else
{
for (int j = 0; j < count; ++j)
m_line_buf[dst++] = 0;
}
}
for (int i = 1; i < m_width; ++i)
{
m_line_buf[i] = DeltaTable[ m_line_buf[i], m_line_buf[i-1] ];
}
}
int GetCount ()
{
int n = 1;
while (0 == m_input.GetNextBit())
++n;
return m_input.GetBits (n) + (1 << n) - 2;
}
static readonly byte[,] DeltaTable = InitTable();
static byte[,] InitTable ()
{
var table = new byte[0x100, 0x100];
for (int j = 0; j < 0x100; ++j)
for (int i = 0; i < 0x100; ++i)
{
int prev = i;
if (i >= 0x80)
prev = 0xFF - i;
int v;
if (2 * prev < j)
{
v = j;
}
else if (0 != (j & 1))
{
v = prev + ((j + 1) >> 1);
}
else
{
v = prev - (j >> 1);
}
if (i >= 0x80)
table[j,i] = (byte)(0xFF - v);
else
table[j,i] = (byte)v;
}
return table;
}
#region IDisposable Members
bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
m_input.Dispose();
_disposed = true;
}
}
#endregion
}
}

View File

@ -73,6 +73,8 @@
<Compile Include="Abel\ArcARC.cs" /> <Compile Include="Abel\ArcARC.cs" />
<Compile Include="Abel\ImageGPS.cs" /> <Compile Include="Abel\ImageGPS.cs" />
<Compile Include="Actgs\ArcDAT.cs" /> <Compile Include="Actgs\ArcDAT.cs" />
<Compile Include="AdvSys\ArcAdvSys3.cs" />
<Compile Include="AdvSys\ImageGWD.cs" />
<Compile Include="AliceSoft\ArcAFA.cs" /> <Compile Include="AliceSoft\ArcAFA.cs" />
<Compile Include="AliceSoft\ArcALK.cs" /> <Compile Include="AliceSoft\ArcALK.cs" />
<Compile Include="AliceSoft\ImageAJP.cs" /> <Compile Include="AliceSoft\ImageAJP.cs" />
@ -92,6 +94,8 @@
<Compile Include="CaramelBox\ArcARC3.cs" /> <Compile Include="CaramelBox\ArcARC3.cs" />
<Compile Include="CaramelBox\ArcARC4.cs" /> <Compile Include="CaramelBox\ArcARC4.cs" />
<Compile Include="CaramelBox\ImageFCB.cs" /> <Compile Include="CaramelBox\ImageFCB.cs" />
<Compile Include="Circus\ArcCRM.cs" />
<Compile Include="Circus\ImageCRXD.cs" />
<Compile Include="Cmvs\ArcPBZ.cs" /> <Compile Include="Cmvs\ArcPBZ.cs" />
<Compile Include="Cmvs\AudioMV.cs" /> <Compile Include="Cmvs\AudioMV.cs" />
<Compile Include="Cmvs\ImagePSB.cs" /> <Compile Include="Cmvs\ImagePSB.cs" />
@ -107,6 +111,9 @@
</Compile> </Compile>
<Compile Include="DDSystem\ArcDDP.cs" /> <Compile Include="DDSystem\ArcDDP.cs" />
<Compile Include="Dogenzaka\ArcBIN.cs" /> <Compile Include="Dogenzaka\ArcBIN.cs" />
<Compile Include="Eagls\WidgetEAGLS.xaml.cs">
<DependentUpon>WidgetEAGLS.xaml</DependentUpon>
</Compile>
<Compile Include="ExHibit\WidgetGYU.xaml.cs"> <Compile Include="ExHibit\WidgetGYU.xaml.cs">
<DependentUpon>WidgetGYU.xaml</DependentUpon> <DependentUpon>WidgetGYU.xaml</DependentUpon>
</Compile> </Compile>
@ -667,6 +674,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Eagls\WidgetEAGLS.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ExHibit\WidgetGYU.xaml"> <Page Include="ExHibit\WidgetGYU.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@ -0,0 +1,98 @@
//! \file ArcCRM.cs
//! \date Wed Oct 12 14:15:11 2016
//! \brief Circus resource archive.
//
// Copyright (C) 2016 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Circus
{
internal class CrmArchive : ArcFile
{
readonly IDictionary<uint, Entry> OffsetMap;
public CrmArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, IDictionary<uint, Entry> offset_map)
: base (arc, impl, dir)
{
OffsetMap = offset_map;
}
internal Stream OpenByOffset (uint offset)
{
Entry entry;
if (!OffsetMap.TryGetValue (offset, out entry))
throw new FileNotFoundException();
return OpenEntry (entry);
}
}
[Export(typeof(ArchiveFormat))]
public class CrmOpener : ArchiveFormat
{
public override string Tag { get { return "CRM"; } }
public override string Description { get { return "Circus image archive"; } }
public override uint Signature { get { return 0x42585243; } } // 'CRXB'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
return null;
var offset_map = new SortedDictionary<uint, Entry>();
int index_offset = 0x10;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
uint offset = file.View.ReadUInt32 (index_offset);
var name = file.View.ReadString (index_offset+8, 0x18);
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = offset;
dir.Add (entry);
offset_map[offset] = entry;
index_offset += 0x20;
}
using (var iterator = offset_map.GetEnumerator())
{
if (iterator.MoveNext())
{
for (;;)
{
var entry = iterator.Current.Value;
if (!iterator.MoveNext())
{
entry.Size = (uint)(file.MaxOffset - entry.Offset);
break;
}
entry.Size = (uint)(iterator.Current.Key - entry.Offset);
}
}
}
return new CrmArchive (file, this, dir, offset_map);
}
}
}

View File

@ -36,12 +36,12 @@ namespace GameRes.Formats.Circus
public override string Tag { get { return "PCK/CIRCUS"; } } public override string Tag { get { return "PCK/CIRCUS"; } }
public override string Description { get { return "Circus resource archive"; } } public override string Description { get { return "Circus resource archive"; } }
public override uint Signature { get { return 0; } } public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } } public override bool CanWrite { get { return false; } }
public PckOpener () public PckOpener ()
{ {
Extensions = new string[] { "pck" }; Extensions = new string[] { "pck", "dat" };
} }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)

View File

@ -39,46 +39,6 @@ namespace GameRes.Formats.Circus
public override uint Signature { get { return 0x4d435058; } } // 'XPCM' public override uint Signature { get { return 0x4d435058; } } // 'XPCM'
public override SoundInput TryOpen (Stream file) public override SoundInput TryOpen (Stream file)
{
return new PcmInput (file);
}
}
public class PcmInput : SoundInput
{
public override string SourceFormat { get { return "raw"; } }
public override int SourceBitrate
{
get { return (int)Format.AverageBytesPerSecond * 8; }
}
#region IO.Stream methods
public override long Position
{
get { return Source.Position; }
set { Source.Position = value; }
}
public override bool CanSeek { get { return Source.CanSeek; } }
public override long Seek (long offset, SeekOrigin origin)
{
return Source.Seek (offset, origin);
}
public override int Read (byte[] buffer, int offset, int count)
{
return Source.Read (buffer, offset, count);
}
public override int ReadByte ()
{
return Source.ReadByte();
}
#endregion
public PcmInput (Stream file) : base (null)
{ {
file.Position = 4; file.Position = 4;
using (var input = new ArcView.Reader (file)) using (var input = new ArcView.Reader (file))
@ -89,6 +49,12 @@ namespace GameRes.Formats.Circus
int mode = input.ReadInt32(); int mode = input.ReadInt32();
int extra = (mode >> 8) & 0xff; int extra = (mode >> 8) & 0xff;
mode &= 0xff; mode &= 0xff;
if (5 == mode)
{
uint ogg_size = input.ReadUInt32();
var ogg = new StreamRegion (file, 0x10, ogg_size);
return new OggInput (ogg);
}
var format = new WaveFormat(); var format = new WaveFormat();
format.FormatTag = input.ReadUInt16(); format.FormatTag = input.ReadUInt16();
format.Channels = input.ReadUInt16(); format.Channels = input.ReadUInt16();
@ -96,20 +62,21 @@ namespace GameRes.Formats.Circus
format.AverageBytesPerSecond = input.ReadUInt32(); format.AverageBytesPerSecond = input.ReadUInt32();
format.BlockAlign = input.ReadUInt16(); format.BlockAlign = input.ReadUInt16();
format.BitsPerSample = input.ReadUInt16(); format.BitsPerSample = input.ReadUInt16();
this.Format = format; Stream pcm;
this.PcmSize = src_size;
if (0 == mode) if (0 == mode)
{ {
this.Source = new StreamRegion (file, file.Position, src_size); pcm = new StreamRegion (file, file.Position, src_size);
} }
else if (1 == mode || 3 == mode) else if (1 == mode || 3 == mode)
{ {
var decoder = new PcmDecoder (input, src_size, extra, (XpcmCompression)mode); var decoder = new PcmDecoder (input, src_size, extra, (XpcmCompression)mode);
this.Source = new MemoryStream (decoder.Unpack(), 0, src_size); pcm = new MemoryStream (decoder.Unpack(), 0, src_size);
file.Dispose(); file.Dispose();
} }
else else
throw new NotSupportedException ("Not supported Circus PCM audio compression"); throw new NotSupportedException ("Not supported Circus PCM audio compression");
return new RawPcmInput (pcm, format);
} }
} }
} }

View File

@ -26,7 +26,6 @@
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using GameRes.Compression; using GameRes.Compression;
@ -37,6 +36,7 @@ namespace GameRes.Formats.Circus
internal class CrxMetaData : ImageMetaData internal class CrxMetaData : ImageMetaData
{ {
public int Compression; public int Compression;
public int CompressionFlags;
public int Colors; public int Colors;
public int Mode; public int Mode;
} }
@ -53,6 +53,9 @@ namespace GameRes.Formats.Circus
var header = new byte[0x14]; var header = new byte[0x14];
if (header.Length != stream.Read (header, 0, header.Length)) if (header.Length != stream.Read (header, 0, header.Length))
return null; return null;
int compression = LittleEndian.ToUInt16 (header, 0xC);
if (compression < 1 || compression > 3)
return null;
int depth = LittleEndian.ToInt16 (header, 0x10); int depth = LittleEndian.ToInt16 (header, 0x10);
var info = new CrxMetaData var info = new CrxMetaData
{ {
@ -61,12 +64,11 @@ namespace GameRes.Formats.Circus
OffsetX = LittleEndian.ToInt16 (header, 4), OffsetX = LittleEndian.ToInt16 (header, 4),
OffsetY = LittleEndian.ToInt16 (header, 6), OffsetY = LittleEndian.ToInt16 (header, 6),
BPP = 0 == depth ? 24 : 1 == depth ? 32 : 8, BPP = 0 == depth ? 24 : 1 == depth ? 32 : 8,
Compression = LittleEndian.ToUInt16 (header, 0xC), Compression = compression,
CompressionFlags = LittleEndian.ToUInt16 (header, 0xE),
Colors = depth, Colors = depth,
Mode = LittleEndian.ToUInt16 (header, 0x12), Mode = LittleEndian.ToUInt16 (header, 0x12),
}; };
if (info.Compression != 1 && info.Compression != 2)
return null;
return info; return info;
} }
@ -86,13 +88,14 @@ namespace GameRes.Formats.Circus
internal sealed class Reader : IDisposable internal sealed class Reader : IDisposable
{ {
Stream m_input; BinaryReader m_input;
byte[] m_output; byte[] m_output;
int m_width; int m_width;
int m_height; int m_height;
int m_stride; int m_stride;
int m_bpp; int m_bpp;
int m_compression; int m_compression;
int m_flags;
int m_mode; int m_mode;
public byte[] Data { get { return m_output; } } public byte[] Data { get { return m_output; } }
@ -106,6 +109,7 @@ namespace GameRes.Formats.Circus
m_height = (int)info.Height; m_height = (int)info.Height;
m_bpp = info.BPP; m_bpp = info.BPP;
m_compression = info.Compression; m_compression = info.Compression;
m_flags = info.CompressionFlags;
m_mode = info.Mode; m_mode = info.Mode;
switch (m_bpp) switch (m_bpp)
{ {
@ -116,33 +120,49 @@ namespace GameRes.Formats.Circus
} }
m_stride = (m_width * m_bpp / 8 + 3) & ~3; m_stride = (m_width * m_bpp / 8 + 3) & ~3;
m_output = new byte[m_height*m_stride]; m_output = new byte[m_height*m_stride];
m_input = input; m_input = new ArcView.Reader (input);
m_input.Position = 0x14; input.Position = 0x14;
if (8 == m_bpp) if (8 == m_bpp)
ReadPalette (info.Colors); ReadPalette (info.Colors);
} }
private void ReadPalette (int colors) private void ReadPalette (int colors)
{ {
int palette_size = colors * 3; int color_size = 0x102 == colors ? 4 : 3;
if (colors > 0x100)
{
colors = 0x100;
}
int palette_size = colors * color_size;
var palette_data = new byte[palette_size]; var palette_data = new byte[palette_size];
if (palette_size != m_input.Read (palette_data, 0, palette_size)) if (palette_size != m_input.Read (palette_data, 0, palette_size))
throw new InvalidFormatException(); throw new InvalidFormatException();
var palette = new Color[colors]; var palette = new Color[colors];
int color_pos = 0;
for (int i = 0; i < palette.Length; ++i) for (int i = 0; i < palette.Length; ++i)
{ {
byte r = palette_data[i*3]; byte r = palette_data[color_pos];
byte g = palette_data[i*3+1]; byte g = palette_data[color_pos+1];
byte b = palette_data[i*3+2]; byte b = palette_data[color_pos+2];
if (0xff == b && 0 == g && 0xff == r) if (0xff == b && 0 == g && 0xff == r)
g = 0xff; g = 0xff;
palette[i] = Color.FromRgb (r, g, b); palette[i] = Color.FromRgb (r, g, b);
color_pos += color_size;
} }
Palette = new BitmapPalette (palette); Palette = new BitmapPalette (palette);
} }
public void Unpack () public void Unpack (bool is_diff = false)
{ {
if (m_compression >= 3)
{
int count = m_input.ReadInt32();
m_input.BaseStream.Seek (count * 0x10, SeekOrigin.Current);
}
if (0 != (m_flags & 0x10))
{
m_input.ReadInt32(); // compressed_size
}
if (1 == m_compression) if (1 == m_compression)
UnpackV1(); UnpackV1();
else else
@ -164,7 +184,7 @@ namespace GameRes.Formats.Circus
int g = m_output[pixel+2]; int g = m_output[pixel+2];
int r = m_output[pixel+3]; int r = m_output[pixel+3];
if (alpha != alpha_flip) if (!is_diff && alpha != alpha_flip)
{ {
b += (w & 1) + shift; b += (w & 1) + shift;
if (b < 0) if (b < 0)
@ -184,6 +204,7 @@ namespace GameRes.Formats.Circus
else if (r > 0xff) else if (r > 0xff)
r = 0xff; r = 0xff;
} }
m_output[pixel] = (byte)b; m_output[pixel] = (byte)b;
m_output[pixel+1] = (byte)g; m_output[pixel+1] = (byte)g;
m_output[pixel+2] = (byte)r; m_output[pixel+2] = (byte)r;
@ -239,61 +260,58 @@ namespace GameRes.Formats.Circus
private void UnpackV1 () private void UnpackV1 ()
{ {
using (var src = new ArcView.Reader (m_input)) byte[] window = new byte[0x10000];
int flag = 0;
int win_pos = 0;
int dst = 0;
while (dst < m_output.Length)
{ {
byte[] window = new byte[0x10000]; flag >>= 1;
int flag = 0; if (0 == (flag & 0x100))
int win_pos = 0; flag = m_input.ReadByte() | 0xff00;
int dst = 0;
while (dst < m_output.Length)
{
flag >>= 1;
if (0 == (flag & 0x100))
flag = src.ReadByte() | 0xff00;
if (0 != (flag & 1)) if (0 != (flag & 1))
{
byte dat = m_input.ReadByte();
window[win_pos++] = dat;
win_pos &= 0xffff;
m_output[dst++] = dat;
}
else
{
byte control = m_input.ReadByte();
int count, offset;
if (control >= 0xc0)
{ {
byte dat = src.ReadByte(); offset = ((control & 3) << 8) | m_input.ReadByte();
window[win_pos++] = dat; count = 4 + ((control >> 2) & 0xf);
win_pos &= 0xffff; }
m_output[dst++] = dat; else if (0 != (control & 0x80))
{
offset = control & 0x1f;
count = 2 + ((control >> 5) & 3);
if (0 == offset)
offset = m_input.ReadByte();
}
else if (0x7f == control)
{
count = 2 + m_input.ReadUInt16();
offset = m_input.ReadUInt16();
} }
else else
{ {
byte control = src.ReadByte(); offset = m_input.ReadUInt16();
int count, offset; count = control + 4;
}
if (control >= 0xc0) offset = win_pos - offset;
{ for (int k = 0; k < count && dst < m_output.Length; k++)
offset = ((control & 3) << 8) | src.ReadByte(); {
count = 4 + ((control >> 2) & 0xf); offset &= 0xffff;
} byte dat = window[offset++];
else if (0 != (control & 0x80)) window[win_pos++] = dat;
{ win_pos &= 0xffff;
offset = control & 0x1f; m_output[dst++] = dat;
count = 2 + ((control >> 5) & 3);
if (0 == offset)
offset = src.ReadByte();
}
else if (0x7f == control)
{
count = 2 + src.ReadUInt16();
offset = src.ReadUInt16();
}
else
{
offset = src.ReadUInt16();
count = control + 4;
}
offset = win_pos - offset;
for (int k = 0; k < count && dst < m_output.Length; k++)
{
offset &= 0xffff;
byte dat = window[offset++];
window[win_pos++] = dat;
win_pos &= 0xffff;
m_output[dst++] = dat;
}
} }
} }
} }
@ -303,7 +321,7 @@ namespace GameRes.Formats.Circus
{ {
int pixel_size = m_bpp / 8; int pixel_size = m_bpp / 8;
int src_stride = m_width * pixel_size; int src_stride = m_width * pixel_size;
using (var zlib = new ZLibStream (m_input, CompressionMode.Decompress, true)) using (var zlib = new ZLibStream (m_input.BaseStream, CompressionMode.Decompress, true))
using (var src = new BinaryReader (zlib)) using (var src = new BinaryReader (zlib))
{ {
if (m_bpp >= 24) if (m_bpp >= 24)
@ -388,6 +406,7 @@ namespace GameRes.Formats.Circus
{ {
if (!m_disposed) if (!m_disposed)
{ {
m_input.Dispose();
m_disposed = true; m_disposed = true;
} }
GC.SuppressFinalize (this); GC.SuppressFinalize (this);

View File

@ -0,0 +1,186 @@
//! \file ImageCRXD.cs
//! \date Thu Oct 13 14:18:47 2016
//! \brief Circus differential image format.
//
// Copyright (C) 2016 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using System.ComponentModel.Composition;
using System.Drawing;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.Circus
{
internal class CrxdMetaData : ImageMetaData
{
public string BaseFileName;
public uint BaseOffset;
public CrxMetaData DiffInfo;
public uint DiffOffset;
}
[Export(typeof(ImageFormat))]
public class CrxdFormat : CrxFormat
{
public override string Tag { get { return "CRXD"; } }
public override string Description { get { return "Circus differential image format"; } }
public override uint Signature { get { return 0x44585243; } } // 'CRXD'
public CrxdFormat ()
{
Extensions = new string[] { "crx" };
}
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[0x24];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
CrxdMetaData info = null;
if (Binary.AsciiEqual (header, 0x20, "CRXJ"))
{
stream.Position = 0x28;
stream.Read (header, 0, 4);
uint diff_offset = LittleEndian.ToUInt32 (header, 0);
using (var crx = OpenByOffset (diff_offset))
{
if (null == crx)
return null;
info = ReadMetaData (crx) as CrxdMetaData;
if (info != null)
info.DiffOffset = diff_offset;
}
}
else if (Binary.AsciiEqual (header, 0x20, "CRXG"))
{
using (var crx = new StreamRegion (stream, 0x20, true))
{
var diff_info = base.ReadMetaData (crx) as CrxMetaData;
if (null == diff_info)
return null;
info = new CrxdMetaData
{
Width = diff_info.Width,
Height = diff_info.Height,
OffsetX = diff_info.OffsetX,
OffsetY = diff_info.OffsetY,
BPP = diff_info.BPP,
DiffInfo = diff_info,
DiffOffset = 0,
};
}
}
if (info != null)
{
info.BaseOffset = LittleEndian.ToUInt32 (header, 8);
info.BaseFileName = Binary.GetCString (header, 0xC, 0x14);
}
return info;
}
Stream OpenByOffset (uint offset)
{
var vfs = VFS.Top as ArchiveFileSystem;
if (null == vfs)
return null;
var arc = vfs.Source as CrmArchive;
if (null == arc)
return null;
return arc.OpenByOffset (offset);
}
Stream OpenDiffStream (Stream diff, CrxdMetaData info)
{
if (0 == info.DiffOffset)
return new StreamRegion (diff, 0x20, true);
diff = OpenByOffset (info.DiffOffset);
if (null == diff)
throw new FileNotFoundException ("Referenced diff image not found");
return new StreamRegion (diff, 0x20);
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
var meta = (CrxdMetaData)info;
Stream base_file = OpenByOffset (meta.BaseOffset);
if (null == base_file)
{
var dir_name = VFS.GetDirectoryName (meta.FileName);
var name = VFS.CombinePath (dir_name, meta.BaseFileName);
if (!VFS.FileExists (name))
throw new FileNotFoundException ("Base image not found", meta.BaseFileName);
base_file = VFS.OpenSeekableStream (name);
}
using (base_file)
{
var base_info = base.ReadMetaData (base_file) as CrxMetaData;
if (null == base_info || base_info.BPP != info.BPP)
throw new InvalidFormatException ("Invalid base image");
using (var reader = new Reader (base_file, base_info))
using (var crx = OpenDiffStream (stream, meta))
using (var diff_reader = new Reader (crx, meta.DiffInfo))
{
reader.Unpack (true);
diff_reader.Unpack (true);
var diff_rect = new Rectangle (meta.OffsetX, meta.OffsetY, (int)meta.Width, (int)meta.Height);
var base_rect = new Rectangle (base_info.OffsetX, base_info.OffsetY,
(int)base_info.Width, (int)base_info.Height);
diff_rect = Rectangle.Intersect (diff_rect, base_rect);
if (diff_rect.IsEmpty)
throw new InvalidFormatException ("Empty diff region");
int pixel_size = base_info.BPP / 8;
int x = diff_rect.X - base_rect.X;
int y = diff_rect.Y - base_rect.Y;
int dst = y * reader.Stride + pixel_size * x;
var image = reader.Data;
int dx = diff_rect.X - meta.OffsetX;
int dy = diff_rect.Y - meta.OffsetY;
int src = dy * diff_reader.Stride + pixel_size * dx;
var diff = diff_reader.Data;
int blend_stride = diff_rect.Width * pixel_size;
for (int row = 0; row < diff_rect.Height; ++row)
{
for (int i = 0; i < blend_stride; i += pixel_size)
{
image[dst+i ] += diff[src+i];
image[dst+i+1] += diff[src+i+1];
image[dst+i+2] += diff[src+i+2];
if (4 == pixel_size)
image[dst+i+3] -= diff[src+i+3];
}
dst += reader.Stride;
src += diff_reader.Stride;
}
return ImageData.Create (base_info, reader.Format, reader.Palette, image, reader.Stride);
}
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("CrxdFormat.Write not implemented");
}
}
}

View File

@ -33,7 +33,7 @@ namespace GameRes.Formats.??????
public override string Description { get { return "?????? engine resource archive"; } } public override string Description { get { return "?????? engine resource archive"; } }
public override uint Signature { get { return 0; } } public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } } public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } } public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {

View File

@ -27,7 +27,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq; using System.Text;
using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.Eagls namespace GameRes.Formats.Eagls
@ -47,10 +49,8 @@ namespace GameRes.Formats.Eagls
} }
internal static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]"; internal static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]";
internal static readonly string Key = "EAGLS_SYSTEM"; internal static readonly byte[] EaglsKey = Encoding.ASCII.GetBytes ("EAGLS_SYSTEM");
internal static readonly byte[] AdvSysKey = Encoding.ASCII.GetBytes ("ADVSYS");
// FIXME not thread-safe
CRuntimeRandomGenerator m_rng = new CRuntimeRandomGenerator();
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
@ -70,6 +70,7 @@ namespace GameRes.Formats.Eagls
bool long_offsets = 40 == entry_size; bool long_offsets = 40 == entry_size;
int name_size = long_offsets ? 0x18 : 0x14; int name_size = long_offsets ? 0x18 : 0x14;
long first_offset = LittleEndian.ToUInt32 (index, name_size); long first_offset = LittleEndian.ToUInt32 (index, name_size);
bool has_scripts = false;
var dir = new List<Entry>(); var dir = new List<Entry>();
while (index_offset < index.Length) while (index_offset < index.Length)
{ {
@ -79,7 +80,10 @@ namespace GameRes.Formats.Eagls
index_offset += name_size; index_offset += name_size;
var entry = FormatCatalog.Instance.Create<Entry> (name); var entry = FormatCatalog.Instance.Create<Entry> (name);
if (name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) if (name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase))
{
entry.Type = "script"; entry.Type = "script";
has_scripts = true;
}
if (long_offsets) if (long_offsets)
{ {
entry.Offset = LittleEndian.ToInt64 (index, index_offset) - first_offset; entry.Offset = LittleEndian.ToInt64 (index, index_offset) - first_offset;
@ -99,32 +103,28 @@ namespace GameRes.Formats.Eagls
if (0 == dir.Count) if (0 == dir.Count)
return null; return null;
if (dir[0].Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) // CG archive if (dir[0].Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) // CG archive
return new CgArchive (file, this, dir); {
else var rng = DetectEncryptionScheme (file, dir[0]);
return new ArcFile (file, this, dir); return new EaglsArchive (file, this, dir, new CgEncryption (rng));
}
else if (has_scripts)
{
var enc = QueryEncryption();
if (enc != null)
return new EaglsArchive (file, this, dir, enc);
}
return new ArcFile (file, this, dir);
} }
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var cg_arc = arc as CgArchive; var earc = arc as EaglsArchive;
if (null != cg_arc) if (null == earc
return cg_arc.DecryptEntry (entry); || !(entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase) ||
if (entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) entry.Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)))
return DecryptDat (arc, entry); return arc.File.CreateStream (entry.Offset, entry.Size);
return arc.File.CreateStream (entry.Offset, entry.Size);
}
Stream DecryptDat (ArcFile arc, Entry entry) return earc.DecryptEntry (entry);
{
byte[] input = arc.File.View.ReadBytes (entry.Offset, entry.Size);
int text_offset = 3600;
int text_length = (int)(entry.Size - text_offset - 2);
m_rng.SRand ((sbyte)input[input.Length-1]);
for (int i = 0; i < text_length; i += 2)
{
input[text_offset + i] ^= (byte)Key[m_rng.Rand() % Key.Length];
}
return new MemoryStream (input);
} }
byte[] DecryptIndex (ArcView idx) byte[] DecryptIndex (ArcView idx)
@ -134,13 +134,14 @@ namespace GameRes.Formats.Eagls
using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset)) using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset))
unsafe unsafe
{ {
m_rng.SRand (view.ReadInt32 (idx_size)); var rng = new CRuntimeRandomGenerator();
rng.SRand (view.ReadInt32 (idx_size));
byte* ptr = view.GetPointer (0); byte* ptr = view.GetPointer (0);
try try
{ {
for (int i = 0; i < idx_size; ++i) for (int i = 0; i < idx_size; ++i)
{ {
output[i] = (byte)(ptr[i] ^ IndexKey[m_rng.Rand() % IndexKey.Length]); output[i] = (byte)(ptr[i] ^ IndexKey[rng.Rand() % IndexKey.Length]);
} }
return output; return output;
} }
@ -150,15 +151,69 @@ namespace GameRes.Formats.Eagls
} }
} }
} }
IRandomGenerator DetectEncryptionScheme (ArcView file, Entry first_entry)
{
int signature = (file.View.ReadInt32 (first_entry.Offset) >> 8) & 0xFFFF;
byte seed = file.View.ReadByte (first_entry.Offset+first_entry.Size-1);
IRandomGenerator[] rng_list = {
new LehmerRandomGenerator(),
new CRuntimeRandomGenerator(),
};
foreach (var rng in rng_list)
{
rng.SRand (seed);
rng.Rand(); // skip LZSS control byte
int test = signature;
test ^= EaglsKey[rng.Rand() % EaglsKey.Length];
test ^= EaglsKey[rng.Rand() % EaglsKey.Length] << 8;
// FIXME
// as key is rather short, this detection could produce false results sometimes
if (0x4D42 == test) // 'BM'
return rng;
}
throw new UnknownEncryptionScheme();
}
public override ResourceOptions GetDefaultOptions ()
{
IEntryEncryption enc = null;
if (!string.IsNullOrEmpty (Settings.Default.EAGLSEncryption))
KnownSchemes.TryGetValue (Settings.Default.EAGLSEncryption, out enc);
return new EaglsOptions { Encryption = enc };
}
public override object GetAccessWidget ()
{
return new GUI.WidgetEAGLS();
}
IEntryEncryption QueryEncryption ()
{
var options = Query<EaglsOptions> (arcStrings.ArcEncryptedNotice);
return options.Encryption;
}
internal static Dictionary<string, IEntryEncryption> KnownSchemes = new Dictionary<string, IEntryEncryption>
{
{ "EAGLS", new EaglsEncryption() },
{ "AdvSys", new AdvSysEncryption() },
};
} }
internal interface IRandomGenerator public class EaglsOptions : ResourceOptions
{
public IEntryEncryption Encryption;
}
public interface IRandomGenerator
{ {
void SRand (int seed); void SRand (int seed);
int Rand (); int Rand ();
} }
internal class CRuntimeRandomGenerator : IRandomGenerator [Serializable]
public class CRuntimeRandomGenerator : IRandomGenerator
{ {
uint m_seed; uint m_seed;
@ -174,7 +229,8 @@ namespace GameRes.Formats.Eagls
} }
} }
internal class LehmerRandomGenerator : IRandomGenerator [Serializable]
public class LehmerRandomGenerator : IRandomGenerator
{ {
int m_seed; int m_seed;
@ -197,57 +253,86 @@ namespace GameRes.Formats.Eagls
} }
} }
internal class CgArchive : ArcFile public interface IEntryEncryption
{ {
IRandomGenerator m_rng; void Decrypt (byte[] data);
}
public CgArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir) [Serializable]
: base (arc, impl, dir) public class CgEncryption : IEntryEncryption
{
readonly byte[] Key = PakOpener.EaglsKey;
readonly IRandomGenerator m_rng;
public CgEncryption (IRandomGenerator rng)
{ {
try m_rng = rng;
{
m_rng = DetectEncryptionScheme();
}
catch
{
this.Dispose();
throw;
}
} }
IRandomGenerator DetectEncryptionScheme () public void Decrypt (byte[] data)
{ {
var first_entry = Dir.First(); m_rng.SRand (data[data.Length-1]);
int signature = (File.View.ReadInt32 (first_entry.Offset) >> 8) & 0xFFFF; int limit = Math.Min (data.Length-1, 0x174b);
byte seed = File.View.ReadByte (first_entry.Offset+first_entry.Size-1); for (int i = 0; i < limit; ++i)
IRandomGenerator[] rng_list = {
new LehmerRandomGenerator(),
new CRuntimeRandomGenerator(),
};
foreach (var rng in rng_list)
{ {
rng.SRand (seed); data[i] ^= (byte)Key[m_rng.Rand() % Key.Length];
rng.Rand(); // skip LZSS control byte
int test = signature;
test ^= PakOpener.Key[rng.Rand() % PakOpener.Key.Length];
test ^= PakOpener.Key[rng.Rand() % PakOpener.Key.Length] << 8;
// FIXME
// as key is rather short, this detection could produce false results sometimes
if (0x4D42 == test) // 'BM'
return rng;
} }
throw new UnknownEncryptionScheme(); }
}
[Serializable]
public class EaglsEncryption : IEntryEncryption
{
readonly byte[] Key = PakOpener.EaglsKey;
readonly IRandomGenerator m_rng;
public EaglsEncryption ()
{
m_rng = new CRuntimeRandomGenerator();
}
public void Decrypt (byte[] data)
{
int text_offset = 3600;
int text_length = data.Length - text_offset - 2;
m_rng.SRand ((sbyte)data[data.Length-1]);
for (int i = 0; i < text_length; i += 2)
{
data[text_offset + i] ^= Key[m_rng.Rand() % Key.Length];
}
}
}
[Serializable]
public class AdvSysEncryption : IEntryEncryption
{
readonly byte[] Key = PakOpener.AdvSysKey;
public void Decrypt (byte[] data)
{
int text_offset = 136000;
int text_length = data.Length - text_offset;
for (int i = 0; i < text_length; ++i)
{
data[text_offset + i] ^= Key[i % Key.Length];
}
}
}
internal class EaglsArchive : ArcFile
{
readonly IEntryEncryption Encryption;
public EaglsArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, IEntryEncryption enc)
: base (arc, impl, dir)
{
Encryption = enc;
} }
public Stream DecryptEntry (Entry entry) public Stream DecryptEntry (Entry entry)
{ {
var input = File.View.ReadBytes (entry.Offset, entry.Size); byte[] input = File.View.ReadBytes (entry.Offset, entry.Size);
m_rng.SRand (input[input.Length-1]); Encryption.Decrypt (input);
int limit = Math.Min (input.Length-1, 0x174b);
for (int i = 0; i < limit; ++i)
{
input[i] ^= (byte)PakOpener.Key[m_rng.Rand() % PakOpener.Key.Length];
}
return new MemoryStream (input); return new MemoryStream (input);
} }
} }

View File

@ -0,0 +1,9 @@
<StackPanel x:Class="GameRes.Formats.GUI.WidgetEAGLS"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fmt="clr-namespace:GameRes.Formats.Eagls"
xmlns:p="clr-namespace:GameRes.Formats.Properties"
MaxWidth="200">
<ComboBox Name="Scheme" ItemsSource="{Binding}"
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=EAGLSEncryption, Mode=TwoWay}" Width="150"/>
</StackPanel>

View File

@ -0,0 +1,22 @@
using System.Windows.Controls;
using System.Linq;
using GameRes.Formats.Eagls;
using GameRes.Formats.Strings;
namespace GameRes.Formats.GUI
{
/// <summary>
/// Interaction logic for WidgetEAGLS.xaml
/// </summary>
public partial class WidgetEAGLS : StackPanel
{
public WidgetEAGLS ()
{
InitializeComponent ();
var schemes = new string[] { arcStrings.ArcIgnoreEncryption };
Scheme.ItemsSource = schemes.Concat (PakOpener.KnownSchemes.Keys);
if (-1 == Scheme.SelectedIndex)
Scheme.SelectedValue = PakOpener.KnownSchemes.First().Key;
}
}
}

View File

@ -152,7 +152,7 @@ namespace GameRes.Formats.KiriKiri
if (entry_size < 0) if (entry_size < 0)
return null; return null;
dir_offset += 12 + entry_size; dir_offset += 12 + entry_size;
if (0x6E666E68 == entry_signature) // "hnfn" if (0x6E666E68 == entry_signature || 0x46696C65 == entry_signature) // "hnfn" || "eliF"
{ {
uint hash = header.ReadUInt32(); uint hash = header.ReadUInt32();
int name_size = header.ReadInt16(); int name_size = header.ReadInt16();

View File

@ -72,9 +72,11 @@ namespace GameRes.Formats.Liar
cur_offset += 18+name_length; cur_offset += 18+name_length;
if (cur_offset > dir_size+24) if (cur_offset > dir_size+24)
return null; return null;
if (entry.CheckPlacement (data_offset + data_size)) if (entry.Size > 0 && entry.CheckPlacement (data_offset + data_size))
dir.Add (entry); dir.Add (entry);
} }
if (0 == dir.Count)
return null;
return new ArcFile (file, this, dir); return new ArcFile (file, this, dir);
} }
} }

View File

@ -34,6 +34,7 @@ using System.Runtime.InteropServices;
using GameRes.Compression; using GameRes.Compression;
using GameRes.Formats.Strings; using GameRes.Formats.Strings;
using GameRes.Formats.Properties; using GameRes.Formats.Properties;
using GameRes.Strings;
namespace GameRes.Formats.NitroPlus namespace GameRes.Formats.NitroPlus
{ {
@ -88,6 +89,7 @@ namespace GameRes.Formats.NitroPlus
CHAOSHEAD, CHAOSHEADTR1, CHAOSHEADTR2, MURAMASATR, MURAMASA, SUMAGA, DJANGO, DJANGOTR, CHAOSHEAD, CHAOSHEADTR1, CHAOSHEADTR2, MURAMASATR, MURAMASA, SUMAGA, DJANGO, DJANGOTR,
LAMENTO, SWEETPOOL, SUMAGASP, DEMONBANE, MURAMASAAD, AXANAEL, KIKOKUGAI, SONICOMITR2, LAMENTO, SWEETPOOL, SUMAGASP, DEMONBANE, MURAMASAAD, AXANAEL, KIKOKUGAI, SONICOMITR2,
SUMAGA3P, SONICOMI, LOSTX, LOSTXTRAILER, DRAMATICALMURDER, TOTONO, PHENOMENO, NEKODA, SUMAGA3P, SONICOMI, LOSTX, LOSTXTRAILER, DRAMATICALMURDER, TOTONO, PHENOMENO, NEKODA,
HANACHIRASU
} }
public class NpaOptions : ResourceOptions public class NpaOptions : ResourceOptions
@ -137,7 +139,9 @@ namespace GameRes.Formats.NitroPlus
var game_id = NpaTitleId.NotEncrypted; var game_id = NpaTitleId.NotEncrypted;
if (encrypted) if (encrypted)
{ {
enc = QueryGameEncryption(); enc = QueryGameEncryption (file.Name);
if (null == enc)
throw new OperationCanceledException (garStrings.MsgUnknownEncryption);
game_id = enc.TitleId; game_id = enc.TitleId;
} }
@ -395,10 +399,18 @@ namespace GameRes.Formats.NitroPlus
return new GUI.CreateNPAWidget(); return new GUI.CreateNPAWidget();
} }
EncryptionScheme QueryGameEncryption () EncryptionScheme QueryGameEncryption (string arc_name)
{ {
var options = Query<NpaOptions> (arcStrings.ArcEncryptedNotice); EncryptionScheme scheme = null;
return options.Scheme; var title = FormatCatalog.Instance.LookupGame (arc_name);
if (!string.IsNullOrEmpty (title))
scheme = GetScheme (title);
if (null == scheme)
{
var options = Query<NpaOptions> (arcStrings.ArcEncryptedNotice);
scheme = options.Scheme;
}
return scheme;
} }
public static NpaTitleId GetTitleId (string title) public static NpaTitleId GetTitleId (string title)

View File

@ -33,7 +33,7 @@ using GameRes.Utility;
namespace GameRes.Formats.NitroPlus namespace GameRes.Formats.NitroPlus
{ {
internal class PakEntry : Entry internal class PakEntry : PackedEntry
{ {
public uint Key; public uint Key;
} }
@ -102,13 +102,15 @@ namespace GameRes.Formats.NitroPlus
return null; return null;
var name = Encodings.cp932.GetString (name_buf, 0, name_length); var name = Encodings.cp932.GetString (name_buf, 0, name_length);
var entry = FormatCatalog.Instance.Create<PackedEntry> (name); var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = base_offset + header.ReadUInt32();
entry.UnpackedSize = header.ReadUInt32(); entry.Offset = base_offset + header.ReadUInt32();
entry.Size = header.ReadUInt32(); entry.UnpackedSize = header.ReadUInt32();
entry.IsPacked = header.ReadInt32() != 0; entry.Size = header.ReadUInt32();
uint psize = header.ReadUInt32(); entry.IsPacked = header.ReadInt32() != 0;
uint psize = header.ReadUInt32();
if (entry.IsPacked) if (entry.IsPacked)
entry.Size = psize; entry.Size = psize;
if (!entry.CheckPlacement (file.MaxOffset)) if (!entry.CheckPlacement (file.MaxOffset))
return null; return null;
dir.Add (entry); dir.Add (entry);
@ -159,21 +161,17 @@ namespace GameRes.Formats.NitroPlus
if (name_len != header.Read (name_buf, 0, name_len)) if (name_len != header.Read (name_buf, 0, name_len))
return null; return null;
uint key = GetKey (name_buf, name_len); uint key = GetKey (name_buf, name_len);
uint offset = header.ReadUInt32() ^ key; var name = Encodings.cp932.GetString (name_buf, 0, name_len);
uint size = header.ReadUInt32() ^ key; var entry = FormatCatalog.Instance.Create<PakEntry> (name);
uint val1 = header.ReadUInt32() ^ key; entry.Offset = (header.ReadUInt32() ^ key) + base_offset;
uint val2 = header.ReadUInt32() ^ key; entry.UnpackedSize = (header.ReadUInt32() ^ key);
uint val3 = header.ReadUInt32() ^ key; uint ignored = (header.ReadUInt32() ^ key);
entry.IsPacked = (header.ReadUInt32() ^ key) != 0;
var entry = new PakEntry { uint packed_size = (header.ReadUInt32() ^ key);
Name = Encodings.cp932.GetString (name_buf, 0, name_len), entry.Key = key;
Offset = base_offset+offset, entry.Size = entry.IsPacked ? packed_size : entry.UnpackedSize;
Size = size,
Key = key,
};
if (!entry.CheckPlacement (file.MaxOffset)) if (!entry.CheckPlacement (file.MaxOffset))
return null; return null;
entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name);
dir.Add (entry); dir.Add (entry);
} }
return dir; return dir;
@ -194,27 +192,14 @@ namespace GameRes.Formats.NitroPlus
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var pak_entry = entry as PakEntry; var pak_entry = entry as PakEntry;
if (pak_entry != null) if (pak_entry != null && !pak_entry.IsPacked)
return OpenV3Entry (arc, pak_entry); return OpenV3Entry (arc, pak_entry);
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
var packed_entry = entry as PackedEntry; var packed_entry = entry as PackedEntry;
if (packed_entry != null && packed_entry.IsPacked) if (packed_entry != null && packed_entry.IsPacked)
return UnpackV2Entry (arc, packed_entry); input = new ZLibStream (input, CompressionMode.Decompress);
return arc.File.CreateStream (entry.Offset, entry.Size); return input;
}
private Stream UnpackV2Entry (ArcFile arc, PackedEntry entry)
{
var input = arc.File.CreateStream (entry.Offset, entry.Size);
try
{
return new ZLibStream (input, CompressionMode.Decompress);
}
catch
{
input.Dispose();
throw;
}
} }
private Stream OpenV3Entry (ArcFile arc, PakEntry entry) private Stream OpenV3Entry (ArcFile arc, PakEntry entry)
@ -230,7 +215,7 @@ namespace GameRes.Formats.NitroPlus
key = Binary.RotR (key, 8); key = Binary.RotR (key, 8);
} }
if (enc_size == entry.Size) if (enc_size == entry.Size)
return new MemoryStream (buf, false); return new MemoryStream (buf);
return new PrefixStream (buf, arc.File.CreateStream (entry.Offset+enc_size, entry.Size-enc_size)); return new PrefixStream (buf, arc.File.CreateStream (entry.Offset+enc_size, entry.Size-enc_size));
} }
} }

View File

@ -68,22 +68,18 @@ namespace GameRes.Formats.Magi
return null; return null;
var name = Encodings.cp932.GetString (name_buffer, 0, name_length); var name = Encodings.cp932.GetString (name_buffer, 0, name_length);
var entry = FormatCatalog.Instance.Create<PackedEntry> (name); var entry = FormatCatalog.Instance.Create<PackedEntry> (name);
entry.Offset = index.ReadUInt32() + base_offset;
uint size = index.ReadUInt32(); entry.Offset = index.ReadUInt32() + base_offset;
entry.UnpackedSize = index.ReadUInt32();
index.ReadUInt32(); index.ReadUInt32();
uint flags = index.ReadUInt32(); uint is_packed = index.ReadUInt32();
uint packed_size = index.ReadUInt32(); uint packed_size = index.ReadUInt32();
entry.IsPacked = flags != 0 && packed_size != 0; entry.IsPacked = is_packed != 0 && packed_size != 0;
if (entry.IsPacked) if (entry.IsPacked)
{
entry.Size = packed_size; entry.Size = packed_size;
entry.UnpackedSize = size;
}
else else
{ entry.Size = entry.UnpackedSize;
entry.Size = size;
entry.UnpackedSize = size;
}
if (!entry.CheckPlacement (file.MaxOffset)) if (!entry.CheckPlacement (file.MaxOffset))
return null; return null;
dir.Add (entry); dir.Add (entry);
@ -94,19 +90,11 @@ namespace GameRes.Formats.Magi
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var input = arc.File.CreateStream (entry.Offset, entry.Size); Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
var pentry = entry as PackedEntry; var pentry = entry as PackedEntry;
if (null == pentry || !pentry.IsPacked) if (null != pentry || pentry.IsPacked)
return input; input = new ZLibStream (input, CompressionMode.Decompress);
try return input;
{
return new ZLibStream (input, CompressionMode.Decompress);
}
catch
{
input.Dispose();
throw;
}
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -78,10 +78,23 @@ namespace GARbro.GUI
public EntryViewModel (Entry entry, int priority) public EntryViewModel (Entry entry, int priority)
{ {
Source = entry; Source = entry;
Name = Path.GetFileName (entry.Name); Name = SafeGetFileName (entry.Name);
Priority = priority; Priority = priority;
} }
static char[] SeparatorCharacters = { '\\', '/', ':' };
/// <summary>
/// Same as Path.GetFileName, but ignores invalid charactes
/// </summary>
string SafeGetFileName (string filename)
{
var name_start = filename.LastIndexOfAny (SeparatorCharacters);
if (-1 == name_start)
return filename;
return filename.Substring (name_start+1);
}
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
public Entry Source { get; private set; } public Entry Source { get; private set; }

View File

@ -226,6 +226,7 @@ Yume Miru Kusuri<br/>
<tr class="last"><td>*.pna</td><td><tt>PNAP</tt></td><td>No</td></tr> <tr class="last"><td>*.pna</td><td><tt>PNAP</tt></td><td>No</td></tr>
<tr class="odd"><td>*.xfl</td><td><tt>LB</tt></td><td>Yes</td><td rowspan="4">Liar-soft</td><td rowspan="4"> <tr class="odd"><td>*.xfl</td><td><tt>LB</tt></td><td>Yes</td><td rowspan="4">Liar-soft</td><td rowspan="4">
Angel Bullet<br/> Angel Bullet<br/>
Divus Rabies<br/>
Fairytale Requiem<br/> Fairytale Requiem<br/>
Love Negotiator<br/> Love Negotiator<br/>
Sekien no Inganock<br/> Sekien no Inganock<br/>
@ -420,6 +421,7 @@ Draculius <span class="footnote">ShiinaRio v2.38</span><br/>
Enkaku Sousa <span class="footnote">2.36 or 2.37</span><br/> Enkaku Sousa <span class="footnote">2.36 or 2.37</span><br/>
Gensou no Idea ~Oratorio Phantasm Historia~<span class="footnote">ShiinaRio v2.49</span><br/> Gensou no Idea ~Oratorio Phantasm Historia~<span class="footnote">ShiinaRio v2.49</span><br/>
Gohoushi Nurse ~Mayonaka no Kyousei Call~ <span class="footnote">ShiinaRio v2.50</span><br/> Gohoushi Nurse ~Mayonaka no Kyousei Call~ <span class="footnote">ShiinaRio v2.50</span><br/>
Hana to Otome ni Shukufuku o <span class="footnote">ShiinaRio v2.46</span><br/>
Helter Skelter <span class="footnote">ShiinaRio v2.40</span><br/> Helter Skelter <span class="footnote">ShiinaRio v2.40</span><br/>
Hin wa Bokura no Fuku no Kami <span class="footnote">ShiinaRio v2.49</span><br/> Hin wa Bokura no Fuku no Kami <span class="footnote">ShiinaRio v2.49</span><br/>
Hitozuma Onna Kyoushi Reika <span class="footnote">ShiinaRio v2.39</span><br/> Hitozuma Onna Kyoushi Reika <span class="footnote">ShiinaRio v2.39</span><br/>
@ -524,6 +526,8 @@ Futago Hinyuu x 3<br/>
Oppai Baka<br/> Oppai Baka<br/>
Oshiete! Yuiko Sensei<br/> Oshiete! Yuiko Sensei<br/>
Mainichi Shabutte Ii Desu ka?<br/> Mainichi Shabutte Ii Desu ka?<br/>
Ryoujoku Chikan Jigoku<br/>
Saimin Class WONDERFUL<br/>
Senpai - Oppai - Kako ni Modori Pai<br/> Senpai - Oppai - Kako ni Modori Pai<br/>
Tsuribaka ~Gakuen Taikou! Joshikousei Tsuriage Adventure~<br/> Tsuribaka ~Gakuen Taikou! Joshikousei Tsuriage Adventure~<br/>
</td></tr> </td></tr>
@ -567,6 +571,7 @@ Medorei ~Okasareta Houkago~<br/>
Onna Kyoushi<br/> Onna Kyoushi<br/>
Ore to Kanojo wa Shuju na Kankei<br/> Ore to Kanojo wa Shuju na Kankei<br/>
Saishuu Chikan Densha<br/> Saishuu Chikan Densha<br/>
Saishuu Chikan Densha 3<br/>
Serina<br/> Serina<br/>
Ura Nyuugaku ~Ineki ni Nureta Kyoukasho~<br/> Ura Nyuugaku ~Ineki ni Nureta Kyoukasho~<br/>
</td></tr> </td></tr>
@ -605,13 +610,15 @@ Soshite Ashita no Sekai yori<br/>
</td></tr> </td></tr>
<tr><td>*.b</td><td><tt>ABMP7</tt><br/><tt>abmp10</tt><br/><tt>abmp11</tt></td><td>No</td></tr> <tr><td>*.b</td><td><tt>ABMP7</tt><br/><tt>abmp10</tt><br/><tt>abmp11</tt></td><td>No</td></tr>
<tr class="last"><td>*.png</td><td><tt>DPNG</tt></td><td>No</td></tr> <tr class="last"><td>*.png</td><td><tt>DPNG</tt></td><td>No</td></tr>
<tr class="odd"><td>*.dat</td><td>-</td><td>No</td><td rowspan="4">Circus</td><td rowspan="4"> <tr class="odd"><td>*.dat</td><td>-</td><td>No</td><td rowspan="5">Circus</td><td rowspan="5">
D.S. -Dal Segno-<br/>
Infantaria XP<br/> Infantaria XP<br/>
Maid no Yakata ~Zetsubou Hen~<br/> Maid no Yakata ~Zetsubou Hen~<br/>
RPG Gakuen<br/> RPG Gakuen<br/>
</td></tr> </td></tr>
<tr class="odd"><td>*.pck</td><td>-</td><td>No</td></tr> <tr class="odd"><td>*.pck</td><td>-</td><td>No</td></tr>
<tr class="odd"><td>*.crx</td><td><tt>CRXG</tt></td><td>No</td></tr> <tr class="odd"><td>*.crx</td><td><tt>CRXG</tt></td><td>No</td></tr>
<tr class="odd"><td>*.crm</td><td><tt>CRXB</tt></td><td>No</td></tr>
<tr class="odd last"><td>*.pcm</td><td><tt>XPCM</tt></td><td>No</td></tr> <tr class="odd last"><td>*.pcm</td><td><tt>XPCM</tt></td><td>No</td></tr>
<tr><td>*.pak</td><td><tt>CHERRY PACK 2.0</tt><br/><tt>CHERRY PACK 3.0</tt><br/>-</td><td>No</td><td>Cherry</td><td> <tr><td>*.pak</td><td><tt>CHERRY PACK 2.0</tt><br/><tt>CHERRY PACK 3.0</tt><br/>-</td><td>No</td><td>Cherry</td><td>
Double<br/> Double<br/>
@ -878,6 +885,7 @@ Gakuen Saimin Reido<br/>
<tr class="odd last"><td>*.akb</td><td><tt>AKB</tt></td><td>No</td></tr> <tr class="odd last"><td>*.akb</td><td><tt>AKB</tt></td><td>No</td></tr>
<tr><td>*.cpz</td><td><tt>CPZ5</tt><br/><tt>CPZ6</tt></td><td>No</td><td rowspan="2">CMVS</td><td rowspan="2"> <tr><td>*.cpz</td><td><tt>CPZ5</tt><br/><tt>CPZ6</tt></td><td>No</td><td rowspan="2">CMVS</td><td rowspan="2">
Amatsutsumi<br/> Amatsutsumi<br/>
Ashita no Kimi to Au Tame ni<br/>
Chrono Clock<br/> Chrono Clock<br/>
Hapymaher<br/> Hapymaher<br/>
Haruiro Ouse<br/> Haruiro Ouse<br/>