implemented ADVEngine 'arc' archives and GPS images.

This commit is contained in:
morkt 2016-02-20 18:23:02 +04:00
parent 77f5693016
commit 4ee3511075
4 changed files with 272 additions and 2 deletions

116
ArcFormats/Abel/ArcARC.cs Normal file
View File

@ -0,0 +1,116 @@
//! \file ArcARC.cs
//! \date Sat Feb 20 13:02:18 2016
//! \brief Archive format used by Abel subsidiaries.
//
// 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 GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.Abel
{
[Export(typeof(ArchiveFormat))]
public class ArcOpener : ArchiveFormat
{
public override string Tag { get { return "ARC/ADVENGINE"; } }
public override string Description { get { return "ADVEngine resource archive"; } }
public override uint Signature { get { return 0x00637261; } } // 'arc'
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public ArcOpener ()
{
Extensions = new string[] { "" };
}
const int IndexEntrySize = 0x26;
public override ArcFile TryOpen (ArcView file)
{
int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count))
return null;
uint base_offset = file.View.ReadUInt32 (0xC);
if (base_offset <= 0x18 || base_offset >= file.MaxOffset)
return null;
uint packed_size = file.View.ReadUInt32 (0x10);
int index_size = file.View.ReadInt32 (0x14);
if (packed_size > file.MaxOffset || index_size / IndexEntrySize != count)
return null;
var name_buffer = new byte[30];
using (var packed = file.CreateStream (0x18, packed_size))
using (var lzss = new LzssStream (packed))
using (var index = new ArcView.Reader (lzss))
{
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
if (name_buffer.Length != index.Read (name_buffer, 0, name_buffer.Length))
return null;
var name = Binary.GetCString (name_buffer, 0, name_buffer.Length);
var entry = FormatCatalog.Instance.Create<Entry> (name);
if (name.EndsWith (".acd", StringComparison.InvariantCultureIgnoreCase))
entry.Type = "script";
entry.Offset = index.ReadUInt32() + base_offset;
entry.Size = index.ReadUInt32();
if (!entry.CheckPlacement (file.MaxOffset))
return null;
dir.Add (entry);
}
return new ArcFile (file, this, dir);
}
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (entry.Size > 12 && entry.Name.EndsWith (".cmp", StringComparison.InvariantCultureIgnoreCase)
&& arc.File.View.AsciiEqual (entry.Offset, "CMP\0"))
return OpenCmpEntry (arc, entry);
if (entry.Size > 8 && entry.Name.EndsWith (".acd", StringComparison.InvariantCultureIgnoreCase)
&& arc.File.View.AsciiEqual (entry.Offset, "ACD\0"))
return OpenAcdEntry (arc, entry);
return base.OpenEntry (arc, entry);
}
Stream OpenAcdEntry (ArcFile arc, Entry entry)
{
var data = arc.File.View.ReadBytes (entry.Offset, entry.Size);
for (int i = 8; i < data.Length; ++i)
{
data[i] = (byte)(0xFF - data[i]);
}
return new MemoryStream (data);
}
Stream OpenCmpEntry (ArcFile arc, Entry entry)
{
uint offset = arc.File.View.ReadUInt32 (entry.Offset+8);
if (offset >= entry.Size)
return base.OpenEntry (arc, entry);
return arc.File.CreateStream (entry.Offset+offset, entry.Size-offset);
}
}
}

137
ArcFormats/Abel/ImageGPS.cs Normal file
View File

