implemented version 6 of PCCS archives.

This commit is contained in:
morkt 2016-02-14 23:20:51 +04:00
parent 289cae9218
commit 8856639058
2 changed files with 79 additions and 31 deletions

View File

@ -27,6 +27,7 @@ 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.Security.Cryptography;
using GameRes.Utility; using GameRes.Utility;
namespace GameRes.Formats.CsWare namespace GameRes.Formats.CsWare
@ -34,6 +35,18 @@ namespace GameRes.Formats.CsWare
internal class PcsEntry : Entry internal class PcsEntry : Entry
{ {
public byte Key; public byte Key;
public uint NameHash; // used in archives version 6
}
internal class PcsArchive : ArcFile
{
public readonly int Version;
public PcsArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, int version)
: base (arc, impl, dir)
{
Version = version;
}
} }
[Export(typeof(ArchiveFormat))] [Export(typeof(ArchiveFormat))]
@ -48,82 +61,88 @@ namespace GameRes.Formats.CsWare
public override ArcFile TryOpen (ArcView file) public override ArcFile TryOpen (ArcView file)
{ {
int version = file.View.ReadUInt16 (4); int version = file.View.ReadUInt16 (4);
if (version < 1 || version > 4) if (version < 1 || version > 6)
return null; return null;
int count = file.View.ReadInt32 (8); int count = file.View.ReadInt32 (8);
if (!IsSaneCount (count)) if (!IsSaneCount (count))
return null; return null;
uint data_offset = file.View.ReadUInt32 (12); uint data_offset = file.View.ReadUInt32 (12);
int index_size = (int)data_offset - 0x10; uint index_size = data_offset - 0x10;
var index = file.View.ReadBytes (0x10, index_size);
int index_offset = 0x10; if (6 == version)
var index_buffer = new byte[0x40]; {
index = ShuffleBlocks (index, file.View.ReadUInt16 (6));
}
int index_offset = 0;
var dir = new List<Entry> (count); var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
int name_length; int name_length;
if (version > 1) if (version > 1)
{ {
name_length = file.View.ReadInt32 (index_offset); name_length = LittleEndian.ToInt32 (index, index_offset);
if (0 == name_length) if (0 == name_length)
break; break;
if (name_length > index_size) if (name_length > index_size)
return null; return null;
index_offset += 5 + name_length; index_offset += 5 + name_length;
} }
name_length = file.View.ReadInt32 (index_offset); name_length = LittleEndian.ToInt32 (index, index_offset);
if (0 == name_length) if (0 == name_length)
break; break;
if (name_length > index_size) if (name_length > index_size)
return null; return null;
if (name_length > index_buffer.Length) index_offset += 5;
index_buffer = new byte[name_length];
file.View.Read (index_offset+5, index_buffer, 0, (uint)name_length);
index_offset += 5 + name_length;
byte checksum; byte checksum;
var name = DecryptName (index_buffer, name_length, out checksum); var name = DecryptName (index, index_offset, name_length, out checksum);
Entry entry; Entry entry;
if (4 == version) if (version >= 4)
{ {
entry = FormatCatalog.Instance.Create<PcsEntry> (name); var pcs_entry = FormatCatalog.Instance.Create<PcsEntry> (name);
(entry as PcsEntry).Key = checksum; entry = pcs_entry;
pcs_entry.Key = checksum;
if (6 == version)
pcs_entry.NameHash = ComputeHash (index, index_offset, name_length-1);
index_offset += name_length;
int c = -1 - checksum; int c = -1 - checksum;
file.View.Read (index_offset, index_buffer, 0, 8);
for (int j = 0; j < 4; ++j) for (int j = 0; j < 4; ++j)
{ {
byte key = (byte)((checksum + (17 << j)) & 0x33); byte key = (byte)((checksum + (17 << j)) & 0x33);
index_buffer[j] = (byte)(c + key - index_buffer[j]); index[index_offset+j] = (byte)(c + key - index[index_offset+j]);
index_buffer[j+4] = (byte)(c + key - index_buffer[j+4]); index[index_offset+j+4] = (byte)(c + key - index[index_offset+j+4]);
} }
entry.Offset = LittleEndian.ToUInt32 (index_buffer, 0);
entry.Size = LittleEndian.ToUInt32 (index_buffer, 4);
} }
else else
{ {
index_offset += name_length;
entry = FormatCatalog.Instance.Create<Entry> (name); entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = file.View.ReadUInt32 (index_offset);
entry.Size = file.View.ReadUInt32 (index_offset+4);
} }
index_offset += 0x10;
if (index_offset > data_offset) if (index_offset > data_offset)
return null; return null;
entry.Offset += data_offset; entry.Offset = LittleEndian.ToUInt32 (index, index_offset) + data_offset;
entry.Size = LittleEndian.ToUInt32 (index, index_offset+4);
if (!entry.CheckPlacement (file.MaxOffset)) if (!entry.CheckPlacement (file.MaxOffset))
return null; return null;
dir.Add (entry); dir.Add (entry);
index_offset += 0x10;
} }
if (0 == dir.Count) if (0 == dir.Count)
return null; return null;
return new ArcFile (file, this, dir); return new PcsArchive (file, this, dir, version);
} }
public override Stream OpenEntry (ArcFile arc, Entry entry) public override Stream OpenEntry (ArcFile arc, Entry entry)
{ {
var pent = entry as PcsEntry; var pent = entry as PcsEntry;
if (null == pent) var parc = arc as PcsArchive;
if (null == pent || null == parc)
return base.OpenEntry (arc, entry); return base.OpenEntry (arc, entry);
uint header_size = Math.Min (entry.Size, 512u); uint header_size = Math.Min (entry.Size, 512u);
var header = arc.File.View.ReadBytes (entry.Offset, header_size); var header = arc.File.View.ReadBytes (entry.Offset, header_size);
if (6 == parc.Version)
{
header = ShuffleBlocks (header, pent.NameHash);
}
for (int i = 0; i < header.Length; ++i) for (int i = 0; i < header.Length; ++i)
{ {
header[i] = (byte)(pent.Key - header[i] - 1); header[i] = (byte)(pent.Key - header[i] - 1);
@ -134,18 +153,46 @@ namespace GameRes.Formats.CsWare
return new PrefixStream (header, rest); return new PrefixStream (header, rest);
} }
string DecryptName (byte[] name_buffer, int length, out byte checksum) string DecryptName (byte[] name_buffer, int offset, int length, out byte checksum)
{ {
int count; int count;
checksum = 0; checksum = 0;
for (count = 0; count < length; ++count) for (count = 0; count < length; ++count)
{ {
if (0 == name_buffer[count]) if (0 == name_buffer[offset+count])
break; break;
name_buffer[count] = Binary.RotByteL (name_buffer[count], 4); name_buffer[offset+count] = Binary.RotByteL (name_buffer[offset+count], 4);
checksum += name_buffer[count]; checksum += name_buffer[offset+count];
} }
return Encodings.cp932.GetString (name_buffer, 0, count); return Encodings.cp932.GetString (name_buffer, offset, count);
}
byte[] ShuffleBlocks (byte[] input, uint key)
{
int block_size = input.Length >> 5;
var output = new byte[input.Length];
var twister = new MersenneTwister (key);
int copied_sections = 0;
for (int i = 0; i < 0x20; ++i)
{
int j = (int)(twister.Rand() & 0x1F);
while (0 != (copied_sections & (1 << j)))
j = (j + 1) & 0x1F;
copied_sections |= 1 << j;
Buffer.BlockCopy (input, j * block_size, output, i * block_size, block_size);
}
int shuffled = block_size << 5;
if (shuffled != input.Length)
Buffer.BlockCopy (input, shuffled, output, shuffled, input.Length-shuffled);
return output;
}
static readonly Lazy<HashAlgorithm> SHA1 = new Lazy<HashAlgorithm> (() => System.Security.Cryptography.SHA1.Create());
uint ComputeHash (byte[] data, int offset, int length)
{
var hash = SHA1.Value.ComputeHash (data, offset, length);
return BigEndian.ToUInt32 (hash, 0);
} }
} }
} }

