637 lines
20 KiB
C#
637 lines
20 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace PspCrypto
|
|
{
|
|
public class DNASStream : Stream
|
|
{
|
|
internal static readonly byte[] DnasKeyBase =
|
|
{
|
|
0x2A, 0x05, 0x54, 0x40, 0x62, 0xD9, 0x1F, 0xE3, 0xF2, 0xD0, 0x2B, 0xC6, 0x21, 0xFF, 0x20, 0x0E,
|
|
0xB1, 0x44, 0x28, 0xDF, 0x0A, 0xCD, 0x14, 0x5B, 0xC8, 0x19, 0x36, 0x90, 0xD1, 0x42, 0x99, 0x2F
|
|
};
|
|
|
|
internal static readonly byte[] DnasKey1 =
|
|
{
|
|
0xED, 0xE2, 0x5D, 0x2D, 0xBB, 0xF8, 0x12, 0xE5, 0x3C, 0x5C, 0x59, 0x32, 0xFA, 0xE3, 0xE2, 0x43
|
|
};
|
|
|
|
internal static readonly byte[] DnasKey2 =
|
|
{
|
|
0x27, 0x74, 0xFB, 0xEB, 0xA4, 0xA0, 0x01, 0xD7, 0x02, 0x56, 0x9E, 0x33, 0x8C, 0x19, 0x57, 0x83
|
|
};
|
|
|
|
private static Memory<byte> _gMemory = new byte[0x640];
|
|
private static Memory<byte> _gCipherMemory = new byte[0x200];
|
|
|
|
private Stream _baseStream;
|
|
private byte[] _versionKey;
|
|
|
|
private long _position;
|
|
private long _pgdOffset;
|
|
private int _keyIndex;
|
|
private int _openFlag;
|
|
private long _dataOffset;
|
|
private long _tableOffset;
|
|
|
|
public int KeyIndex => _keyIndex;
|
|
|
|
private PgdDesc _desc;
|
|
|
|
internal unsafe struct PgdHeader
|
|
{
|
|
public uint Magic;
|
|
public int KeyIndex;
|
|
public int DrmType;
|
|
public int Unk12;
|
|
private fixed byte _descKey[0x10];
|
|
public Span<byte> DescKey
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _descKey)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
private fixed byte _keyHash[0x10];
|
|
public Span<byte> KeyHash
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _keyHash)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
private fixed byte _pgdDesc[0x30];
|
|
public Span<byte> PgdDesc
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _pgdDesc)
|
|
{
|
|
return new Span<byte>(ptr, 0x30);
|
|
}
|
|
}
|
|
}
|
|
private fixed byte _macTableHash[0x10];
|
|
public Span<byte> MacTableHash
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _macTableHash)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
private fixed byte _hash70[0x10];
|
|
public Span<byte> Hash70
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _hash70)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
private fixed byte _hash80[0x10];
|
|
public Span<byte> Hash80
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _hash80)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal unsafe struct PgdDesc
|
|
{
|
|
private fixed byte _key[0x10];
|
|
|
|
public Span<byte> Key
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _key)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
public int Version;
|
|
public int DataSize;
|
|
public int BlockSize;
|
|
public int DataOffset;
|
|
private fixed byte _unk20[0x10];
|
|
|
|
public Span<byte> Unk20
|
|
{
|
|
get
|
|
{
|
|
fixed (byte* ptr = _unk20)
|
|
{
|
|
return new Span<byte>(ptr, 0x10);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public int BlockSize => _desc.BlockSize;
|
|
|
|
public DNASStream(Stream stream, long pgdOffset, ReadOnlySpan<byte> versionKey, int flag = 2)
|
|
: this(stream, pgdOffset, versionKey.ToArray(), flag)
|
|
{
|
|
|
|
}
|
|
|
|
public DNASStream(Stream stream, long pgdOffset, byte[] versionKey = null, int flag = 2)
|
|
{
|
|
_baseStream = stream;
|
|
_pgdOffset = pgdOffset;
|
|
var offset = stream.Seek(pgdOffset, SeekOrigin.Begin);
|
|
if (offset != _pgdOffset)
|
|
{
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
Span<byte> hdr = _gMemory[..0x90].Span;
|
|
var size = stream.Read(hdr);
|
|
if (size != 0x90)
|
|
{
|
|
throw new ArgumentException("stream too small", nameof(stream));
|
|
}
|
|
var header = MemoryMarshal.AsRef<PgdHeader>(hdr);
|
|
_keyIndex = header.KeyIndex;
|
|
if (_keyIndex == 1)
|
|
{
|
|
_versionKey = versionKey ?? new byte[16];
|
|
}
|
|
else
|
|
{
|
|
if (versionKey == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(versionKey));
|
|
}
|
|
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<AMCTRL.MAC_KEY>()];
|
|
AMCTRL.sceDrmBBMacInit(mkey, 1);
|
|
AMCTRL.sceDrmBBMacUpdate(mkey, DnasKeyBase, (_keyIndex - 1) * 0x10);
|
|
AMCTRL.sceDrmBBMacFinal(mkey, _versionKey, versionKey);
|
|
return;
|
|
}
|
|
|
|
int macType;
|
|
int cipherType;
|
|
|
|
if (header.DrmType == 1)
|
|
{
|
|
flag |= 4;
|
|
macType = 1;
|
|
cipherType = 1;
|
|
if (header.KeyIndex > 1)
|
|
{
|
|
flag |= 0xc;
|
|
macType = 3;
|
|
}
|
|
}
|
|
else if (header.DrmType == 0 && (flag & 4) == 0)
|
|
{
|
|
macType = 2;
|
|
cipherType = 2;
|
|
}
|
|
else
|
|
{
|
|
throw new IOException();
|
|
}
|
|
|
|
byte[] dnasKey = null;
|
|
|
|
if ((flag & 2) != 0)
|
|
{
|
|
dnasKey = DnasKey1;
|
|
}
|
|
else if ((flag & 1) != 0)
|
|
{
|
|
dnasKey = DnasKey2;
|
|
}
|
|
|
|
if (dnasKey == null)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
|
|
var ret = CheckBBMac(hdr, 0x80, dnasKey, header.Hash80, macType);
|
|
if (ret != 0)
|
|
{
|
|
throw new IOException("Wrong MAC 0x80");
|
|
}
|
|
|
|
if (!Utils.IsEmpty(_versionKey, 0x10))
|
|
{
|
|
ret = CheckBBMac(hdr, 0x70, _versionKey, header.Hash70, macType);
|
|
}
|
|
else
|
|
{
|
|
ret = GetMacKey(hdr, 0x70, _versionKey, header.Hash70, macType);
|
|
}
|
|
|
|
if (ret != 0)
|
|
{
|
|
throw new IOException("Wrong MAC 0x70");
|
|
}
|
|
|
|
ret = DoBBCipher(header.PgdDesc, 0x30, 0, _versionKey, header.DescKey, cipherType);
|
|
if (ret != 0)
|
|
{
|
|
throw new IOException($"Error 0x{ret:X8}");
|
|
}
|
|
var desc = MemoryMarshal.AsRef<PgdDesc>(header.PgdDesc);
|
|
if (desc.Version != 0)
|
|
{
|
|
throw new IOException($"Error 0x{8051020:X8}");
|
|
}
|
|
|
|
if (desc.BlockSize != 0x400)
|
|
{
|
|
throw new IOException($"Error 0x{80510204:X8}");
|
|
}
|
|
|
|
_openFlag = flag | 0x10;
|
|
_desc = desc;
|
|
_dataOffset = _desc.DataOffset + pgdOffset;
|
|
var blockSize = desc.BlockSize;
|
|
var alignSize = (desc.DataSize + 15) & ~15;
|
|
var tableSize = ((alignSize + blockSize - 1) & ~(blockSize - 1)) / (blockSize / 16);
|
|
_tableOffset = pgdOffset + 0x90 + alignSize;
|
|
if (header.KeyIndex < 3 && 0x7ffff < tableSize)
|
|
{
|
|
|
|
}
|
|
{
|
|
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<AMCTRL.MAC_KEY>()];
|
|
ret = AMCTRL.sceDrmBBMacInit(mkey, macType);
|
|
stream.Seek(_tableOffset, SeekOrigin.Begin);
|
|
int read = 0;
|
|
if (tableSize != 0)
|
|
{
|
|
Span<byte> dataBuf = new byte[0x400];
|
|
do
|
|
{
|
|
var tmpSize = tableSize - read;
|
|
if (tmpSize > 0x400)
|
|
{
|
|
tmpSize = 0x400;
|
|
}
|
|
|
|
var data = dataBuf.Slice(0, tmpSize);
|
|
var readSize = stream.Read(data);
|
|
if (readSize != tmpSize)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
ret = AMCTRL.sceDrmBBMacUpdate(mkey, data, tmpSize);
|
|
if (ret != 0)
|
|
{
|
|
throw new Exception();
|
|
}
|
|
read += 0x400;
|
|
} while (read < tableSize);
|
|
}
|
|
ret = AMCTRL.sceDrmBBMacFinal2(mkey, header.MacTableHash, _versionKey);
|
|
if (ret != 0)
|
|
{
|
|
throw new IOException($"Error 0x{80510204:X8}");
|
|
}
|
|
}
|
|
|
|
_position = 0;
|
|
|
|
}
|
|
|
|
public override void Flush()
|
|
{
|
|
_baseStream.Flush();
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
int ret;
|
|
Span<byte> bufferSpan = buffer;
|
|
if ((_openFlag & 0x10) == 0)
|
|
{
|
|
throw new IOException($"Error 0x{80510207:X8}");
|
|
}
|
|
|
|
var dataSize = _desc.DataSize;
|
|
var seekOffset = _position;
|
|
if (seekOffset < dataSize)
|
|
{
|
|
var macType = 2;
|
|
var cipherType = 2;
|
|
if ((_openFlag & 4) != 0)
|
|
{
|
|
macType = 1;
|
|
if ((_openFlag & 8) != 0)
|
|
{
|
|
macType = 3;
|
|
}
|
|
|
|
cipherType = 1;
|
|
}
|
|
|
|
var endOffset = dataSize;
|
|
if (seekOffset + count <= dataSize)
|
|
{
|
|
endOffset = (int)(seekOffset + count);
|
|
}
|
|
|
|
var blockSize = _desc.BlockSize;
|
|
var alignOffset = (int)seekOffset;
|
|
var totalReadSize = 0;
|
|
while (true)
|
|
{
|
|
if (endOffset <= alignOffset)
|
|
{
|
|
return totalReadSize;
|
|
}
|
|
|
|
if ((_openFlag & 0x10) == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var align = alignOffset & blockSize - 1;
|
|
|
|
alignOffset -= align;
|
|
var alignBlockSize = endOffset - alignOffset;
|
|
int readBytes;
|
|
int uVar6;
|
|
Span<byte> readBuffer;
|
|
if (align == 0 && blockSize <= alignBlockSize)
|
|
{
|
|
readBytes = alignBlockSize & ~(blockSize - 1);
|
|
readBuffer = bufferSpan;
|
|
uVar6 = readBytes;
|
|
}
|
|
else
|
|
{
|
|
readBytes = blockSize;
|
|
readBuffer = _gMemory.Span;
|
|
if (dataSize < alignOffset + blockSize)
|
|
{
|
|
readBytes = (dataSize - alignOffset + 15) & ~15;
|
|
}
|
|
|
|
uVar6 = blockSize;
|
|
if (endOffset < alignOffset + blockSize)
|
|
{
|
|
uVar6 = alignBlockSize;
|
|
}
|
|
}
|
|
|
|
_baseStream.Seek(_dataOffset + alignOffset, SeekOrigin.Begin);
|
|
var realReadBytes = _baseStream.Read(readBuffer.Slice(0, readBytes));
|
|
if (realReadBytes < readBytes)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
|
|
var tableOffset = (alignOffset / blockSize) * 0x10;
|
|
_baseStream.Seek(_tableOffset + tableOffset, SeekOrigin.Begin);
|
|
var blockNr = 0;
|
|
if (readBytes != 0)
|
|
{
|
|
var tableReadOffset = 0;
|
|
var cipherSpan = _gCipherMemory.Span;
|
|
do
|
|
{
|
|
alignBlockSize = readBytes - tableReadOffset;
|
|
if (blockSize < readBytes - tableReadOffset)
|
|
{
|
|
alignBlockSize = blockSize;
|
|
}
|
|
|
|
if ((blockNr & 0x1f) == 0)
|
|
{
|
|
if (blockSize == 0)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
|
|
var tableBlock = readBytes / blockSize - blockNr;
|
|
if (tableBlock == 0)
|
|
{
|
|
tableBlock = 1;
|
|
}
|
|
|
|
if (0x20 < tableBlock)
|
|
{
|
|
tableBlock = 0x20;
|
|
}
|
|
cipherSpan.Fill(0);
|
|
realReadBytes = _baseStream.Read(cipherSpan[..(tableBlock * 16)]);
|
|
if (realReadBytes < tableBlock * 16)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
}
|
|
if (_keyIndex < 3)
|
|
{
|
|
ret = CheckBBMac(readBuffer[tableReadOffset..], alignBlockSize, _versionKey,
|
|
cipherSpan.Slice((blockNr & 0x1f) * 16), macType);
|
|
}
|
|
else
|
|
{
|
|
ret = CheckBBMac(readBuffer[tableReadOffset..], alignBlockSize, _versionKey,
|
|
cipherSpan[((blockNr & 0x1f) * 16)..], macType, alignOffset);
|
|
}
|
|
|
|
if (ret != 0)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
|
|
tableReadOffset += blockSize;
|
|
blockNr++;
|
|
} while (tableReadOffset < readBytes);
|
|
}
|
|
ret = DoBBCipher(readBuffer, readBytes, alignOffset + align >> 4, _versionKey, _desc.Key, cipherType);
|
|
|
|
if (ret != 0)
|
|
{
|
|
throw new IOException();
|
|
}
|
|
var iVar2 = uVar6 - align;
|
|
seekOffset += iVar2;
|
|
_position = seekOffset;
|
|
if (readBuffer != bufferSpan)
|
|
{
|
|
readBuffer.Slice(align, iVar2).CopyTo(bufferSpan);
|
|
}
|
|
bufferSpan = bufferSpan[iVar2..];
|
|
totalReadSize += iVar2;
|
|
alignOffset += uVar6;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
if ((_openFlag & 0x10) == 0)
|
|
{
|
|
throw new IOException($"Error 0x{80510206:X8}");
|
|
}
|
|
|
|
var dataSize = _desc.DataSize;
|
|
|
|
switch (origin)
|
|
{
|
|
case SeekOrigin.Begin:
|
|
break;
|
|
case SeekOrigin.Current:
|
|
offset += -_position;
|
|
break;
|
|
case SeekOrigin.End:
|
|
offset += dataSize;
|
|
break;
|
|
}
|
|
if (offset > 0xffffffff)
|
|
{
|
|
offset = 0xffffffff;
|
|
}
|
|
|
|
if (offset > dataSize)
|
|
{
|
|
offset = dataSize;
|
|
}
|
|
|
|
_position = offset;
|
|
|
|
return _position;
|
|
}
|
|
|
|
public override void SetLength(long value)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
internal static int CheckBBMac(ReadOnlySpan<byte> data, int size, ReadOnlySpan<byte> key, ReadOnlySpan<byte> hash, int macType, int seed = 0)
|
|
{
|
|
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<AMCTRL.MAC_KEY>()];
|
|
Span<byte> tmpKey = stackalloc byte[0x10];
|
|
int ret = unchecked((int)0x80510201);
|
|
if (hash != null)
|
|
{
|
|
ret = AMCTRL.sceDrmBBMacInit(mkey, macType);
|
|
if (ret != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = AMCTRL.sceDrmBBMacUpdate(mkey, data, size);
|
|
if (ret != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
key.CopyTo(tmpKey);
|
|
if (seed != 0)
|
|
{
|
|
var tmpXor = MemoryMarshal.Cast<byte, int>(tmpKey);
|
|
tmpXor[0] ^= seed;
|
|
}
|
|
|
|
ret = AMCTRL.sceDrmBBMacFinal2(mkey, hash, tmpKey);
|
|
if (ret != 0)
|
|
{
|
|
ret = unchecked((int)0x80510207);
|
|
}
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
internal static int GetMacKey(ReadOnlySpan<byte> data, int size, Span<byte> key, ReadOnlySpan<byte> hash, int macType, int seed = 0)
|
|
{
|
|
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<AMCTRL.MAC_KEY>()];
|
|
Span<byte> tmpKey = stackalloc byte[0x10];
|
|
int ret = unchecked((int)0x80510201);
|
|
if (hash != null)
|
|
{
|
|
ret = AMCTRL.sceDrmBBMacInit(mkey, macType);
|
|
if (ret != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = AMCTRL.sceDrmBBMacUpdate(mkey, data, size);
|
|
if (ret != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = AMCTRL.bbmac_getkey(mkey, hash, tmpKey);
|
|
if (ret != 0)
|
|
{
|
|
ret = unchecked((int)0x80510207);
|
|
}
|
|
if (seed != 0)
|
|
{
|
|
var tmpXor = MemoryMarshal.Cast<byte, int>(tmpKey);
|
|
tmpXor[0] ^= seed;
|
|
}
|
|
tmpKey.CopyTo(key);
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
internal static int DoBBCipher(Span<byte> data, int size, int seed, ReadOnlySpan<byte> versionKey,
|
|
ReadOnlySpan<byte> headerKey, int cipherType)
|
|
{
|
|
int ret = AMCTRL.sceDrmBBCipherInit(out var ckey, cipherType, 2, headerKey, versionKey, seed);
|
|
if (ret != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = AMCTRL.sceDrmBBCipherUpdate(ref ckey, data, size);
|
|
if (ret != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
ret = AMCTRL.sceDrmBBCipherFinal(ref ckey);
|
|
return ret;
|
|
}
|
|
|
|
public byte[] VersionKey => _versionKey;
|
|
|
|
public override bool CanRead => _baseStream.CanRead;
|
|
public override bool CanSeek => _baseStream.CanSeek;
|
|
public override bool CanWrite => false;
|
|
public override long Length => _desc.DataSize;
|
|
public override long Position
|
|
{
|
|
get => _position;
|
|
set => _position = value;
|
|
}
|
|
}
|
|
}
|