mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-27 15:44:00 +08:00
implemented encrypted Nitro+ archives creation.
This commit is contained in:
parent
8697257e79
commit
dc9f0ebb44
@ -29,6 +29,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.ComponentModel.Composition;
|
using System.ComponentModel.Composition;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using ZLibNet;
|
using ZLibNet;
|
||||||
using GameRes.Formats.Strings;
|
using GameRes.Formats.Strings;
|
||||||
@ -64,7 +65,7 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
{
|
{
|
||||||
NotEncrypted,
|
NotEncrypted,
|
||||||
CHAOSHEAD, CHAOSHEADTR1, CHAOSHEADTR2, MURAMASATR, MURAMASA, SUMAGA, DJANGO, DJANGOTR,
|
CHAOSHEAD, CHAOSHEADTR1, CHAOSHEADTR2, MURAMASATR, MURAMASA, SUMAGA, DJANGO, DJANGOTR,
|
||||||
LAMENTO, LAMENTOTR, 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
arcStrings.ArcNoEncryption,
|
arcStrings.ArcNoEncryption,
|
||||||
"Chaos;Head", "Chaos;Head Trial 1", "Chaos;Head Trial 2", "Muramasa Trial", "Muramasa",
|
"Chaos;Head", "Chaos;Head Trial 1", "Chaos;Head Trial 2", "Muramasa Trial", "Muramasa",
|
||||||
"Sumaga", "Zoku Satsuriku no Django", "Zoku Satsuriku no Django Trial", "Lamento",
|
"Sumaga", "Zoku Satsuriku no Django", "Zoku Satsuriku no Django Trial", "Lamento",
|
||||||
"Lamento Trial", "Sweet Pool", "Sumaga Special", "Demonbane", "MuramasaAD", "Axanael",
|
"Sweet Pool", "Sumaga Special", "Demonbane", "MuramasaAD", "Axanael",
|
||||||
"Kikokugai", "Sonicomi Trial 2", "Sumaga 3% Trial", "Sonicomi Version 1.0",
|
"Kikokugai", "Sonicomi Trial 2", "Sumaga 3% Trial", "Sonicomi Version 1.0",
|
||||||
"Guilty Crown Lost Xmas", "Guilty Crown Lost Xmas Trailer", "DRAMAtical Murder",
|
"Guilty Crown Lost Xmas", "Guilty Crown Lost Xmas Trailer", "DRAMAtical Murder",
|
||||||
"Kimi to Kanojo to Kanojo no Koi", "Phenomeno", "Nekoda -Nyanda-",
|
"Kimi to Kanojo to Kanojo no Koi", "Phenomeno", "Nekoda -Nyanda-",
|
||||||
@ -121,11 +122,7 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
if (encrypted)
|
if (encrypted)
|
||||||
game_id = QueryGameEncryption();
|
game_id = QueryGameEncryption();
|
||||||
|
|
||||||
int key;
|
int key = GetArchiveKey (game_id, key1, key2);
|
||||||
if (encrypted && (game_id == NpaTitleId.LAMENTO || game_id == NpaTitleId.LAMENTOTR))
|
|
||||||
key = key1 + key2;
|
|
||||||
else
|
|
||||||
key = key1 * key2;
|
|
||||||
|
|
||||||
long cur_offset = 41;
|
long cur_offset = 41;
|
||||||
var dir = new List<Entry> (file_count);
|
var dir = new List<Entry> (file_count);
|
||||||
@ -196,9 +193,14 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
throw new FileSizeException();
|
throw new FileSizeException();
|
||||||
entry.Offset = data_offset;
|
entry.Offset = data_offset;
|
||||||
entry.UnpackedSize = (uint)size;
|
entry.UnpackedSize = (uint)size;
|
||||||
|
Stream destination = output;
|
||||||
|
if (NpaTitleId.NotEncrypted != npa_options.TitleId)
|
||||||
|
destination = new EncryptedStream (output, entry, npa_options.TitleId, index.Key);
|
||||||
|
try
|
||||||
|
{
|
||||||
if (entry.IsPacked)
|
if (entry.IsPacked)
|
||||||
{
|
{
|
||||||
using (var zstream = new ZLibStream (output, CompressionMode.Compress,
|
using (var zstream = new ZLibStream (destination, CompressionMode.Compress,
|
||||||
CompressionLevel.Level9, true))
|
CompressionLevel.Level9, true))
|
||||||
{
|
{
|
||||||
file.CopyTo (zstream);
|
file.CopyTo (zstream);
|
||||||
@ -208,9 +210,15 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
file.CopyTo (output);
|
file.CopyTo (destination);
|
||||||
entry.Size = entry.UnpackedSize;
|
entry.Size = entry.UnpackedSize;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (destination is EncryptedStream)
|
||||||
|
destination.Dispose();
|
||||||
|
}
|
||||||
data_offset += entry.Size;
|
data_offset += entry.Size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,25 +240,31 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
header.Write (index.FileCount);
|
header.Write (index.FileCount);
|
||||||
header.Write ((long)0);
|
header.Write ((long)0);
|
||||||
header.Write (index.Size);
|
header.Write (index.Size);
|
||||||
|
int entry_number = 0;
|
||||||
foreach (var entry in index.Entries)
|
foreach (var entry in index.Entries)
|
||||||
{
|
{
|
||||||
header.Write (entry.RawName.Length);
|
header.Write (entry.RawName.Length);
|
||||||
header.Write (entry.RawName);
|
for (int i = 0; i < entry.RawName.Length; ++i)
|
||||||
|
{
|
||||||
|
header.Write ((byte)(entry.RawName[i] - DecryptName (i, entry_number, index.Key)));
|
||||||
|
}
|
||||||
header.Write ((byte)("directory" == entry.Type ? 1 : 2));
|
header.Write ((byte)("directory" == entry.Type ? 1 : 2));
|
||||||
header.Write (entry.FolderId);
|
header.Write (entry.FolderId);
|
||||||
header.Write ((uint)entry.Offset);
|
header.Write ((uint)entry.Offset);
|
||||||
header.Write (entry.Size);
|
header.Write (entry.Size);
|
||||||
header.Write (entry.UnpackedSize);
|
header.Write (entry.UnpackedSize);
|
||||||
|
++entry_number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||||
{
|
{
|
||||||
|
Stream input;
|
||||||
if (arc is NpaArchive && entry is NpaEntry)
|
if (arc is NpaArchive && entry is NpaEntry)
|
||||||
return OpenEncryptedEntry (arc as NpaArchive, entry as NpaEntry);
|
input = new EncryptedStream (arc as NpaArchive, entry as NpaEntry);
|
||||||
|
else
|
||||||
var input = arc.File.CreateStream (entry.Offset, entry.Size);
|
input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||||
return UnpackEntry (input, entry as PackedEntry);
|
return UnpackEntry (input, entry as PackedEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,56 +275,7 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream OpenEncryptedEntry (NpaArchive arc, NpaEntry entry)
|
internal static byte DecryptName (int index, int curfile, int arc_key)
|
||||||
{
|
|
||||||
int key = GetKeyFromEntry (entry, arc.GameId, arc.Key);
|
|
||||||
int encrypted_length = 0x1000;
|
|
||||||
|
|
||||||
if (arc.GameId != NpaTitleId.LAMENTO && arc.GameId != NpaTitleId.LAMENTOTR)
|
|
||||||
encrypted_length += entry.RawName.Length;
|
|
||||||
if (encrypted_length > entry.Size)
|
|
||||||
encrypted_length = (int)entry.Size;
|
|
||||||
|
|
||||||
using (var view = arc.File.CreateViewAccessor (entry.Offset, entry.Size))
|
|
||||||
{
|
|
||||||
byte[] buffer = new byte[entry.Size];
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
byte* src = view.GetPointer (entry.Offset);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int x;
|
|
||||||
for (x = 0; x < encrypted_length; x++)
|
|
||||||
{
|
|
||||||
if (arc.GameId == NpaTitleId.LAMENTO || arc.GameId == NpaTitleId.LAMENTOTR)
|
|
||||||
{
|
|
||||||
buffer[x] = (byte)(arc.KeyTable[src[x]] - key);
|
|
||||||
}
|
|
||||||
else if (arc.GameId == NpaTitleId.TOTONO)
|
|
||||||
{
|
|
||||||
byte r = src[x];
|
|
||||||
r = arc.KeyTable[r];
|
|
||||||
r = arc.KeyTable[r];
|
|
||||||
r = arc.KeyTable[r];
|
|
||||||
r = (byte)~r;
|
|
||||||
buffer[x] = (byte)((sbyte)r - key - x);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buffer[x] = (byte)(arc.KeyTable[src[x]] - key - x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (x != entry.Size)
|
|
||||||
Marshal.Copy ((IntPtr)(src+x), buffer, x, (int)(entry.Size-x));
|
|
||||||
} finally {
|
|
||||||
view.SafeMemoryMappedViewHandle.ReleasePointer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return UnpackEntry (new MemoryStream (buffer, false), entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte DecryptName (int index, int curfile, int arc_key)
|
|
||||||
{
|
{
|
||||||
int key = 0xFC*index;
|
int key = 0xFC*index;
|
||||||
|
|
||||||
@ -327,9 +292,17 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
return (byte)(key & 0xff);
|
return (byte)(key & 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static byte GetKeyFromEntry (NpaEntry entry, NpaTitleId game_id, int key2)
|
internal static int GetArchiveKey (NpaTitleId game_id, int key1, int key2)
|
||||||
{
|
{
|
||||||
int key1;
|
if (NpaTitleId.LAMENTO == game_id)
|
||||||
|
return key1 + key2;
|
||||||
|
else
|
||||||
|
return key1 * key2;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static byte GetKeyFromEntry (NpaEntry entry, NpaTitleId game_id, int arc_key)
|
||||||
|
{
|
||||||
|
int key;
|
||||||
switch (game_id)
|
switch (game_id)
|
||||||
{
|
{
|
||||||
case NpaTitleId.AXANAEL:
|
case NpaTitleId.AXANAEL:
|
||||||
@ -339,28 +312,27 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
case NpaTitleId.LOSTX:
|
case NpaTitleId.LOSTX:
|
||||||
case NpaTitleId.DRAMATICALMURDER:
|
case NpaTitleId.DRAMATICALMURDER:
|
||||||
case NpaTitleId.PHENOMENO:
|
case NpaTitleId.PHENOMENO:
|
||||||
key1 = 0x20101118;
|
key = 0x20101118;
|
||||||
break;
|
break;
|
||||||
case NpaTitleId.TOTONO:
|
case NpaTitleId.TOTONO:
|
||||||
key1 = 0x12345678;
|
key = 0x12345678;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
key1 = unchecked((int)0x87654321);
|
key = unchecked((int)0x87654321);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var name = entry.RawName;
|
var name = entry.RawName;
|
||||||
int i;
|
for (int i = 0; i < name.Length; ++i)
|
||||||
for (i = 0; i < name.Length; ++i)
|
key -= name[i];
|
||||||
key1 -= name[i];
|
|
||||||
|
|
||||||
int key = key1 * i;
|
key *= name.Length;
|
||||||
|
|
||||||
if (game_id != NpaTitleId.LAMENTO && game_id != NpaTitleId.LAMENTOTR) // if the game is not Lamento
|
if (game_id != NpaTitleId.LAMENTO) // if the game is not Lamento
|
||||||
{
|
{
|
||||||
key += key2;
|
key += arc_key;
|
||||||
key *= (int)entry.UnpackedSize;
|
key *= (int)entry.UnpackedSize;
|
||||||
}
|
}
|
||||||
return (byte)(key & 0xff);
|
return (byte)key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] GenerateKeyTable (NpaTitleId title_id)
|
public static byte[] GenerateKeyTable (NpaTitleId title_id)
|
||||||
@ -383,14 +355,26 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
var eax = BaseTable[i];
|
var eax = BaseTable[i];
|
||||||
table[eax] = (byte)(edx & 0xff);
|
table[eax] = (byte)(edx & 0xff);
|
||||||
}
|
}
|
||||||
for (int i = 16; i+1 < order.Length; i+=2)
|
for (int i = 17; i < order.Length; i+=2)
|
||||||
{
|
{
|
||||||
int ecx = order[i];
|
int ecx = order[i-1];
|
||||||
int edx = order[i+1];
|
int edx = order[i];
|
||||||
byte tmp = table[ecx];
|
byte tmp = table[ecx];
|
||||||
table[ecx] = table[edx];
|
table[ecx] = table[edx];
|
||||||
table[edx] = tmp;
|
table[edx] = tmp;
|
||||||
}
|
}
|
||||||
|
if (NpaTitleId.TOTONO == title_id)
|
||||||
|
{
|
||||||
|
var totono_table = new byte[256];
|
||||||
|
for (int i = 0; i < 256; ++i)
|
||||||
|
{
|
||||||
|
byte r = table[i];
|
||||||
|
r = table[r];
|
||||||
|
r = table[r];
|
||||||
|
totono_table[i] = (byte)~r;
|
||||||
|
}
|
||||||
|
table = totono_table;
|
||||||
|
}
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,6 +406,8 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
|
|
||||||
public static NpaTitleId GetTitleId (string title)
|
public static NpaTitleId GetTitleId (string title)
|
||||||
{
|
{
|
||||||
|
Debug.Assert (KnownSchemes.Length == OrderTable.Length,
|
||||||
|
"Number of known encryptions schemes does not match available order tables.");
|
||||||
var index = Array.IndexOf (KnownSchemes, title);
|
var index = Array.IndexOf (KnownSchemes, title);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
return (NpaTitleId)index;
|
return (NpaTitleId)index;
|
||||||
@ -468,8 +454,6 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
new byte[] { 0xed,0xee,0xee,0xef,0xed,0xee,0xee,0xee,0xfe,0xde,0xee,0xef,0xed,0xee,0xfe,0xdf,0x1e,0x4e,0x66,0xb6 },
|
new byte[] { 0xed,0xee,0xee,0xef,0xed,0xee,0xee,0xee,0xfe,0xde,0xee,0xef,0xed,0xee,0xfe,0xdf,0x1e,0x4e,0x66,0xb6 },
|
||||||
// LAMENTO
|
// LAMENTO
|
||||||
new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 },
|
new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 },
|
||||||
// LAMENTOTR
|
|
||||||
new byte[] { 0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0xee,0x1e,0x4e,0x66,0xb6 },
|
|
||||||
// SWEETPOOL
|
// SWEETPOOL
|
||||||
new byte[] { 0x38,0x9c,0x2a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8a,0x8b,0x8b,0xae,0xae,0xae,0xa8,0xa8 },
|
new byte[] { 0x38,0x9c,0x2a,0x8b,0x8b,0x8b,0x8b,0x8c,0x8a,0x8b,0x8b,0xae,0xae,0xae,0xa8,0xa8 },
|
||||||
// SUMAGASP
|
// SUMAGASP
|
||||||
@ -526,15 +510,13 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
public Indexer (IEnumerable<Entry> source_list, NpaOptions options)
|
public Indexer (IEnumerable<Entry> source_list, NpaOptions options)
|
||||||
{
|
{
|
||||||
m_entries = new List<NpaEntry> (source_list.Count());
|
m_entries = new List<NpaEntry> (source_list.Count());
|
||||||
var game_id = options.TitleId;
|
m_key = NpaOpener.GetArchiveKey (options.TitleId, options.Key1, options.Key2);
|
||||||
if (game_id == NpaTitleId.LAMENTO || game_id == NpaTitleId.LAMENTOTR)
|
|
||||||
m_key = options.Key1 + options.Key2;
|
|
||||||
else
|
|
||||||
m_key = options.Key1 * options.Key2;
|
|
||||||
|
|
||||||
foreach (var entry in source_list)
|
foreach (var entry in source_list)
|
||||||
{
|
{
|
||||||
string name = entry.Name;
|
string name = entry.Name;
|
||||||
|
try
|
||||||
|
{
|
||||||
var dir = Path.GetDirectoryName (name);
|
var dir = Path.GetDirectoryName (name);
|
||||||
int folder_id = 0;
|
int folder_id = 0;
|
||||||
if (!string.IsNullOrEmpty (dir))
|
if (!string.IsNullOrEmpty (dir))
|
||||||
@ -547,28 +529,18 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
IsPacked = compress,
|
IsPacked = compress,
|
||||||
RawName = EncodeName (name, m_entries.Count),
|
RawName = m_encoding.GetBytes (name),
|
||||||
FolderId = folder_id,
|
FolderId = folder_id,
|
||||||
};
|
};
|
||||||
++m_file_count;
|
++m_file_count;
|
||||||
AddEntry (npa_entry);
|
AddEntry (npa_entry);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
byte[] EncodeName (string name, int entry_number)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
byte[] raw_name = m_encoding.GetBytes (name);
|
|
||||||
for (int i = 0; i < name.Length; ++i)
|
|
||||||
raw_name[i] -= NpaOpener.DecryptName (i, entry_number, m_key);
|
|
||||||
return raw_name;
|
|
||||||
}
|
|
||||||
catch (EncoderFallbackException X)
|
catch (EncoderFallbackException X)
|
||||||
{
|
{
|
||||||
throw new InvalidFileName (name, arcStrings.MsgIllegalCharacters, X);
|
throw new InvalidFileName (name, arcStrings.MsgIllegalCharacters, X);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AddEntry (NpaEntry entry)
|
void AddEntry (NpaEntry entry)
|
||||||
{
|
{
|
||||||
@ -600,12 +572,188 @@ namespace GameRes.Formats.NitroPlus
|
|||||||
Size = 0,
|
Size = 0,
|
||||||
UnpackedSize = 0,
|
UnpackedSize = 0,
|
||||||
IsPacked = false,
|
IsPacked = false,
|
||||||
RawName = EncodeName (path, m_entries.Count),
|
RawName = m_encoding.GetBytes (path),
|
||||||
FolderId = folder_id,
|
FolderId = folder_id,
|
||||||
};
|
};
|
||||||
m_entries.Add (npa_entry);
|
AddEntry (npa_entry);
|
||||||
}
|
}
|
||||||
return folder_id;
|
return folder_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stream class for files stored in encrypted NPA archives.
|
||||||
|
/// </summary>
|
||||||
|
internal class EncryptedStream : Stream
|
||||||
|
{
|
||||||
|
private Stream m_stream;
|
||||||
|
private Lazy<byte[]> m_encrypted;
|
||||||
|
private int m_encrypted_length;
|
||||||
|
private bool m_read_mode;
|
||||||
|
private long m_base_pos;
|
||||||
|
|
||||||
|
public override bool CanRead { get { return m_read_mode; } }
|
||||||
|
public override bool CanSeek { get { return m_stream.CanSeek; } }
|
||||||
|
public override bool CanWrite { get { return !m_read_mode; } }
|
||||||
|
public override long Length { get { return m_stream.Length - m_base_pos; } }
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { return m_stream.Position - m_base_pos; }
|
||||||
|
set { m_stream.Position = m_base_pos + value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate byte CryptFunc (int index, byte value);
|
||||||
|
CryptFunc Encrypt;
|
||||||
|
|
||||||
|
public EncryptedStream (NpaArchive arc, NpaEntry entry)
|
||||||
|
{
|
||||||
|
m_read_mode = true;
|
||||||
|
m_encrypted_length = GetEncryptedLength (entry, arc.GameId);
|
||||||
|
int key = NpaOpener.GetKeyFromEntry (entry, arc.GameId, arc.Key);
|
||||||
|
|
||||||
|
m_stream = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||||
|
m_encrypted = new Lazy<byte[]> (() => InitEncrypted (key, arc.GameId, arc.KeyTable));
|
||||||
|
m_base_pos = m_stream.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptedStream (Stream output, NpaEntry entry, NpaTitleId game_id, int arc_key)
|
||||||
|
{
|
||||||
|
m_read_mode = false;
|
||||||
|
m_encrypted_length = GetEncryptedLength (entry, game_id);
|
||||||
|
int key = NpaOpener.GetKeyFromEntry (entry, game_id, arc_key);
|
||||||
|
|
||||||
|
m_stream = output;
|
||||||
|
m_encrypted = new Lazy<byte[]> (() => new byte[m_encrypted_length]);
|
||||||
|
m_base_pos = m_stream.Position;
|
||||||
|
|
||||||
|
byte[] decrypt_table = NpaOpener.GenerateKeyTable (game_id);
|
||||||
|
byte[] encrypt_table = new byte[256];
|
||||||
|
for (int i = 0; i < 256; ++i)
|
||||||
|
encrypt_table[decrypt_table[i]] = (byte)i;
|
||||||
|
|
||||||
|
if (NpaTitleId.LAMENTO == game_id)
|
||||||
|
{
|
||||||
|
Encrypt = (i, x) => encrypt_table[(x + key) & 0xff];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Encrypt = (i, x) => encrypt_table[(x + key + i) & 0xff];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetEncryptedLength (NpaEntry entry, NpaTitleId game_id)
|
||||||
|
{
|
||||||
|
int length = 0x1000;
|
||||||
|
if (game_id != NpaTitleId.LAMENTO)
|
||||||
|
length += entry.RawName.Length;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] InitEncrypted (int key, NpaTitleId game_id, byte[] key_table)
|
||||||
|
{
|
||||||
|
var position = Position;
|
||||||
|
if (0 != position)
|
||||||
|
Position = 0;
|
||||||
|
byte[] buffer = new byte[m_encrypted_length];
|
||||||
|
m_encrypted_length = m_stream.Read (buffer, 0, m_encrypted_length);
|
||||||
|
Position = position;
|
||||||
|
|
||||||
|
if (game_id == NpaTitleId.LAMENTO)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_encrypted_length; i++)
|
||||||
|
buffer[i] = (byte)(key_table[buffer[i]] - key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_encrypted_length; i++)
|
||||||
|
buffer[i] = (byte)(key_table[buffer[i]] - key - i);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region System.IO.Stream methods
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
m_stream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek (long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
if (SeekOrigin.Begin == origin)
|
||||||
|
offset += m_base_pos;
|
||||||
|
offset = m_stream.Seek (offset, origin);
|
||||||
|
return offset - m_base_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength (long length)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException ("EncryptedStream.SetLength is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read (byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
var position = Position;
|
||||||
|
if (position >= m_encrypted_length)
|
||||||
|
return m_stream.Read (buffer, offset, count);
|
||||||
|
int read = Math.Min (m_encrypted_length - (int)position, count);
|
||||||
|
Array.Copy (m_encrypted.Value, (int)position, buffer, offset, read);
|
||||||
|
m_stream.Seek (read, SeekOrigin.Current);
|
||||||
|
if (read < count)
|
||||||
|
{
|
||||||
|
read += m_stream.Read (buffer, offset+read, count-read);
|
||||||
|
}
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int ReadByte ()
|
||||||
|
{
|
||||||
|
var position = Position;
|
||||||
|
if (position >= m_encrypted_length)
|
||||||
|
return m_stream.ReadByte();
|
||||||
|
m_stream.Seek (1, SeekOrigin.Current);
|
||||||
|
return m_encrypted.Value[(int)position];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write (byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
var position = Position;
|
||||||
|
if (position < m_encrypted_length)
|
||||||
|
{
|
||||||
|
int limit = (int)position + Math.Min (m_encrypted_length - (int)position, count);
|
||||||
|
for (int i = (int)position; i < limit; ++i, ++offset, --count)
|
||||||
|
{
|
||||||
|
m_encrypted.Value[i] = Encrypt (i, buffer[offset]);
|
||||||
|
}
|
||||||
|
m_stream.Write (m_encrypted.Value, (int)position, limit-(int)position);
|
||||||
|
}
|
||||||
|
if (count > 0)
|
||||||
|
m_stream.Write (buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteByte (byte value)
|
||||||
|
{
|
||||||
|
var position = Position;
|
||||||
|
if (position < m_encrypted_length)
|
||||||
|
value = Encrypt ((int)position, value);
|
||||||
|
m_stream.WriteByte (value);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable Members
|
||||||
|
bool disposed = false;
|
||||||
|
protected override void Dispose (bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposed)
|
||||||
|
{
|
||||||
|
if (disposing && m_read_mode)
|
||||||
|
{
|
||||||
|
m_stream.Dispose();
|
||||||
|
}
|
||||||
|
m_encrypted = null;
|
||||||
|
disposed = true;
|
||||||
|
base.Dispose (disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user