View File

@ -663,6 +663,7 @@ Mahokoi ~Ecchi na Mahou de Koi x Koi Shichau~<br/>
<tr class="odd"><td>*.alp</td><td><tt>AP-2</tt></td><td>No</td></tr> <tr class="odd"><td>*.alp</td><td><tt>AP-2</tt></td><td>No</td></tr>
<tr class="odd"><td>*.anm</td><td><tt>AN00</tt></td><td>No</td></tr> <tr class="odd"><td>*.anm</td><td><tt>AN00</tt></td><td>No</td></tr>
<tr><td>*.pcs</td><td><tt>PCCS</tt></td><td>No</td><td>C's ware</td><td> <tr><td>*.pcs</td><td><tt>PCCS</tt></td><td>No</td><td>C's ware</td><td>
Kuro to Kuro to Kuro no Saidan ~Kodoku~<br/>
Mikan<br/> Mikan<br/>
</td></tr> </td></tr>
<tr class="odd"><td>*.052<br/>*.055</td><td><tt>VAFSH</tt></td><td>No</td><td rowspan="2">Softpal</td><td rowspan="2"> <tr class="odd"><td>*.052<br/>*.055</td><td><tt>VAFSH</tt></td><td>No</td><td rowspan="2">Softpal</td><td rowspan="2">