chovy-sign/PbpResign/Program.cs

1356 lines
49 KiB
C#

using CommunityToolkit.HighPerformance;
using Ionic.Zlib;
using PspCrypto;
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
using Vita.ContentManager;
namespace PbpResign
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PbpHeader
{
public int Sig;
public int Ver;
public int ParamOff;
public int Icon0Off;
public int Icon1Off;
public int Pic0Off;
public int Pic1Off;
public int Snd0Off;
public int DataPspOff;
public int DataPsarOff;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct NpUmdImgBody
{
public ushort SectorSize; // 0x0800
public ushort Unk2; // 0xE000
public uint Unk4;
public uint Unk8;
public uint Unk12;
public uint Unk16;
public uint LbaStart;
public uint Unk24;
public uint NSectors;
public uint Unk32;
public uint LbaEnd;
public uint Unk40;
public uint BlockEntryOffset;
private fixed byte discId_[0x10];
public Span<byte> DiscId
{
get
{
fixed (byte* ptr = discId_)
{
return new Span<byte>(ptr, 0x10);
}
}
}
public ushort HeaderStartOffset;
public ushort HeaderStartOffset1;
public uint ThreadPriority;
public byte Unk72;
public byte BBMacParam;
public byte Unk74;
public byte Unk75;
public uint Unk76;
public uint Unk80;
public uint Unk84;
public uint Unk88;
public uint Unk92;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct NpUmdImgHdr
{
public int Magic0;
public int Magic1;
public int NpFlags;
public int BlockBasis;
private fixed byte contentId_[0x30];
public Span<byte> ContentId
{
get
{
fixed (byte* ptr = contentId_)
{
return new Span<byte>(ptr, 0x30);
}
}
}
public NpUmdImgBody Body;
private fixed byte headerKey_[0x10];
public Span<byte> HeaderKey
{
get
{
fixed (byte* ptr = headerKey_)
{
return new Span<byte>(ptr, 0x10);
}
}
}
private fixed byte dataKey_[0x10];
public Span<byte> DataKey
{
get
{
fixed (byte* ptr = dataKey_)
{
return new Span<byte>(ptr, 0x10);
}
}
}
private fixed byte headerHash_[0x10];
public Span<byte> HeaderHash
{
get
{
fixed (byte* ptr = headerHash_)
{
return new Span<byte>(ptr, 0x10);
}
}
}
private fixed byte Pad[0x8];
private fixed byte eCDsaSig_[0x28];
public Span<byte> ECDsaSig
{
get
{
fixed (byte* ptr = eCDsaSig_)
{
return new Span<byte>(ptr, 0x28);
}
}
}
}
unsafe struct NpBlock
{
private fixed byte mac_[0x10];
public Span<byte> Mac
{
get
{
fixed (byte* ptr = mac_)
{
return new Span<byte>(ptr, 0x10);
}
}
}
public int Offset;
public int Size;
public int Unk1;
public int Unk2;
}
[StructLayout(LayoutKind.Sequential)]
struct CDDA_ENTRY
{
public int offset;
public int size;
public int padding;
public int key;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct ISO_ENTRY
{
public int offset;
public ushort size;
public short marker; // 0x01 or 0x00
private fixed byte checksum_[0x10]; // First 0x10 bytes of sha1 sum of 0x10 disc sectors
public Span<byte> checksum
{
get
{
fixed (byte* ptr = checksum_)
{
return new Span<byte>(ptr, 0x10);
}
}
}
public fixed byte padding[0x8];
}
[StructLayout(LayoutKind.Sequential)]
struct STARTDAT_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] magic; // STARTDAT
public uint unk1; // 0x01
public uint unk2; // 0x01
public int header_size;
public int data_size;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct SIMPLE_HEADER
{
private fixed byte magic_[8]; // SIMPLE__
public Span<byte> magic
{
get
{
fixed (byte* ptr = magic_)
{
return new Span<byte>(ptr, 8);
}
}
}
public uint unk1; // 0x64
public uint unk2; // 0x01
public int data_size;
public int unk3; // 0 or chcksm
public int unk4; // 0 or chcksm
}
class Program
{
private static byte[] Idps = { 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
private static string CId = "JP0177-NPJH50145_00-VALKYRIA2DLC002B";
private static KeyGen _keyGen;
private static readonly Memory<byte> VersionKey = new byte[16];
private static readonly Memory<byte> NewVersionKey = new byte[16];
//struct VersionKey
//{
// public byte[] Fixed;
// public byte[] Type2;
// public byte[] Type3;
//}
//private static readonly Dictionary<string, VersionKey> Keys = new()
//{
// {
// "JP0177-NPJH50145_00-VALKYRIA2DLC002B",
// new VersionKey
// {
// Fixed = new byte[] { 0x38, 0x20, 0xD0, 0x11, 0x07, 0xA3, 0xFF, 0x3E, 0x0A, 0x4C, 0x20, 0x85, 0x39, 0x10, 0xB5, 0x54 },
// Type2 = new byte[] { 0x80, 0x2C, 0x03, 0xB8, 0xB9, 0x1E, 0xB6, 0xF8, 0xE8, 0xF6, 0xB8, 0x54, 0xAD, 0x8C, 0x0E, 0x25 },
// Type3 = new byte[] { 0x90, 0xC2, 0x03, 0x02, 0x27, 0x90, 0x7C, 0x0C, 0x7A, 0xCD, 0x83, 0x30, 0x28, 0x13, 0x90, 0x83 }
// }
// },
// {
// "EP9000-NPEG00005_00-0000000000000001",
// new VersionKey
// {
// Fixed = new byte[] { 0x5A, 0xB0, 0xB5, 0xE2, 0xC3, 0x2E, 0xE3, 0xBA, 0xFE, 0xF8, 0x0A, 0xDE, 0x35, 0xBD, 0x78, 0x88 },
// Type2 = new byte[] { 0x5A, 0xB0, 0xB5, 0xE2, 0xC3, 0x2E, 0xE3, 0xBA, 0xFE, 0xF8, 0x0A, 0xDE, 0x35, 0xBD, 0x78, 0x88 },
// Type3 = new byte[] { 0x0B, 0x84, 0x50, 0xE0, 0x63, 0x52, 0x36, 0x74, 0x01, 0x1C, 0x6B, 0x2B, 0x94, 0x82, 0x9F, 0x7A }
// }
// }
//};
static readonly byte[] multi_iso_magic = {
0x50, // P
0x53, // S
0x54, // T
0x49, // I
0x54, // T
0x4C, // L
0x45, // E
0x49, // I
0x4D, // M
0x47, // G
0x30, // 0
0x30, // 0
0x30, // 0
0x30, // 0
0x30, // 0
0x30 // 0
};
static readonly byte[] iso_magic = {
0x50, // P
0x53, // S
0x49, // I
0x53, // S
0x4F, // O
0x49, // I
0x4D, // M
0x47, // G
0x30, // 0
0x30, // 0
0x30, // 0
0x30 // 0
};
static T ReadStruct<T>(BinaryReader reader) where T : struct
{
byte[] buff = reader.ReadBytes(Marshal.SizeOf<T>());
GCHandle handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
T t = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
handle.Free();
return t;
}
static T ReadStruct<T>(Stream stream) where T : struct
{
Span<byte> buff = stackalloc byte[Unsafe.SizeOf<T>()];
stream.Read(buff);
T t = MemoryMarshal.AsRef<T>(buff);
return t;
}
static bool CopyNormalData(Stream input, Stream output, int offset, int size)
{
if (size == 0)
{
return true;
}
if (offset + size < input.Length)
{
input.Seek(offset, SeekOrigin.Begin);
var buff = new byte[size];
var len = input.Read(buff);
if (len != size)
{
return false;
}
output.Seek(offset, SeekOrigin.Begin);
output.Write(buff);
return true;
}
return false;
}
static void XorTable(Span<uint> tp)
{
tp[4] ^= tp[3] ^ tp[2];
tp[5] ^= tp[2] ^ tp[1];
tp[6] ^= tp[0] ^ tp[3];
tp[7] ^= tp[1] ^ tp[0];
}
static bool CopyNpUmdImg(Stream input, Stream output, PbpHeader pbpHdr, Span<byte> psarBuff, NpUmdImgHdr npHdr)
{
Span<byte> buff = stackalloc byte[0x800]; //
int len;
Span<byte> digest = stackalloc byte[0x14];
SceDdrdb.sceDdrdbHash(psarBuff, 0xd8, digest);
Span<byte> point = stackalloc byte[Marshal.SizeOf<KIRKEngine.ECDSA_POINT>()];
KeyVault.Px2.AsSpan().CopyTo(point);
KeyVault.Py2.AsSpan().CopyTo(point[0x14..]);
var ret = SceDdrdb.sceDdrdbSigvry(point, digest, npHdr.ECDsaSig);
if (ret != 0)
{
return false;
}
var vkey = new byte[0x10];
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<AMCTRL.MAC_KEY>()];
AMCTRL.sceDrmBBMacInit(mkey, 3);
AMCTRL.sceDrmBBMacUpdate(mkey, psarBuff, 0xc0);
AMCTRL.bbmac_getkey(mkey, npHdr.HeaderHash, vkey);
AMCTRL.sceDrmBBCipherInit(out var ckey, 1, 2, npHdr.HeaderKey, vkey, 0);
AMCTRL.sceDrmBBCipherUpdate(ref ckey, psarBuff[0x40..], 0x60);
AMCTRL.sceDrmBBCipherFinal(ref ckey);
npHdr = MemoryMarshal.AsRef<NpUmdImgHdr>(psarBuff);
var lbasize = npHdr.Body.LbaEnd - npHdr.Body.LbaStart + 1;
if (npHdr.BlockBasis == 0)
{
return false;
}
var totalBlocks = (int)(lbasize / npHdr.BlockBasis + (lbasize % npHdr.BlockBasis != 0 ? 1 : 0));
var tablesize = totalBlocks * 0x20;
if ((npHdr.Body.Unk40 & (1 << (Idps[5] & 0x1F))) == 0)
{
return false;
}
//Span<byte> newVkey = npHdr.NpFlags switch
//{
// 1 => Keys[CId].Fixed,
// 2 => _keyGen.GetVersionKey(),
// 3 => Keys[CId].Type3,
// _ => throw new NotSupportedException("unknown np flag")
//};
var paramSize = pbpHdr.Icon0Off - pbpHdr.ParamOff;
var dataPspSize = pbpHdr.DataPsarOff - pbpHdr.DataPspOff;
var buffSize = paramSize + 0x30;
if (buffSize > 0x800)
{
return false;
}
if (pbpHdr.DataPspOff + dataPspSize >= input.Length)
{
return false;
}
Span<byte> paramSpan = stackalloc byte[paramSize];
input.Seek(pbpHdr.ParamOff, SeekOrigin.Begin);
input.Read(paramSpan);
output.Seek(pbpHdr.ParamOff, SeekOrigin.Begin);
output.Write(paramSpan);
paramSpan.CopyTo(buff);
npHdr.ContentId.CopyTo(buff[paramSize..]);
input.Seek(pbpHdr.DataPspOff, SeekOrigin.Begin);
len = input.Read(buff.Slice(paramSize + 0x30, 0x28));
if (len < 0x28)
{
return false;
}
SceDdrdb.sceDdrdbHash(buff, paramSize + 0x30, digest);
// sig.r = buff.Skip(paramSize + 0x30).Take(0x14).ToArray();
// sig.s = buff.Skip(paramSize + 0x30 + 0x14).Take(0x14).ToArray();
ret = SceDdrdb.sceDdrdbSigvry(point, digest, buff.Slice(paramSize + 0x30, 0x28));
if (ret != 0)
{
return false;
}
var offset = input.Seek(pbpHdr.DataPspOff + 0x560, SeekOrigin.Begin);
if (offset != pbpHdr.DataPspOff + 0x560)
{
return false;
}
len = input.Read(buff.Slice(0, 0x34));
if (len != 0x34)
{
return false;
}
if (Encoding.ASCII.GetString(npHdr.ContentId).TrimEnd('\0') !=
Encoding.ASCII.GetString(buff[..0x30]).TrimEnd('\0'))
{
return false;
}
var bufidx = 0x33;
var off = Marshal.OffsetOf<NpUmdImgHdr>(nameof(npHdr.NpFlags)).ToInt32();
for (int i = 0; i < 4; i++)
{
if (buff[bufidx - i] != psarBuff[off + i])
{
return false;
}
}
Span<byte> newCid = stackalloc byte[0x30];
Encoding.ASCII.GetBytes(CId).AsSpan().CopyTo(newCid);
// Copy PSP.DATA
var pspDataSize = pbpHdr.DataPsarOff - pbpHdr.DataPspOff;
Memory<byte> pspData = new byte[pspDataSize];
input.Seek(pbpHdr.DataPspOff, SeekOrigin.Begin);
input.Read(pspData.Span);
paramSpan.CopyTo(buff);
newCid.CopyTo(buff[paramSize..]);
ECDsaHelper.SignParamSfo(buff[..(paramSize + 0x30)], pspData.Span);
newCid.CopyTo(pspData.Span[0x560..]);
var opnssmpOff = MemoryMarshal.Read<int>(pspData.Span[0x30..]);
var opnssmpSize = MemoryMarshal.Read<int>(pspData.Span[0x34..]);
if (opnssmpOff != 0 && opnssmpSize != 0)
{
var opnssmp = pspData.Slice(opnssmpOff, opnssmpSize);
using var ms = opnssmp.AsStream();
using var dnas = new DNASStream(ms, 0);
Span<byte> opnssmpData = new byte[dnas.Length];
dnas.Read(opnssmpData);
DNASHelper.Encrypt(pspData.Span[opnssmpOff..], opnssmpData, NewVersionKey.Span, opnssmpData.Length, dnas.KeyIndex, 1);
}
output.Seek(pbpHdr.DataPspOff, SeekOrigin.Begin);
output.Write(pspData.Span);
AMCTRL.sceDrmBBMacInit(mkey, 3);
var entityOff = pbpHdr.DataPsarOff + npHdr.Body.BlockEntryOffset;
offset = input.Seek(entityOff, SeekOrigin.Begin);
if (offset == entityOff)
{
if (tablesize > 0)
{
buff = new byte[0x8000];
for (int i = 0; i < tablesize; i += 0x8000)
{
var tmpsize = tablesize - i;
if (tmpsize > 0x8000)
{
tmpsize = 0x8000;
}
len = input.Read(buff[..0x8000]);
if (len < 0x8000)
{
return false;
}
// sceAmctrl_driver_9227EA79
AMCTRL.sceDrmBBMacUpdate(mkey, buff, tmpsize);
}
ret = AMCTRL.sceDrmBBMacFinal2(mkey, npHdr.DataKey, vkey);
if (ret != 0)
{
return false;
}
}
else
{
return false;
}
}
else
{
return false;
}
input.Seek(entityOff, SeekOrigin.Begin);
Span<byte> table = new byte[tablesize];
var tp = MemoryMarshal.Cast<byte, uint>(table);
input.Read(table);
// Decrypt Table
for (int i = 0; i < totalBlocks; i++)
XorTable(tp[(i * 8)..]);
var blocks = MemoryMarshal.Cast<byte, NpBlock>(table);
for (int i = 0; i < blocks.Length; i++)
{
Span<byte> blockData = new byte[blocks[i].Size];
input.Seek(pbpHdr.DataPsarOff + blocks[i].Offset, SeekOrigin.Begin);
input.Read(blockData);
// Verify MAC
AMCTRL.sceDrmBBMacInit(mkey, 3);
AMCTRL.sceDrmBBMacUpdate(mkey, blockData, blocks[i].Size);
ret = AMCTRL.sceDrmBBMacFinal2(mkey, blocks[i].Mac, vkey);
if (ret != 0)
{
return false;
}
// Decrypt block
AMCTRL.sceDrmBBCipherInit(out ckey, 1, 2, npHdr.HeaderKey, vkey, blocks[i].Offset >> 4);
AMCTRL.sceDrmBBCipherUpdate(ref ckey, blockData, blocks[i].Size);
AMCTRL.sceDrmBBCipherFinal(ref ckey);
// TODO LzrDecompress
// Encrypt block
AMCTRL.sceDrmBBCipherInit(out ckey, 1, 2, npHdr.HeaderKey, NewVersionKey.Span, blocks[i].Offset >> 4);
AMCTRL.sceDrmBBCipherUpdate(ref ckey, blockData, blocks[i].Size);
AMCTRL.sceDrmBBCipherFinal(ref ckey);
// Build Mac
AMCTRL.sceDrmBBMacInit(mkey, 3);
AMCTRL.sceDrmBBMacUpdate(mkey, blockData, blocks[i].Size);
AMCTRL.sceDrmBBMacFinal(mkey, blocks[i].Mac, NewVersionKey.Span);
Utils.BuildDrmBBMacFinal2(blocks[i].Mac);
output.Seek(pbpHdr.DataPsarOff + blocks[i].Offset, SeekOrigin.Begin);
output.Write(blockData);
}
// Encrypt Table
for (int i = 0; i < totalBlocks; i++)
XorTable(tp[(i * 8)..]);
output.Seek(entityOff, SeekOrigin.Begin);
output.Write(table);
AMCTRL.sceDrmBBMacInit(mkey, 3);
AMCTRL.sceDrmBBMacUpdate(mkey, table, tablesize);
AMCTRL.sceDrmBBMacFinal(mkey, npHdr.DataKey, NewVersionKey.Span);
Utils.BuildDrmBBMacFinal2(npHdr.DataKey);
newCid.CopyTo(npHdr.ContentId);
// Encrypt NPUMDIMG body.
AMCTRL.sceDrmBBCipherInit(out ckey, 1, 2, npHdr.HeaderKey, NewVersionKey.Span, 0);
AMCTRL.sceDrmBBCipherUpdate(ref ckey, psarBuff[0x40..], 0x60);
AMCTRL.sceDrmBBCipherFinal(ref ckey);
// Generate header hash.
AMCTRL.sceDrmBBMacInit(mkey, 3);
AMCTRL.sceDrmBBMacUpdate(mkey, psarBuff, 0xC0);
AMCTRL.sceDrmBBMacFinal(mkey, npHdr.HeaderHash, NewVersionKey.Span);
Utils.BuildDrmBBMacFinal2(npHdr.HeaderHash);
ECDsaHelper.SignNpImageHeader(psarBuff);
output.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
output.Write(psarBuff);
return true;
}
static int CopyStartData(Stream input, Stream output, int psarOffset)
{
int startdat_offset;
using var br = new BinaryReader(input, new UTF8Encoding(), true);
startdat_offset = br.ReadInt32();
if (startdat_offset > 0)
{
// Read the STARTDAT header
br.BaseStream.Seek(psarOffset + startdat_offset, SeekOrigin.Begin);
var startdat_header = ReadStruct<STARTDAT_HEADER>(br);
br.BaseStream.Seek(psarOffset + startdat_offset, SeekOrigin.Begin);
// Read the STARTDAT data.
int startdat_size = startdat_header.header_size + startdat_header.data_size;
byte[] startdat_data = br.ReadBytes(startdat_size);
output.Seek(psarOffset + startdat_offset, SeekOrigin.Begin);
output.Write(startdat_data, 0, startdat_size);
}
return startdat_offset;
}
static bool DecryptIsoHeader(Stream input, Span<byte> header, int header_offset, out int block_size)
{
// Seek to the ISO header.
// input.Seek(header_offset, SeekOrigin.Current);
// Read the ISO header.
using var dnas = new DNASStream(input, input.Position + header_offset);
block_size = dnas.BlockSize;
dnas.VersionKey.CopyTo(VersionKey.Span);
if (header.Length == dnas.Length)
{
dnas.Read(header);
return true;
}
return false;
}
static bool CopySimpleData(Stream input, Stream output, PbpHeader pbpHdr, int simple_data_offset)
{
if (simple_data_offset > 0)
{
using var dnas = new DNASStream(input, pbpHdr.DataPsarOff + simple_data_offset);
Span<byte> simpleData = new byte[dnas.Length];
if (dnas.Read(simpleData) == dnas.Length)
{
var simpleDataEnc = new byte[input.Length - pbpHdr.DataPsarOff - simple_data_offset];
DNASHelper.Encrypt(simpleDataEnc, simpleData, NewVersionKey.Span, simpleData.Length, dnas.KeyIndex, 1, blockSize: dnas.BlockSize);
output.Seek(pbpHdr.DataPsarOff + simple_data_offset, SeekOrigin.Begin);
output.Write(simpleDataEnc);
return true;
}
}
return false;
}
static bool CopyUnknownData(Stream input, Stream output, PbpHeader pbpHdr, int unknown_data_offset, int startdat_offset)
{
if (unknown_data_offset > 0)
{
input.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
using var dnas = new DNASStream(input, input.Position + unknown_data_offset, VersionKey.Span);
Span<byte> unknownData = new byte[dnas.Length];
if (dnas.Read(unknownData) == dnas.Length)
{
var unknownDataEnc = new byte[startdat_offset - unknown_data_offset];
DNASHelper.Encrypt(unknownDataEnc, unknownData, NewVersionKey.Span, unknownData.Length, dnas.KeyIndex, 1, blockSize: dnas.BlockSize);
output.Seek(pbpHdr.DataPsarOff + unknown_data_offset, SeekOrigin.Begin);
output.Write(unknownDataEnc);
return true;
}
}
return false;
}
static bool CopyAudio(Stream input, Stream output, ReadOnlySpan<byte> iso_table, PbpHeader pbpHdr, int base_offset = 0)
{
// Set CDDA entry.
CDDA_ENTRY audio_entry;
var iso_offset = MemoryMarshal.Read<int>(iso_table[0x7fc..]);
// Start of the ISO data.
var iso_base_offset = iso_offset + base_offset;
// Start the audio track number counter at 2 (data track is always the first one).
int audio_track_count = 2;
// Read the audio track table (starts at 0x800 and ends at offset 0xE20).
for (var audio_offset = 0x800; audio_offset < 0xE20; audio_offset += Unsafe.SizeOf<CDDA_ENTRY>())
{
// Read the CDDA entry.
audio_entry = MemoryMarshal.Read<CDDA_ENTRY>(iso_table[audio_offset..]);
// Reached the last entry.
if (audio_entry.offset == 0)
{
break;
}
// Locate the block offset in the DATA.PSAR.
input.Seek(pbpHdr.DataPsarOff + iso_base_offset + audio_entry.offset, SeekOrigin.Begin);
output.Seek(pbpHdr.DataPsarOff + iso_base_offset + audio_entry.offset, SeekOrigin.Begin);
// Read the data.
Span<byte> track_data = new byte[audio_entry.size];
input.Read(track_data);
output.Write(track_data);
// Increment the track counter.
audio_track_count++;
}
return true;
}
static bool CopyIso(Stream input, Stream output, ReadOnlySpan<byte> iso_table, Span<byte> header, int base_offset, PbpHeader pbpHdr, int hdrBlockSize)
{
var header_offset = base_offset + 0x400;
var header_size = 0xB6600;
// Setup buffers.
int iso_block_size = 0x9300;
Span<byte> iso_block_comp = new byte[iso_block_size]; // Compressed block.
byte[] iso_block_decomp = new byte[iso_block_size]; // Decompressed block.
// Locate the block table.
int table_offset = 0x3C00; // Fixed offset.
int iso_base_offset = 0x100000 + base_offset; // Start of compressed ISO data.
// Read the first entry.
var entry = MemoryMarshal.Read<ISO_ENTRY>(iso_table[table_offset..]);
var entries = MemoryMarshal.Cast<byte, ISO_ENTRY>(header[0x3C00..]);
var i = 0;
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<PspCrypto.AMCTRL.MAC_KEY>()];
// Keep reading entries until we reach the end of the table.
while (entry.size > 0)
{
// Locate the block offset in the DATA.PSAR.
var block = iso_block_comp[..entry.size];
input.Seek(pbpHdr.DataPsarOff + iso_base_offset + entry.offset, SeekOrigin.Begin);
input.Read(block);
output.Seek(pbpHdr.DataPsarOff + iso_base_offset + entry.offset, SeekOrigin.Begin);
output.Write(block);
PspCrypto.AMCTRL.sceDrmBBMacInit(mkey, 3);
PspCrypto.AMCTRL.sceDrmBBMacUpdate(mkey, iso_block_comp, entry.size);
int ret = PspCrypto.AMCTRL.sceDrmBBMacFinal2(mkey, entries[i].checksum, VersionKey.Span);
if (ret != 0)
{
Console.WriteLine("ERROR: BLOCK CROP");
return false;
}
PspCrypto.AMCTRL.sceDrmBBMacInit(mkey, 3);
PspCrypto.AMCTRL.sceDrmBBMacUpdate(mkey, iso_block_comp, entry.size);
Span<byte> checksum = new byte[20 + 0x10];
PspCrypto.AMCTRL.sceDrmBBMacFinal(mkey, checksum[20..], NewVersionKey.Span);
ref var aesHdr = ref MemoryMarshal.AsRef<KIRKEngine.KIRK_AES128CBC_HEADER>(checksum);
aesHdr.mode = KIRKEngine.KIRK_MODE_ENCRYPT_CBC;
aesHdr.keyseed = 0x63;
aesHdr.data_size = 0x10;
PspCrypto.KIRKEngine.sceUtilsBufferCopyWithRange(checksum, 0x10, checksum, 0x10,
KIRKEngine.KIRK_CMD_ENCRYPT_IV_0);
checksum.Slice(20, 0x10).CopyTo(entries[i].checksum);
// Go to next entry.
table_offset += Marshal.SizeOf<ISO_ENTRY>();
entry = MemoryMarshal.Read<ISO_ENTRY>(iso_table[table_offset..]);
i++;
}
Span<byte> headerEnc = new byte[header_size];
DNASHelper.Encrypt(headerEnc, header, NewVersionKey.Span, header.Length, 1, 1, blockSize: hdrBlockSize);
output.Seek(pbpHdr.DataPsarOff + header_offset, SeekOrigin.Begin);
output.Write(headerEnc);
output.Flush();
return true;
}
static bool CopyPsxLoader(Stream input, Stream output, PbpHeader pbpHdr)
{
var pspDataSize = pbpHdr.DataPsarOff - pbpHdr.DataPspOff;
Memory<byte> loader = new byte[pspDataSize];
input.Seek(pbpHdr.DataPspOff, SeekOrigin.Begin);
input.Read(loader.Span);
Span<byte> config = new byte[0x410];
loader.Span.Slice(0x150, 0x410).CopyTo(config);
var hdr = MemoryMarshal.AsRef<PSPHeader2>(loader.Span);
var ret = SceMesgLed.sceMesgLed_driver_EBB4613D(loader.Span, hdr.pspSize, out var newSize, VersionKey.Span);
if (ret != 0)
{
return false;
}
Span<byte> decMod;
if (loader.Span[0] == 0x1f && loader.Span[1] == 0x8b)
{
using var ms = loader.AsStream();
using var gz = new GZipStream(ms, CompressionMode.Decompress);
using var decMs = new MemoryStream();
gz.CopyTo(decMs);
decMod = decMs.ToArray();
}
else
{
decMod = loader.Span[..newSize];
}
Span<byte> loaderEnc = new byte[pspDataSize];
var key = Convert.ToHexString(NewVersionKey.Span);
SceMesgLed.Encrypt(loaderEnc, decMod, hdr.tag, SceExecFileDecryptMode.DECRYPT_MODE_POPS_EXEC, NewVersionKey.Span, CId, config);
output.Seek(pbpHdr.DataPspOff, SeekOrigin.Begin);
output.Write(loaderEnc);
return true;
}
static bool CopyPsIsoImg(Stream input, Stream output, PbpHeader pbpHdr)
{
input.Seek(pbpHdr.DataPsarOff + 12, SeekOrigin.Begin);
int startdat_offset = CopyStartData(input, output, pbpHdr.DataPsarOff);
// Decrypt the ISO header and get the block table.
// NOTE: In a single disc, the ISO header is located at offset 0x400 and has a length of 0xB6600.
Span<byte> header = new byte[0x400 + 0xb3880];
input.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
input.Read(header[..0x400]);
output.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
output.Write(header[..0x400]);
Span<byte> iso_table = header[0x400..];
input.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
if (!DecryptIsoHeader(input, iso_table, 0x400, out var hdrBlockSize))
{
return false;
}
// Save the ISO disc name and title (UTF-8).
ReadOnlySpan<byte> iso_disc_name = iso_table[..0x10];
ReadOnlySpan<byte> iso_title = iso_table.Slice(0xE2C, 0x80);
string iso_disc_name_utf8 = Encoding.UTF8.GetString(iso_disc_name).TrimEnd('\0');
string iso_title_utf8 = Encoding.UTF8.GetString(iso_title).TrimEnd('\0');
Console.WriteLine($"ISO disc: {iso_disc_name_utf8}");
Console.WriteLine($"ISO title: {iso_title_utf8}\n");
// Seek inside the ISO table to find the SIMPLE data offset.
int simple_data_offset = MemoryMarshal.Read<int>(iso_table[0xE20..]);
if (!CopySimpleData(input, output, pbpHdr, simple_data_offset))
{
Console.WriteLine("CopySimpleData failed!");
return false;
}
// Seek inside the ISO table to find the unknown data offset.
int unknown_data_offset = MemoryMarshal.Read<int>(iso_table[0xEDE..]);
if (unknown_data_offset > 0)
{
if (!CopyUnknownData(input, output, pbpHdr, unknown_data_offset, startdat_offset))
{
Console.WriteLine("CopyUnknownData failed");
return false;
}
}
// Extract the CDDA tracks.
if (!CopyAudio(input, output, iso_table, pbpHdr))
{
Console.WriteLine("CopyAudio failed");
return false;
}
if (!CopyIso(input, output, iso_table, header[0x400..], 0, pbpHdr, hdrBlockSize))
{
Console.WriteLine("CopyIso failed");
return false;
}
if (!CopyPsxLoader(input, output, pbpHdr))
{
Console.WriteLine("CopyPsxLoader failed");
return false;
}
return true;
}
static bool ReadIsoMap(Stream input, PbpHeader pbpHdr, Span<byte> isoMap)
{
var mapOffset = 0x200;
using var dnas = new DNASStream(input, pbpHdr.DataPsarOff + mapOffset, VersionKey.Span);
Span<byte> buffer = new byte[dnas.Length];
if (dnas.Read(buffer) == dnas.Length)
{
buffer.CopyTo(isoMap);
return true;
}
return false;
}
static void WriteIsoMap(Stream output, PbpHeader pbpHdr, ReadOnlySpan<byte> isoMap)
{
var mapOffset = 0x200;
var mapSize = 0x2A0;
var isoMapEnc = new byte[mapSize];
DNASHelper.Encrypt(isoMapEnc, isoMap, NewVersionKey.Span, isoMap.Length, 1, 1);
output.Seek(pbpHdr.DataPsarOff + mapOffset, SeekOrigin.Begin);
output.Write(isoMapEnc);
}
static bool CopyPsTitleImg(Stream input, Stream output, PbpHeader pbpHdr)
{
input.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
output.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
Span<byte> psTitle = new byte[200];
input.Read(psTitle);
output.Write(psTitle);
output.Flush();
input.Seek(pbpHdr.DataPsarOff + 16, SeekOrigin.Begin);
int startdat_offset = CopyStartData(input, output, pbpHdr.DataPsarOff);
Span<byte> isoMap = new byte[0x200];
if (!ReadIsoMap(input, pbpHdr, isoMap))
{
Console.WriteLine("ReadIsoMap failed");
return false;
}
Span<int> discOffsets = isoMap[..20].Cast<byte, int>();
Span<byte> macKeys = isoMap.Slice(20, 16 * 5);
ReadOnlySpan<byte> iso_disc_name = isoMap.Slice(0x64, 0x20);
ReadOnlySpan<byte> iso_title = isoMap.Slice(0x10C, 0x80);
string iso_disc_name_utf8;
string iso_title_utf8;
unsafe
{
fixed (byte* ptr = iso_disc_name)
{
ReadOnlySpan<byte> disc = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr);
iso_disc_name_utf8 = Encoding.UTF8.GetString(disc);
}
fixed (byte* ptr = iso_title)
{
ReadOnlySpan<byte> title = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(ptr);
iso_title_utf8 = Encoding.UTF8.GetString(title);
}
}
Console.WriteLine($"ISO disc: {iso_disc_name_utf8}");
Console.WriteLine($"ISO title: {iso_title_utf8}\n");
int simple_data_offset = MemoryMarshal.Read<int>(isoMap[0x84..]);
if (!CopySimpleData(input, output, pbpHdr, simple_data_offset))
{
Console.WriteLine("CopySimpleData failed");
return false;
}
// Build each valid ISO image.
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<PspCrypto.AMCTRL.MAC_KEY>()];
for (int i = 0; i < discOffsets.Length; i++)
{
var diskOffset = discOffsets[i];
if (diskOffset > 0)
{
Span<byte> header = new byte[0x400 + 0xb3880];
input.Seek(pbpHdr.DataPsarOff + diskOffset, SeekOrigin.Begin);
input.Read(header[..0x400]);
output.Seek(pbpHdr.DataPsarOff + diskOffset, SeekOrigin.Begin);
output.Write(header[..0x400]);
Span<byte> iso_table = header[0x400..];
input.Seek(pbpHdr.DataPsarOff + diskOffset, SeekOrigin.Begin);
if (!DecryptIsoHeader(input, iso_table, 0x400, out var hdrBlockSize))
{
return false;
}
int ret = PspCrypto.AMCTRL.sceDrmBBMacInit(mkey, 3);
ret = PspCrypto.AMCTRL.sceDrmBBMacUpdate(mkey, header, 0xb3c80);
ret = PspCrypto.AMCTRL.sceDrmBBMacFinal2(mkey, macKeys[(i * 0x10)..], VersionKey.Span);
if (ret != 0)
{
Console.WriteLine("ERROR: Header miss match");
return false;
}
var unknown = MemoryMarshal.Read<int>(iso_table[0xEDE..]);
Console.WriteLine($"unknown {unknown}");
// Extract the CDDA tracks.
if (!CopyAudio(input, output, iso_table, pbpHdr, diskOffset))
{
Console.WriteLine("CopyAudio failed");
return false;
}
if (!CopyIso(input, output, iso_table, header[0x400..], diskOffset, pbpHdr, hdrBlockSize))
{
Console.WriteLine($"CopyIso disc{i} failed");
return false;
}
ret = PspCrypto.AMCTRL.sceDrmBBMacInit(mkey, 3);
ret = PspCrypto.AMCTRL.sceDrmBBMacUpdate(mkey, header, 0xb3c80);
Span<byte> newKey = new byte[20 + 0x10];
ret = PspCrypto.AMCTRL.sceDrmBBMacFinal(mkey, newKey[20..], NewVersionKey.Span);
ref var aesHdr = ref MemoryMarshal.AsRef<KIRKEngine.KIRK_AES128CBC_HEADER>(newKey);
aesHdr.mode = KIRKEngine.KIRK_MODE_ENCRYPT_CBC;
aesHdr.keyseed = 0x63;
aesHdr.data_size = 0x10;
PspCrypto.KIRKEngine.sceUtilsBufferCopyWithRange(newKey, 0x10, newKey, 0x10,
KIRKEngine.KIRK_CMD_ENCRYPT_IV_0);
newKey.Slice(20, 0x10).CopyTo(macKeys[(i * 0x10)..]);
}
}
WriteIsoMap(output, pbpHdr, isoMap);
if (!CopyPsxLoader(input, output, pbpHdr))
{
Console.WriteLine("CopyPsxLoader failed");
return false;
}
return true;
}
static int GetKeyType(Stream input, PbpHeader pbpHdr)
{
input.Seek(pbpHdr.DataPspOff + 0x560, SeekOrigin.Begin);
Span<byte> contentInfo = new byte[0x34];
input.Read(contentInfo);
var keyType = BinaryPrimitives.ReadInt32BigEndian(contentInfo[0x30..]);
return keyType;
}
static bool CopyData(Stream input, Stream output, PbpHeader pbpHdr, out int type)
{
type = -1;
input.Seek(pbpHdr.DataPsarOff, SeekOrigin.Begin);
Span<byte> psarBuff = stackalloc byte[0x100];
var len = input.Read(psarBuff);
if (len != 0x100)
{
return false;
}
ref var npHdr = ref MemoryMarshal.AsRef<NpUmdImgHdr>(psarBuff);
if (npHdr.Magic0 == 0x4d55504e && npHdr.Magic1 == 0x474d4944)
{
int ret = _keyGen.GetVersionKey(NewVersionKey.Span, npHdr.NpFlags);
if (ret != 0)
{
Console.WriteLine($"GetVersionKey {npHdr.NpFlags} failed");
return false;
}
type = 0;
return CopyNpUmdImg(input, output, pbpHdr, psarBuff, npHdr);
}
if (psarBuff[..12].SequenceEqual(iso_magic))
{
var keyType = GetKeyType(input, pbpHdr);
int ret = _keyGen.GetVersionKey(NewVersionKey.Span, keyType);
if (ret != 0)
{
Console.WriteLine("GetVersionKey 1 failed");
return false;
}
type = 1;
return CopyPsIsoImg(input, output, pbpHdr);
}
if (psarBuff[..16].SequenceEqual(multi_iso_magic))
{
var keyType = GetKeyType(input, pbpHdr);
int ret = _keyGen.GetVersionKey(NewVersionKey.Span, keyType);
if (ret != 0)
{
Console.WriteLine("GetVersionKey 1 failed");
return false;
}
type = 2;
return CopyPsTitleImg(input, output, pbpHdr);
}
return false;
}
class KeyGen
{
private readonly byte[] _actData;
private readonly byte[] _rifData;
public KeyGen(string rifName)
{
_actData = File.ReadAllBytes("act.dat");
_rifData = File.ReadAllBytes(rifName);
}
public int GetVersionKey(Span<byte> versionKey, int type) => SceNpDrm.sceNpDrmGetVersionKey(versionKey, _actData, _rifData, type);
}
static bool CopyDocument(string src, string dist)
{
using var fs = File.OpenRead(src);
return true;
}
static void Main(string[] args)
{
if (args.Length != 3)
{
Console.Write("Usage PbpResign.exe accountId contentId input");
return;
}
if (!File.Exists("act.dat"))
{
Console.WriteLine("act.dat not exist");
return;
}
if (!File.Exists("psid"))
{
Console.WriteLine("psid not exist");
return;
}
var aid = args[0];
if (aid.Length < 16)
{
aid = aid.PadLeft(16, '0');
}
var aidData = Convert.FromHexString(aid);
SceNpDrm.Aid = BitConverter.ToUInt64(aidData);
var cmaKey = KeyGenerator.GenerateKey(aidData);
Console.WriteLine(Convert.ToHexString(cmaKey));
var srcPbp = args[2];
if (!File.Exists(srcPbp))
{
Console.WriteLine($"{srcPbp} not exist");
return;
}
Idps = File.ReadAllBytes("psid");
SceNpDrm.SetPSID(Idps);
CId = args[1];
var rifName = $"{CId}.rif";
if (!File.Exists(rifName))
{
Console.WriteLine($"{rifName} not exist");
return;
}
_keyGen = new KeyGen(rifName);
var srcPath = Path.GetDirectoryName(srcPbp);
try
{
using var input = File.OpenRead(srcPbp);
var hdr = new byte[Marshal.SizeOf<PbpHeader>()];
var len = input.Read(hdr);
if (len != hdr.Length)
{
Console.WriteLine("Wrong input");
return;
}
var pbpHdr = MemoryMarshal.AsRef<PbpHeader>(hdr);
if (pbpHdr.Sig != 0x50425000)
{
Console.WriteLine("Wrong pbp sig");
return;
}
input.Seek(pbpHdr.ParamOff, SeekOrigin.Begin);
var paramSize = pbpHdr.Icon0Off - pbpHdr.ParamOff;
Span<byte> param = new byte[paramSize];
input.Read(param);
var sfoDic = Sfo.ReadSfo(param);
string distPath;
if (sfoDic["DISC_ID"] is string diskId)
{
distPath = Path.Combine(srcPath, $"signed\\game\\ux0_pspemu_temp_game_PSP_GAME_{diskId}");
}
else
{
Console.WriteLine("Can't get diskid");
return;
}
if (!Directory.Exists(distPath))
{
Directory.CreateDirectory(distPath);
}
var psxDoc = Path.Combine(srcPath, "DOCUMENT.DAT");
var psxDistDoc = Path.Combine(distPath, "DOCUMENT.DAT");
if (File.Exists(psxDoc))
{
File.Copy(psxDoc, psxDistDoc, true);
}
var vitaPath = Path.Combine(distPath, "VITA_PATH.TXT");
var vitaPathData = $"ux0:pspemu/temp/game/PSP/GAME/{diskId}\0";
File.WriteAllText(vitaPath, vitaPathData);
var licensePath = Path.Combine(srcPath, $"signed\\license\\ux0_pspemu_temp_game_PSP_LICENSE");
if (!Directory.Exists(licensePath))
{
Directory.CreateDirectory(licensePath);
}
var distLicense = Path.Combine(licensePath, rifName);
File.Copy(rifName, distLicense, true);
vitaPath = Path.Combine(licensePath, "VITA_PATH.TXT");
vitaPathData = "ux0:pspemu/temp/game/PSP/LICENSE\0";
File.WriteAllText(vitaPath, vitaPathData);
var sceSysPath = Path.Combine(srcPath, "signed\\sce_sys");
if (!Directory.Exists(sceSysPath))
{
Directory.CreateDirectory(sceSysPath);
}
var paramPath = Path.Combine(sceSysPath, "param.sfo");
using (var fs = File.Create(paramPath))
{
fs.Write(param);
fs.Flush();
}
var distPbp = Path.Combine(distPath, "EBOOT.PBP");
using var output = File.OpenWrite(distPbp);
output.SetLength(input.Length);
output.Write(hdr);
if (!CopyNormalData(input, output, pbpHdr.ParamOff, paramSize))
{
Console.WriteLine("Wrong PARAM.SFO data");
return;
}
var icon0Size = pbpHdr.Icon1Off - pbpHdr.Icon0Off;
if (!CopyNormalData(input, output, pbpHdr.Icon0Off, icon0Size))
{
Console.WriteLine("Wrong ICON0.PNG data");
return;
}
Span<byte> icon0 = new byte[icon0Size];
input.Seek(pbpHdr.Icon0Off, SeekOrigin.Begin);
input.Read(icon0);
var icon0Path = Path.Combine(sceSysPath, "icon0.png");
using (var fs = File.Create(icon0Path))
{
fs.Write(icon0);
fs.Flush();
}
var icon1Size = pbpHdr.Pic0Off - pbpHdr.Icon1Off;
if (!CopyNormalData(input, output, pbpHdr.Icon1Off, icon1Size))
{
Console.WriteLine("Wrong ICON1.PMF/ICON1.PNG data");
return;
}
var pic0Size = pbpHdr.Pic1Off - pbpHdr.Pic0Off;
if (!CopyNormalData(input, output, pbpHdr.Pic0Off, pic0Size))
{
Console.WriteLine("Wrong PIC0.PNG/UNKNOWN.PNG data");
return;
}
var pic1Size = pbpHdr.Snd0Off - pbpHdr.Pic1Off;
if (!CopyNormalData(input, output, pbpHdr.Pic1Off, pic1Size))
{
Console.WriteLine("Wrong PIC1.PNG/PICT1.PNG data");
return;
}
var snd0Size = pbpHdr.DataPspOff - pbpHdr.Snd0Off;
if (!CopyNormalData(input, output, pbpHdr.Snd0Off, snd0Size))
{
Console.WriteLine("Wrong SND0.AT3 data");
return;
}
if (!CopyData(input, output, pbpHdr, out var type))
{
Console.WriteLine("Wrong DATA.PSP/DATA.PSAR data");
}
else
{
Console.WriteLine($"Resign {srcPbp} to {distPbp} Successful");
var sigPath = Path.Combine(distPath, "__sce_ebootpbp");
input.Close();
output.Close();
Span<byte> ebootsig = stackalloc byte[0x200];
switch (type)
{
case 0:
SceNpDrm.KsceNpDrmEbootSigGenPsp(distPbp, ebootsig, 0x3600000);
File.WriteAllBytes(sigPath, ebootsig.ToArray());
break;
case 1:
SceNpDrm.KsceNpDrmEbootSigGenPs1(distPbp, ebootsig, 0x3600000);
File.WriteAllBytes(sigPath, ebootsig.ToArray());
break;
case 2:
SceNpDrm.KsceNpDrmEbootSigGenPs1(distPbp, ebootsig, 0x3600000);
File.WriteAllBytes(sigPath, ebootsig.ToArray());
break;
}
//var srcDir = Path.GetDirectoryName(srcPbp);
//var documentPath = Path.Combine(srcDir, "DOCUMENT.DAT");
//var newDocumentPath = Path.Combine(distDir, "DOCUMENT_MOD.DAT");
//if (File.Exists(documentPath))
//{
// CopyDocument(documentPath, newDocumentPath);
//}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}