@ -0,0 +1,137 @@
//! \file ImageGPS.cs
//! \date Sat Feb 20 14:13:10 2016
//! \brief ADVEngine compressed bitmap.
//
// 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 GameRes.Compression;
using GameRes.Utility;
namespace GameRes.Formats.Abel
{
internal class GpsMetaData : ImageMetaData
{
public byte Compression;
public int PackedSize;
public int UnpackedSize;
}
[Export(typeof(ImageFormat))]
public class GpsFormat : BmpFormat
{
public override string Tag { get { return "GPS"; } }
public override string Description { get { return "ADVEngine compressed bitmap"; } }
public override uint Signature { get { return 0x535047; } } // 'GPS'
public GpsFormat ()
{
Extensions = new string[] { "gps", "cmp" };
}
public override ImageMetaData ReadMetaData (Stream stream)
{
var header = new byte[0x29];
if (header.Length != stream.Read (header, 0, header.Length))
return null;
var gps = new GpsMetaData
{
Width = LittleEndian.ToUInt32 (header, 0x19),
Height = LittleEndian.ToUInt32 (header, 0x1D),
Compression = header[0x10],
UnpackedSize = LittleEndian.ToInt32 (header, 0x11),
PackedSize = LittleEndian.ToInt32 (header, 0x15),
};
// read BMP header
using (var input = OpenGpsStream (stream, gps.Compression, 0x54))
{
var bmp_info = base.ReadMetaData (input);
if (null == bmp_info)
return null;
gps.BPP = bmp_info.BPP;
return gps;
}
}
public override ImageData Read (Stream stream, ImageMetaData info)
{
var gps = (GpsMetaData)info;
stream.Position = 0x29;
using (var input = OpenGpsStream (stream, gps.Compression, gps.UnpackedSize))
return base.Read (input, info);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GpsFormat.Write not implemented");
}
Stream OpenGpsStream (Stream input, byte compression, int unpacked_size)
{
if (0 == compression)
return new StreamRegion (input, 0x29, true);
else if (1 == compression)
return OpenRLEStream (input, unpacked_size);
else if (2 == compression)
return new LzssStream (input, LzssMode.Decompress, true);
else if (3 == compression)
{
using (var lzss = new LzssStream (input, LzssMode.Decompress, true))
return OpenRLEStream (lzss, unpacked_size);
}
else
throw new InvalidFormatException();
}
Stream OpenRLEStream (Stream input, int output_size)
{
var output = new byte[output_size];
UnpackRLE (input, output);
return new MemoryStream (output);
}
void UnpackRLE (Stream input, byte[] output)
{
int dst = 0;
while (dst < output.Length)
{
int count = Math.Min (3, output.Length-dst);
count = input.Read (output, dst, count);
if (count < 3)
break;
count = input.ReadByte();
if (-1 == count)
break;
dst += 3;
if (count > 1)
{
count = Math.Min ((count-1) * 3, output.Length-dst);
Binary.CopyOverlapped (output, dst-3, dst, count);
dst += count;
}
}
}
}
}

View File

@ -62,8 +62,11 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Abel\ArcARC.cs" />
<Compile Include="Abel\ImageGPS.cs" />
<Compile Include="Actgs\ArcDAT.cs" />
<Compile Include="ArcCG.cs" />
<Compile Include="ImageLZ.cs" />
<Compile Include="NitroPlus\ArcNPK.cs" />
<Compile Include="Softpal\ArcPAC.cs" />
<Compile Include="Softpal\AudioBGM.cs" />
@ -251,6 +254,7 @@
<Compile Include="ShiinaRio\ArcS25.cs" />
<Compile Include="ArcSAF.cs" />
<Compile Include="BlackCyc\ArcVPK.cs" />
<Compile Include="Will\ArcPNA.cs" />
<Compile Include="Will\ArcPulltop.cs" />
<Compile Include="Xuse\ArcBIN.cs" />
<Compile Include="Xuse\ArcGD.cs" />

View File

