chovy-sign/PspCrypto/DNASStream.cs

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;
}
}
}