mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-24 03:44:13 +08:00
implemented 'vff' archives and GAL images.
This commit is contained in:
parent
9b04f39738
commit
cb04b322f2
@ -72,6 +72,7 @@
|
||||
</Compile>
|
||||
<Compile Include="Ankh\ArcGRP.cs" />
|
||||
<Compile Include="ArcCG.cs" />
|
||||
<Compile Include="LiveMaker\ArcVF.cs" />
|
||||
<Compile Include="ArcZIP.cs" />
|
||||
<Compile Include="AudioVOC.cs" />
|
||||
<Compile Include="AZSys\WidgetAZ.xaml.cs">
|
||||
@ -103,6 +104,10 @@
|
||||
<Compile Include="ImageLZ.cs" />
|
||||
<Compile Include="KiriKiri\ChainReactionCrypt.cs" />
|
||||
<Compile Include="Liar\ImageLIM.cs" />
|
||||
<Compile Include="LiveMaker\ImageGAL.cs" />
|
||||
<Compile Include="LiveMaker\WidgetGAL.xaml.cs">
|
||||
<DependentUpon>WidgetGAL.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Nekopunch\ArcPAK.cs" />
|
||||
<Compile Include="NitroPlus\ArcPAK.cs" />
|
||||
<Compile Include="MnoViolet\ImageDIF.cs" />
|
||||
@ -549,6 +554,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="LiveMaker\WidgetGAL.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="NitroPlus\CreateNPAWidget.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
244
ArcFormats/LiveMaker/ArcVF.cs
Normal file
244
ArcFormats/LiveMaker/ArcVF.cs
Normal file
@ -0,0 +1,244 @@
|
||||
//! \file ArcVF.cs
|
||||
//! \date Wed Jun 08 00:27:36 2016
|
||||
//! \brief LiveMaker 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.Diagnostics;
|
||||
using System.IO;
|
||||
using GameRes.Compression;
|
||||
|
||||
namespace GameRes.Formats.LiveMaker
|
||||
{
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class VffOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "DAT/vf"; } }
|
||||
public override string Description { get { return "LiveMaker resource archive"; } }
|
||||
public override uint Signature { get { return 0x666676; } } // 'vff'
|
||||
public override bool IsHierarchic { get { return true; } }
|
||||
public override bool CanCreate { get { return false; } }
|
||||
|
||||
public VffOpener ()
|
||||
{
|
||||
Extensions = new string[] { "dat" };
|
||||
Signatures = new uint[] { 0x666676, 0 };
|
||||
}
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
ArcView index_file = file;
|
||||
ArcView extra_file = null;
|
||||
try
|
||||
{
|
||||
// possible filesystem structure:
|
||||
// game.dat -- main archive body
|
||||
// game.001 -- [optional] extra part
|
||||
// game.ext -- [optional] separate index (could be included into the main body)
|
||||
|
||||
if (!file.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase))
|
||||
return null;
|
||||
uint signature = index_file.View.ReadUInt32 (0);
|
||||
if (0x666676 != signature)
|
||||
{
|
||||
var ext_filename = Path.ChangeExtension (file.Name, ".ext");
|
||||
if (!VFS.FileExists (ext_filename))
|
||||
return null;
|
||||
index_file = VFS.OpenView (ext_filename);
|
||||
signature = index_file.View.ReadUInt32 (0);
|
||||
if (0x666676 != signature)
|
||||
return null;
|
||||
}
|
||||
int count = index_file.View.ReadInt32 (6);
|
||||
if (!IsSaneCount (count))
|
||||
return null;
|
||||
|
||||
var dir = ReadIndex (index_file, count);
|
||||
if (null == dir)
|
||||
return null;
|
||||
long max_offset = file.MaxOffset;
|
||||
for (int i = 0; i < dir.Count; ++i)
|
||||
{
|
||||
if (!dir[i].CheckPlacement (max_offset))
|
||||
{
|
||||
if (extra_file != null)
|
||||
{
|
||||
// remove entries that don't fit into game.dat+game.001
|
||||
int discard = dir.Count - i;
|
||||
Trace.WriteLine (string.Format ("{0} entries didn't fit and were discarded", discard), "[vff]");
|
||||
dir.RemoveRange (i, discard);
|
||||
break;
|
||||
}
|
||||
var extra_filename = Path.ChangeExtension (file.Name, ".001");
|
||||
if (!VFS.FileExists (extra_filename))
|
||||
return null;
|
||||
extra_file = VFS.OpenView (extra_filename);
|
||||
max_offset += extra_file.MaxOffset;
|
||||
if (!dir[i].CheckPlacement (max_offset))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (null == extra_file)
|
||||
return new ArcFile (file, this, dir);
|
||||
return new VffArchive (file, this, dir, extra_file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (extra_file != null)
|
||||
extra_file.Dispose();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (index_file != file)
|
||||
index_file.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var vff = arc as VffArchive;
|
||||
Stream input = null;
|
||||
if (vff != null)
|
||||
input = vff.OpenStream (entry);
|
||||
else
|
||||
input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
|
||||
var pent = entry as PackedEntry;
|
||||
if (pent != null && pent.IsPacked)
|
||||
return new ZLibStream (input, CompressionMode.Decompress);
|
||||
else
|
||||
return input;
|
||||
}
|
||||
|
||||
List<Entry> ReadIndex (ArcView file, int count)
|
||||
{
|
||||
uint index_offset = 0xA;
|
||||
var name_buffer = new byte[0x100];
|
||||
var rnd = new TpRandom (0x75D6EE39u);
|
||||
var dir = new List<Entry> (count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
uint name_length = file.View.ReadUInt32 (index_offset);
|
||||
index_offset += 4;
|
||||
if (0 == name_length || name_length > name_buffer.Length)
|
||||
return null;
|
||||
if (name_length != file.View.Read (index_offset, name_buffer, 0, name_length))
|
||||
return null;
|
||||
index_offset += name_length;
|
||||
|
||||
var name = DecryptName (name_buffer, (int)name_length, rnd);
|
||||
dir.Add (FormatCatalog.Instance.Create<PackedEntry> (name));
|
||||
}
|
||||
rnd.Reset();
|
||||
uint offset = file.View.ReadUInt32 (index_offset) ^ rnd.GetRand32();
|
||||
foreach (var entry in dir)
|
||||
{
|
||||
index_offset += 8;
|
||||
uint next_offset = file.View.ReadUInt32 (index_offset) ^ rnd.GetRand32();
|
||||
entry.Offset = offset;
|
||||
entry.Size = next_offset - offset;
|
||||
offset = next_offset;
|
||||
}
|
||||
index_offset += 8;
|
||||
foreach (PackedEntry entry in dir)
|
||||
{
|
||||
entry.IsPacked = 0 == file.View.ReadByte (index_offset++);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
string DecryptName (byte[] name_buf, int name_length, TpRandom key)
|
||||
{
|
||||
for (int i = 0; i < name_length; ++i)
|
||||
{
|
||||
name_buf[i] ^= (byte)key.GetRand32();
|
||||
}
|
||||
return Encodings.cp932.GetString (name_buf, 0, name_length);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TpRandom
|
||||
{
|
||||
uint m_seed;
|
||||
uint m_current;
|
||||
|
||||
public TpRandom (uint seed)
|
||||
{
|
||||
m_seed = seed;
|
||||
m_current = 0;
|
||||
}
|
||||
|
||||
public uint GetRand32 ()
|
||||
{
|
||||
m_current += m_current << 2;
|
||||
m_current += m_seed;
|
||||
return m_current;
|
||||
}
|
||||
|
||||
public void Reset ()
|
||||
{
|
||||
m_current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class VffArchive : ArcFile
|
||||
{
|
||||
readonly ArcView ExtraFile;
|
||||
|
||||
public VffArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, ArcView extra_file)
|
||||
: base (arc, impl, dir)
|
||||
{
|
||||
ExtraFile = extra_file;
|
||||
}
|
||||
|
||||
internal Stream OpenStream (Entry entry)
|
||||
{
|
||||
if (entry.Offset < File.MaxOffset && entry.Offset+entry.Size > File.MaxOffset)
|
||||
{
|
||||
var first_part = File.View.ReadBytes (entry.Offset, entry.Size);
|
||||
var second_part = ExtraFile.CreateStream (0, entry.Size - (uint)first_part.Length);
|
||||
return new PrefixStream (first_part, second_part);
|
||||
}
|
||||
else if (entry.Offset >= File.MaxOffset)
|
||||
{
|
||||
return ExtraFile.CreateStream (entry.Offset, entry.Size);
|
||||
}
|
||||
else
|
||||
return File.CreateStream (entry.Offset, entry.Size);
|
||||
}
|
||||
|
||||
bool _vff_disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!_vff_disposed)
|
||||
{
|
||||
if (disposing && ExtraFile != null)
|
||||
ExtraFile.Dispose();
|
||||
_vff_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
570
ArcFormats/LiveMaker/ImageGAL.cs
Normal file
570
ArcFormats/LiveMaker/ImageGAL.cs
Normal file
@ -0,0 +1,570 @@
|
||||
//! \file ImageGAL.cs
|
||||
//! \date Wed Jun 08 03:07:41 2016
|
||||
//! \brief LiveMaker 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.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using GameRes.Compression;
|
||||
using GameRes.Formats.Properties;
|
||||
using GameRes.Formats.Strings;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.LiveMaker
|
||||
{
|
||||
internal class GalMetaData : ImageMetaData
|
||||
{
|
||||
public int Version;
|
||||
public int FrameCount;
|
||||
public bool Shuffled;
|
||||
public int Compression;
|
||||
public uint Mask;
|
||||
public int BlockWidth;
|
||||
public int BlockHeight;
|
||||
public int DataOffset;
|
||||
}
|
||||
|
||||
internal class GalOptions : ResourceOptions
|
||||
{
|
||||
public uint Key;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GalScheme : ResourceScheme
|
||||
{
|
||||
public Dictionary<string, uint> KnownKeys;
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class GalFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "GAL"; } }
|
||||
public override string Description { get { return "LiveMaker image format"; } }
|
||||
public override uint Signature { get { return 0x656C6147; } } // 'Gale'
|
||||
|
||||
public static Dictionary<string, uint> KnownKeys = new Dictionary<string, uint>();
|
||||
|
||||
public override ResourceScheme Scheme
|
||||
{
|
||||
get { return new GalScheme { KnownKeys = KnownKeys }; }
|
||||
set { KnownKeys = ((GalScheme)value).KnownKeys; }
|
||||
}
|
||||
|
||||
public override ImageMetaData ReadMetaData (Stream stream)
|
||||
{
|
||||
var header = new byte[0x30];
|
||||
if (11 != stream.Read (header, 0, 11))
|
||||
return null;
|
||||
int version = header[4] * 100 + header[5] * 10 + header[6] - 5328;
|
||||
if (version < 100 || version > 107)
|
||||
return null;
|
||||
int header_size = LittleEndian.ToInt32 (header, 7);
|
||||
if (header_size < 0x28 || header_size > 0x100)
|
||||
return null;
|
||||
if (header_size > header.Length)
|
||||
header = new byte[header_size];
|
||||
if (header_size != stream.Read (header, 0, header_size))
|
||||
return null;
|
||||
if (version != LittleEndian.ToInt32 (header, 0))
|
||||
return null;
|
||||
return new GalMetaData
|
||||
{
|
||||
Width = LittleEndian.ToUInt32 (header, 4),
|
||||
Height = LittleEndian.ToUInt32 (header, 8),
|
||||
BPP = LittleEndian.ToInt32 (header, 0xC),
|
||||
Version = version,
|
||||
FrameCount = LittleEndian.ToInt32 (header, 0x10),
|
||||
Shuffled = header[0x15] != 0,
|
||||
Compression = header[0x16],
|
||||
Mask = LittleEndian.ToUInt32 (header, 0x18),
|
||||
BlockWidth = LittleEndian.ToInt32 (header, 0x1C),
|
||||
BlockHeight = LittleEndian.ToInt32 (header, 0x20),
|
||||
DataOffset = header_size + 11,
|
||||
};
|
||||
}
|
||||
|
||||
uint? LastKey = null;
|
||||
|
||||
public override ImageData Read (Stream stream, ImageMetaData info)
|
||||
{
|
||||
var meta = (GalMetaData)info;
|
||||
uint key = 0;
|
||||
if (meta.Shuffled)
|
||||
{
|
||||
if (LastKey != null)
|
||||
key = LastKey.Value;
|
||||
else
|
||||
key = QueryKey();
|
||||
}
|
||||
try
|
||||
{
|
||||
using (var reader = new GalReader (stream, meta, key))
|
||||
{
|
||||
reader.Unpack();
|
||||
if (meta.Shuffled)
|
||||
LastKey = key;
|
||||
return ImageData.Create (info, reader.Format, reader.Palette, reader.Data, reader.Stride);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
LastKey = null;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write (Stream file, ImageData image)
|
||||
{
|
||||
throw new System.NotImplementedException ("GalFormat.Write not implemented");
|
||||
}
|
||||
|
||||
public override ResourceOptions GetDefaultOptions ()
|
||||
{
|
||||
return new GalOptions { Key = Settings.Default.GALKey };
|
||||
}
|
||||
|
||||
public override object GetAccessWidget ()
|
||||
{
|
||||
return new GUI.WidgetGAL();
|
||||
}
|
||||
|
||||
uint QueryKey ()
|
||||
{
|
||||
if (!KnownKeys.Any())
|
||||
return 0;
|
||||
var options = Query<GalOptions> (arcStrings.ArcImageEncrypted);
|
||||
return options.Key;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GalReader : IDisposable
|
||||
{
|
||||
BinaryReader m_input;
|
||||
GalMetaData m_info;
|
||||
byte[] m_output;
|
||||
List<Frame> m_frames;
|
||||
uint m_key;
|
||||
|
||||
public byte[] Data { get { return m_output; } }
|
||||
public PixelFormat Format { get; private set; }
|
||||
public BitmapPalette Palette { get; private set; }
|
||||
public int Stride { get; private set; }
|
||||
|
||||
public GalReader (Stream input, GalMetaData info, uint key)
|
||||
{
|
||||
m_info = info;
|
||||
if (m_info.Compression < 0 || m_info.Compression > 2)
|
||||
throw new InvalidFormatException();
|
||||
m_frames = new List<Frame> (m_info.FrameCount);
|
||||
m_key = key;
|
||||
m_input = new ArcView.Reader (input);
|
||||
}
|
||||
|
||||
internal class Frame
|
||||
{
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int BPP;
|
||||
public int Stride;
|
||||
public int AlphaStride;
|
||||
public List<Layer> Layers;
|
||||
public Color[] Palette;
|
||||
|
||||
public Frame (int layer_count)
|
||||
{
|
||||
Layers = new List<Layer> (layer_count);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Layer
|
||||
{
|
||||
public byte[] Pixels;
|
||||
public byte[] Alpha;
|
||||
}
|
||||
|
||||
public void Unpack ()
|
||||
{
|
||||
m_input.BaseStream.Position = m_info.DataOffset;
|
||||
uint name_length = m_input.ReadUInt32();
|
||||
m_input.BaseStream.Seek (name_length, SeekOrigin.Current);
|
||||
uint mask = m_input.ReadUInt32();
|
||||
m_input.BaseStream.Seek (9, SeekOrigin.Current);
|
||||
int layer_count = m_input.ReadInt32();
|
||||
if (layer_count < 1)
|
||||
throw new InvalidFormatException();
|
||||
if (layer_count > 1)
|
||||
throw new NotImplementedException();
|
||||
|
||||
// XXX only first frame is interpreted.
|
||||
|
||||
var frame = new Frame (layer_count);
|
||||
frame.Width = m_input.ReadInt32();
|
||||
frame.Height = m_input.ReadInt32();
|
||||
frame.BPP = m_input.ReadInt32();
|
||||
if (frame.BPP <= 0)
|
||||
throw new InvalidFormatException();
|
||||
if (frame.BPP <= 8)
|
||||
frame.Palette = ReadPalette (1 << frame.BPP);
|
||||
frame.Stride = (frame.Width * frame.BPP + 7) / 8;
|
||||
frame.AlphaStride = (frame.Width + 3) & ~3;
|
||||
if (frame.BPP >= 8)
|
||||
frame.Stride = (frame.Stride + 3) & ~3;
|
||||
m_frames.Add (frame);
|
||||
for (int i = 0; i < layer_count; ++i)
|
||||
{
|
||||
m_input.ReadInt32();
|
||||
m_input.ReadInt32();
|
||||
m_input.ReadByte(); // visibility
|
||||
m_input.ReadInt32(); // -1
|
||||
m_input.ReadInt32(); // 0xFF
|
||||
m_input.ReadByte();
|
||||
name_length = m_input.ReadUInt32();
|
||||
m_input.BaseStream.Seek (name_length, SeekOrigin.Current);
|
||||
if (m_info.Version >= 107)
|
||||
m_input.ReadByte();
|
||||
var layer = new Layer();
|
||||
int layer_size = m_input.ReadInt32();
|
||||
long layer_end = m_input.BaseStream.Position + layer_size;
|
||||
layer.Pixels = UnpackLayer (frame, layer_size);
|
||||
m_input.BaseStream.Position = layer_end;
|
||||
int alpha_size = m_input.ReadInt32();
|
||||
if (alpha_size != 0)
|
||||
{
|
||||
layer.Alpha = UnpackLayer (frame, alpha_size, true);
|
||||
}
|
||||
frame.Layers.Add (layer);
|
||||
}
|
||||
Flatten (0);
|
||||
}
|
||||
|
||||
byte[] UnpackLayer (Frame frame, int length, bool is_alpha = false)
|
||||
{
|
||||
using (var packed = new StreamRegion (m_input.BaseStream, m_input.BaseStream.Position, length, true))
|
||||
{
|
||||
if (0 == m_info.Compression || 2 == m_info.Compression && is_alpha)
|
||||
return ReadZlib (frame, packed, is_alpha);
|
||||
if (2 == m_info.Compression)
|
||||
return ReadJpeg (frame, packed);
|
||||
return ReadBlocks (frame, packed, is_alpha);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] ReadBlocks (Frame frame, Stream packed, bool is_alpha)
|
||||
{
|
||||
if (m_info.BlockWidth <= 0 || m_info.BlockHeight <= 0)
|
||||
return ReadRaw (frame, packed, is_alpha);
|
||||
int blocks_w = (frame.Width + m_info.BlockWidth - 1) / m_info.BlockWidth;
|
||||
int blocks_h = (frame.Height + m_info.BlockHeight - 1) / m_info.BlockHeight;
|
||||
int blocks_count = blocks_w * blocks_h;
|
||||
var data = new byte[blocks_count * 8];
|
||||
packed.Read (data, 0, data.Length);
|
||||
var refs = new int[blocks_count * 2];
|
||||
Buffer.BlockCopy (data, 0, refs, 0, data.Length);
|
||||
if (m_info.Shuffled)
|
||||
ShuffleBlocks (refs, blocks_count);
|
||||
|
||||
int bpp = is_alpha ? 8 : frame.BPP;
|
||||
int stride = is_alpha ? frame.AlphaStride : frame.Stride;
|
||||
var pixels = new byte[stride * frame.Height];
|
||||
int i = 0;
|
||||
for (int y = 0; y < frame.Height; y += m_info.BlockHeight)
|
||||
{
|
||||
int height = Math.Min (m_info.BlockHeight, frame.Height - y);
|
||||
for (int x = 0; x < frame.Width; x += m_info.BlockWidth)
|
||||
{
|
||||
int dst = y * stride + (x * bpp + 7) / 8;
|
||||
int width = Math.Min (m_info.BlockWidth, frame.Width - x);
|
||||
int chunk_size = (width * bpp + 7) / 8;
|
||||
if (-1 == refs[i])
|
||||
{
|
||||
for (int j = 0; j < height; ++j)
|
||||
{
|
||||
packed.Read (pixels, dst, chunk_size);
|
||||
dst += stride;
|
||||
}
|
||||
}
|
||||
else if (-2 == refs[i])
|
||||
{
|
||||
int src_x = m_info.BlockWidth * (refs[i+1] % blocks_w);
|
||||
int src_y = m_info.BlockHeight * (refs[i+1] / blocks_w);
|
||||
int src = src_y * stride + (src_x * bpp + 7) / 8;
|
||||
for (int j = 0; j < height; ++j)
|
||||
{
|
||||
Buffer.BlockCopy (pixels, src, pixels, dst, chunk_size);
|
||||
src += stride;
|
||||
dst += stride;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int frame_ref = refs[i];
|
||||
int layer_ref = refs[i+1];
|
||||
if (frame_ref > m_frames.Count || layer_ref > m_frames[frame_ref].Layers.Count)
|
||||
throw new InvalidFormatException();
|
||||
var layer = m_frames[frame_ref].Layers[layer_ref];
|
||||
byte[] src = is_alpha ? layer.Alpha : layer.Pixels;
|
||||
for (int j = 0; j < height; ++j)
|
||||
{
|
||||
Buffer.BlockCopy (src, dst, pixels, dst, chunk_size);
|
||||
dst += stride;
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
byte[] ReadRaw (Frame frame, Stream packed, bool is_alpha)
|
||||
{
|
||||
int stride = is_alpha ? frame.AlphaStride : frame.Stride;
|
||||
var pixels = new byte[frame.Height * stride];
|
||||
if (m_info.Shuffled)
|
||||
{
|
||||
foreach (var dst in RandomSequence (frame.Height, m_key))
|
||||
{
|
||||
packed.Read (pixels, dst*stride, stride);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
packed.Read (pixels, 0, pixels.Length);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
byte[] ReadZlib (Frame frame, Stream packed, bool is_alpha)
|
||||
{
|
||||
using (var zs = new ZLibStream (packed, CompressionMode.Decompress))
|
||||
return ReadBlocks (frame, zs, is_alpha);
|
||||
}
|
||||
|
||||
byte[] ReadJpeg (Frame frame, Stream packed)
|
||||
{
|
||||
var decoder = new JpegBitmapDecoder (packed, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
var bitmap = decoder.Frames[0];
|
||||
frame.BPP = bitmap.Format.BitsPerPixel;
|
||||
int stride = bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8;
|
||||
var pixels = new byte[bitmap.PixelHeight * stride];
|
||||
bitmap.CopyPixels (pixels, stride, 0);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
void Flatten (int frame_num)
|
||||
{
|
||||
// XXX only first layer is considered.
|
||||
|
||||
var frame = m_frames[frame_num];
|
||||
var layer = frame.Layers[0];
|
||||
if (null == layer.Alpha)
|
||||
{
|
||||
m_output = layer.Pixels;
|
||||
if (null != frame.Palette)
|
||||
Palette = new BitmapPalette (frame.Palette);
|
||||
if (8 == frame.BPP)
|
||||
Format = PixelFormats.Indexed8;
|
||||
else if (16 == frame.BPP)
|
||||
Format = PixelFormats.Bgr565;
|
||||
else if (24 == frame.BPP)
|
||||
Format = PixelFormats.Bgr24;
|
||||
else if (32 == frame.BPP)
|
||||
Format = PixelFormats.Bgr32;
|
||||
else if (4 == frame.BPP)
|
||||
Format = PixelFormats.Indexed4;
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
Stride = frame.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_output = new byte[frame.Width * frame.Height * 4];
|
||||
switch (frame.BPP)
|
||||
{
|
||||
case 4: Flatten4bpp (frame, layer); break;
|
||||
case 8: Flatten8bpp (frame, layer); break;
|
||||
case 16: Flatten16bpp (frame, layer); break;
|
||||
case 24: Flatten24bpp (frame, layer); break;
|
||||
case 32: Flatten32bpp (frame, layer); break;
|
||||
default: throw new NotSupportedException ("Not supported color depth");
|
||||
}
|
||||
Format = PixelFormats.Bgra32;
|
||||
Stride = frame.Width * 4;
|
||||
}
|
||||
}
|
||||
|
||||
void Flatten4bpp (Frame frame, Layer layer)
|
||||
{
|
||||
int dst = 0;
|
||||
int src = 0;
|
||||
int a = 0;
|
||||
for (int y = 0; y < frame.Height; ++y)
|
||||
{
|
||||
for (int x = 0; x < frame.Width; ++x)
|
||||
{
|
||||
byte pixel = layer.Pixels[src + x/2];
|
||||
int index = 0 == (x & 1) ? (pixel & 0xF) : (pixel >> 4);
|
||||
var color = frame.Palette[index];
|
||||
m_output[dst++] = color.B;
|
||||
m_output[dst++] = color.G;
|
||||
m_output[dst++] = color.R;
|
||||
m_output[dst++] = layer.Alpha[a+x];
|
||||
}
|
||||
src += frame.Stride;
|
||||
a += frame.AlphaStride;
|
||||
}
|
||||
}
|
||||
|
||||
void Flatten8bpp (Frame frame, Layer layer)
|
||||
{
|
||||
int dst = 0;
|
||||
int src = 0;
|
||||
int a = 0;
|
||||
for (int y = 0; y < frame.Height; ++y)
|
||||
{
|
||||
for (int x = 0; x < frame.Width; ++x)
|
||||
{
|
||||
var color = frame.Palette[ layer.Pixels[src+x] ];
|
||||
m_output[dst++] = color.B;
|
||||
m_output[dst++] = color.G;
|
||||
m_output[dst++] = color.R;
|
||||
m_output[dst++] = layer.Alpha[a+x];
|
||||
}
|
||||
src += frame.Stride;
|
||||
a += frame.AlphaStride;
|
||||
}
|
||||
}
|
||||
|
||||
void Flatten16bpp (Frame frame, Layer layer)
|
||||
{
|
||||
int src = 0;
|
||||
int dst = 0;
|
||||
int a = 0;
|
||||
for (int y = 0; y < frame.Height; ++y)
|
||||
{
|
||||
for (int x = 0; x < frame.Width; ++x)
|
||||
{
|
||||
int pixel = LittleEndian.ToUInt16 (layer.Pixels, src + x*2);
|
||||
m_output[dst++] = (byte)((pixel & 0x001F) * 0xFF / 0x001F);
|
||||
m_output[dst++] = (byte)((pixel & 0x07E0) * 0xFF / 0x07E0);
|
||||
m_output[dst++] = (byte)((pixel & 0xF800) * 0xFF / 0xF800);
|
||||
m_output[dst++] = layer.Alpha[a+x];
|
||||
}
|
||||
src += frame.Stride;
|
||||
a += frame.AlphaStride;
|
||||
}
|
||||
}
|
||||
|
||||
void Flatten24bpp (Frame frame, Layer layer)
|
||||
{
|
||||
int src = 0;
|
||||
int dst = 0;
|
||||
int a = 0;
|
||||
int gap = frame.Stride - frame.Width * 3;
|
||||
for (int y = 0; y < frame.Height; ++y)
|
||||
{
|
||||
for (int x = 0; x < frame.Width; ++x)
|
||||
{
|
||||
m_output[dst++] = layer.Pixels[src++];
|
||||
m_output[dst++] = layer.Pixels[src++];
|
||||
m_output[dst++] = layer.Pixels[src++];
|
||||
m_output[dst++] = layer.Alpha[a+x];
|
||||
}
|
||||
src += gap;
|
||||
a += frame.AlphaStride;
|
||||
}
|
||||
}
|
||||
|
||||
void Flatten32bpp (Frame frame, Layer layer)
|
||||
{
|
||||
int src = 0;
|
||||
int dst = 0;
|
||||
int a = 0;
|
||||
for (int y = 0; y < frame.Height; ++y)
|
||||
{
|
||||
for (int x = 0; x < frame.Width; ++x)
|
||||
{
|
||||
m_output[dst++] = layer.Pixels[src];
|
||||
m_output[dst++] = layer.Pixels[src+1];
|
||||
m_output[dst++] = layer.Pixels[src+2];
|
||||
m_output[dst++] = layer.Alpha[a+x];
|
||||
src += 4;
|
||||
}
|
||||
a += frame.AlphaStride;
|
||||
}
|
||||
}
|
||||
|
||||
Color[] ReadPalette (int colors)
|
||||
{
|
||||
var palette_data = m_input.ReadBytes (4 * colors);
|
||||
if (palette_data.Length != 4 * colors)
|
||||
throw new EndOfStreamException();
|
||||
var palette = new Color[colors];
|
||||
for (int i = 0; i < colors; ++i)
|
||||
{
|
||||
int c = i * 4;
|
||||
palette[i] = Color.FromRgb (palette_data[c+2], palette_data[c+1], palette_data[c]);
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
|
||||
void ShuffleBlocks (int[] refs, int count)
|
||||
{
|
||||
var copy = refs.Clone() as int[];
|
||||
int src = 0;
|
||||
foreach (var index in RandomSequence (count, m_key))
|
||||
{
|
||||
refs[index*2] = copy[src++];
|
||||
refs[index*2+1] = copy[src++];
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<int> RandomSequence (int count, uint seed)
|
||||
{
|
||||
var tp = new TpRandom (seed);
|
||||
var order = Enumerable.Range (0, count).ToList<int>();
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int n = (int)(tp.GetRand32() % (uint)order.Count);
|
||||
yield return order[n];
|
||||
order.RemoveAt (n);
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool _disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
m_input.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
45
ArcFormats/LiveMaker/WidgetGAL.xaml
Normal file
45
ArcFormats/LiveMaker/WidgetGAL.xaml
Normal file
@ -0,0 +1,45 @@
|
||||
<StackPanel x:Class="GameRes.Formats.GUI.WidgetGAL"
|
||||
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:local="clr-namespace:GameRes.Formats.GUI"
|
||||
MaxWidth="250" Orientation="Vertical">
|
||||
<StackPanel.Resources>
|
||||
<local:GaleKeyConverter x:Key="keyConverter"/>
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Validation.HasError" Value="true">
|
||||
<Setter Property="ToolTip"
|
||||
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<Label HorizontalAlignment="Left" Margin="0,0,5,5" Padding="0">
|
||||
<TextBlock Text="{x:Static s:arcStrings.GALChoose}" TextWrapping="WrapWithOverflow"/>
|
||||
</Label>
|
||||
<ComboBox Name="Title" ItemsSource="{Binding}"
|
||||
SelectedValue="{Binding ElementName=Key, Path=Text, Converter={StaticResource keyConverter}}"
|
||||
SelectedValuePath="Value" DisplayMemberPath="Key"
|
||||
Width="200" HorizontalAlignment="Left"/>
|
||||
<TextBox Name="Key" Width="200" HorizontalAlignment="Left" Margin="0,5,0,0">
|
||||
<TextBox.Text>
|
||||
<Binding Source="{x:Static p:Settings.Default}" Path="GALKey" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" Converter="{StaticResource keyConverter}">
|
||||
<Binding.ValidationRules>
|
||||
<local:GaleKeyRule/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
<Validation.ErrorTemplate>
|
||||
<ControlTemplate>
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Right" Foreground="Red" FontWeight="Bold" Text="!" VerticalAlignment="Center"/>
|
||||
<Border BorderBrush="Red" BorderThickness="1">
|
||||
<AdornedElementPlaceholder Name="ValidationAdorner" />
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</ControlTemplate>
|
||||
</Validation.ErrorTemplate>
|
||||
</TextBox>
|
||||
</StackPanel>
|
73
ArcFormats/LiveMaker/WidgetGAL.xaml.cs
Normal file
73
ArcFormats/LiveMaker/WidgetGAL.xaml.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using GameRes.Formats.LiveMaker;
|
||||
using GameRes.Formats.Properties;
|
||||
using GameRes.Formats.Strings;
|
||||
|
||||
namespace GameRes.Formats.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for WidgetGAL.xaml
|
||||
/// </summary>
|
||||
public partial class WidgetGAL : StackPanel
|
||||
{
|
||||
public WidgetGAL()
|
||||
{
|
||||
InitializeComponent();
|
||||
var first_item = new KeyValuePair<string, uint> (arcStrings.ArcIgnoreEncryption, 0u);
|
||||
var items = new KeyValuePair<string, uint>[] { first_item };
|
||||
this.Title.ItemsSource = items.Concat (GalFormat.KnownKeys.OrderBy (x => x.Key));
|
||||
}
|
||||
}
|
||||
|
||||
[ValueConversion(typeof(uint), typeof(string))]
|
||||
public class GaleKeyConverter : IValueConverter
|
||||
{
|
||||
public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is uint)
|
||||
return ((uint)value).ToString ("X");
|
||||
else if (value is string)
|
||||
return ConvertBack (value, targetType, parameter, culture);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is uint)
|
||||
return Convert (value, targetType, parameter, culture);
|
||||
string strValue = value as string;
|
||||
uint result_key;
|
||||
if (!string.IsNullOrWhiteSpace (strValue)
|
||||
&& uint.TryParse (strValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result_key))
|
||||
return result_key;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class GaleKeyRule : ValidationRule
|
||||
{
|
||||
public override ValidationResult Validate (object value, CultureInfo cultureInfo)
|
||||
{
|
||||
uint key = 0;
|
||||
try
|
||||
{
|
||||
if (value is uint)
|
||||
key = (uint)value;
|
||||
else if (!string.IsNullOrWhiteSpace (value as string))
|
||||
key = UInt32.Parse ((string)value, NumberStyles.HexNumber);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ValidationResult (false, arcStrings.INTKeyRequirement);
|
||||
}
|
||||
return new ValidationResult (true, null);
|
||||
}
|
||||
}
|
||||
}
|
12
ArcFormats/Properties/Settings.Designer.cs
generated
12
ArcFormats/Properties/Settings.Designer.cs
generated
@ -549,5 +549,17 @@ namespace GameRes.Formats.Properties {
|
||||
this["MEDScriptScheme"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("0")]
|
||||
public uint GALKey {
|
||||
get {
|
||||
return ((uint)(this["GALKey"]));
|
||||
}
|
||||
set {
|
||||
this["GALKey"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,5 +134,8 @@
|
||||
<Setting Name="MEDScriptScheme" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="GALKey" Type="System.UInt32" Scope="User">
|
||||
<Value Profile="(Default)">0</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
9
ArcFormats/Strings/arcStrings.Designer.cs
generated
9
ArcFormats/Strings/arcStrings.Designer.cs
generated
@ -188,6 +188,15 @@ namespace GameRes.Formats.Strings {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Choose title or enter 32-bit hex key.
|
||||
/// </summary>
|
||||
public static string GALChoose {
|
||||
get {
|
||||
return ResourceManager.GetString("GALChoose", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to âge proprietary image format.
|
||||
/// </summary>
|
||||
|
@ -372,4 +372,8 @@
|
||||
<value>아카이브 내용이 암호화 되었을 수 있습니다.
|
||||
적절한 암호체계를 선택하세요.</value>
|
||||
</data>
|
||||
<data name="GALChoose" xml:space="preserve">
|
||||
<value>Choose title or enter 32-bit hex key</value>
|
||||
<comment>translation pending</comment>
|
||||
</data>
|
||||
</root>
|
@ -375,4 +375,7 @@ choose appropriate encryption scheme.</value>
|
||||
<value>Archive content could be encrypted.
|
||||
Choose appropriate encryption scheme.</value>
|
||||
</data>
|
||||
<data name="GALChoose" xml:space="preserve">
|
||||
<value>Choose title or enter 32-bit hex key</value>
|
||||
</data>
|
||||
</root>
|
@ -158,6 +158,9 @@
|
||||
<data name="DPKKeys" xml:space="preserve">
|
||||
<value>Ключи шифрования</value>
|
||||
</data>
|
||||
<data name="GALChoose" xml:space="preserve">
|
||||
<value>Выберите наименование или введите 32-битный ключ</value>
|
||||
</data>
|
||||
<data name="INTChooseExe" xml:space="preserve">
|
||||
<value>Выберите исполняемый файл</value>
|
||||
</data>
|
||||
|
@ -377,4 +377,8 @@ choose appropriate encryption scheme.</value>
|
||||
Choose appropriate encryption scheme.</value>
|
||||
<comment>translation pending</comment>
|
||||
</data>
|
||||
<data name="GALChoose" xml:space="preserve">
|
||||
<value>Choose title or enter 32-bit hex key</value>
|
||||
<comment>translation pending</comment>
|
||||
</data>
|
||||
</root>
|
@ -136,6 +136,9 @@
|
||||
<setting name="MEDScriptScheme" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="GALKey" serializeAs="String">
|
||||
<value>0</value>
|
||||
</setting>
|
||||
</GameRes.Formats.Properties.Settings>
|
||||
</userSettings>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
|
||||
|
@ -474,6 +474,7 @@ Eve to Iu Na no Omocha<br/>
|
||||
Hissatsu Chikannin II<br/>
|
||||
Otomegari<br/>
|
||||
Ryoujoku Gojuusou<br/>
|
||||
Shinjin Kangofu Miho<br/>
|
||||
Tokumei Sentai Sirenger<br/>
|
||||
Tokumei Sentai Yuzu Ranger<br/>
|
||||
Zetsuboushi<br/>
|
||||
@ -580,6 +581,7 @@ Vampire Crusaders<br/>
|
||||
<tr><td>*.gpk+*.gtb<br/>*.vpk+*.vtb</td><td>-</td><td>No</td><td rowspan="3">Black Cyc</td><td rowspan="3">
|
||||
Before Dawn Daybreak ~Shinen no Utahime~<br/>
|
||||
Gun-Katana<br/>
|
||||
Hana Goyomi<br/>
|
||||
Jishou Seirei Majutsushi vs Shinsei Daiikkyuu Akuma<br/>
|
||||
Kurogane no Tsubasa ~The Alchemist's Story~<br/>
|
||||
Mushitsukai<br/>
|
||||
@ -874,6 +876,11 @@ Taiiku Souko ~Shoujotachi no Sange~<br/>
|
||||
Big Magnum Harimoto-sensei<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.mfc</td><td><tt>MFC</tt></td><td>No</td></tr>
|
||||
<tr class="odd"><td>game.dat</td><td><tt>vff</tt></td><td>No</td><td rowspan="2">LiveMaker</td><td rowspan="2">
|
||||
Grope ~Yami no Naka no Kotori-tachi~<br/>
|
||||
Ryoujoku Seifuku Jogakuen ~Chimitsu ni Nureta Seifuku~<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.gal</td><td><tt>Gale105</tt><br/><tt>Gale106</tt></td><td>No</td></tr>
|
||||
</table>
|
||||
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
|
||||
</body>
|
||||
|
Loading…
x
Reference in New Issue
Block a user