@ -81,6 +81,7 @@ Okaa-san ga Ippai!<br/>
</td></tr>
<tr class="odd"><td>*.pak</td><td><tt>HyPack</tt></td><td>Yes</td><td>Kogado</td><td>Symphonic Rain</td></tr>
<tr><td>*+*.lst</td><td>-</td><td>No</td><td rowspan="2">Nexton LikeC</td><td rowspan="2">
Injoku Shinryuu Club<br/>
Moon.<br/>
Ryoujoku Famiresu Choukyou Menu<br/>
Ryoujoku Idol Mesu Dorei<br/>
@ -148,9 +149,13 @@ Kyrie ~Blood Royal 3~<br/>
Shiosai no Himei<br/>
</td></tr>
<tr class="odd"><td>*.gcp</td><td><tt>CMP1</tt></td><td>No</td></tr>
<tr><td>*.pd</td><td><tt>PackOnly</tt><br/><tt>PackPlus</tt><br/><tt>FlyingShinePDFile</tt></td><td>Yes</td><td>Flying Shine</td><td>Cross†Channel</td></tr>
<tr><td>*.pd</td><td><tt>PackOnly</tt><br/><tt>PackPlus</tt><br/><tt>FlyingShinePDFile</tt></td><td>Yes</td><td>Flying Shine</td><td>
Akarui Mirai ~Wet And Messy 2nd time~<br/>
Cross†Channel<br/>
Crime Rhyme<br/>
</td></tr>
<tr class="odd"><td>*.rpa</td><td><tt>RPA-3.0</tt></td><td>Yes</td><td>Ren'Py</td><td>Katawa Shoujo</td></tr>
<tr><td>*.arc</td><td>-</td><td>Yes</td><td rowspan="2">Will</td><td rowspan="2">
<tr><td>*.arc</td><td>-</td><td>Yes</td><td rowspan="3">Will</td><td rowspan="3">
Cynthia ~Sincerely to You~<br/>
Folklore Jam<br/>
I/O<br/>
@ -163,6 +168,7 @@ Tsuma Youji 2<br/>
Yume Miru Kusuri<br/>
</td></tr>
<tr><td>*.wip<br/>*.msk<br/>*.mos</td><td><tt>WIPF</tt></td><td>No</td></tr>
<tr><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="3">Liar-soft</td><td rowspan="3">
Angel Bullet<br/>
Fairytale Requiem<br/>
@ -176,6 +182,7 @@ Altered Pink ~Tokumu Sentai Duel Ranger~<br/>
Aozora Gakko no Sensei-kun<br/>
Cafe Sourire<br/>
Coμ<br/>
Crime Rhyme series<br/>
Damegane<br/>
Distance<br/>
Fate/stay night<br/>
@ -186,6 +193,7 @@ Hanafubuki ~Sennen no Koi o Shimashita~<br/>
Haruiro ☆ Communication ♪<br/>
Hime to Majin to Koi Suru Tamashii<br/>
Imouto Style<br/>
Inaho no Mirai</br>
Mayoeru Futari to Sekai no Subete<br/>
Natsupochi<br/>
Nidaime wa ☆ Mahou Shoujo<br/>
@ -337,6 +345,7 @@ Trouble @ Spiral!<br/>
<tr class="odd"><td>*.epa</td><td><tt>EP</tt></td><td>No</td></tr>
<tr><td>*.arc</td><td><tt>MAI</tt></td><td>No</td><td rowspan="2">Matsuri Kikaku</td><td rowspan="2">
Chikan Sharyou Nigousha<br/>
Hime Musha<br/>
Warusa<br/>
</td></tr>
<tr><td>*.ami<br/>*.cmp<br/></td><td><tt>AM</tt><br/><tt>CM</tt></td><td>No</td></tr>
@ -684,6 +693,10 @@ Unity Marriage ~Futari no Hanayome~<br/>
<tr><td>*.npk</td><td><tt>NPK2</tt></td><td>No</td><td>Nitro+</td><td>
Tokyo Necro<br/>
</td></tr>
<tr class="odd"><td>*</td><td><tt>arc</tt></td><td>No</td><td rowspan="2">ADVEngine</td><td rowspan="2">
Thanatos no Koi ~In Ane Otouto Soukan~<br/>
</td></tr>
<tr class="odd"><td>*.gps</td><td><tt>GPS</tt></td><td>No</td></tr>
</table>
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
</body>