Add code to read SBI File (Subchannel information, for libcrypt), and include magic word inside ISO HEADER

This commit is contained in:
Li 2023-04-30 21:08:57 +12:00
parent b13fa203a8
commit 6647b319a2
10 changed files with 228 additions and 37 deletions

View File

@ -30,24 +30,28 @@ namespace GameBuilder.Cue
return totalTracks;
}
public static byte BinaryDecimalConv(int i)
public static byte BinaryDecimalToDecimal(int i)
{
return Convert.ToByte(Convert.ToInt32(10 * (i - i % 16) / 16 + i % 16));
}
public static byte DecimalToBinaryDecimal(int i)
{
return Convert.ToByte(Convert.ToInt32((i % 10) + 16 * ((i / 10) % 10)));
}
public int IdxToSectorRel(CueIndex index)
public static int IdxToSectorRel(DiscIndex index)
{
int offset = (((index.Mrel * 60) + index.Srel) * 75 + index.Frel);
return offset;
}
public int IdxToSector(CueIndex index)
public static int IdxToSector(DiscIndex index)
{
int offset = (((index.m * 60) + index.s) * 75 + index.f);
return offset;
}
public CueIndex SectorToIdx(int sector)
public static DiscIndex SectorToIdx(int sector)
{
CueIndex idx = new CueIndex(1);
DiscIndex idx = new DiscIndex(1);
int x = sector;
int f = sector % 75;
@ -56,11 +60,6 @@ namespace GameBuilder.Cue
int s = x % 60;
int m = Convert.ToInt32(Math.Floor(Convert.ToDouble(x) / 60.0));
//idx.Mrel = Convert.ToByte(Convert.ToInt32(((sector / 75) / 60)));
//idx.Srel = Convert.ToByte(Convert.ToInt32(((sector / 75) % 60)));
//idx.Frel = Convert.ToByte(Convert.ToInt32(((sector % 75))));
//idx.Sdelta = 2; // why?
idx.Mrel = Convert.ToInt16(m);
idx.Srel = Convert.ToInt16(s);
idx.Frel = Convert.ToInt16(f);
@ -185,11 +184,11 @@ namespace GameBuilder.Cue
byte[] tocA0Entry = new byte[10] { 0x41, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00 };
// And an A1 track (determines how many tracks there are)
byte[] tocA1Entry = new byte[10] { 0x41, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, BinaryDecimalConv(GetTotalTracks()), 0x00, 0x00 };
byte[] tocA1Entry = new byte[10] { 0x41, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x00, DecimalToBinaryDecimal(GetTotalTracks()), 0x00, 0x00 };
// the A2 track is a bit more complicated ..
int totalSectors = getTotalSectorSz();
CueIndex idx = SectorToIdx(totalSectors);
DiscIndex idx = SectorToIdx(totalSectors);
idx.Sdelta = 2;
byte[] tocA2Entry = new byte[10] { 0x41, 0x00, 0xA2, 0x00, 0x00, 0x00, 0x00, idx.M, idx.S, idx.F };
@ -246,7 +245,7 @@ namespace GameBuilder.Cue
if (tracks[i] is null) continue;
int pos = positions[tracks[i].binFileName];
CueIndex idx = this.SectorToIdx(pos);
DiscIndex idx = SectorToIdx(pos);
// pregap not included on first track
if (tracks[i].TrackNo == 1) tracks[i].TrackIndex[0].Sdelta = 0;

View File

@ -14,7 +14,7 @@ namespace GameBuilder.Cue
public TrackType TrackType;
public byte TrackNo;
public CueIndex[] TrackIndex;
public DiscIndex[] TrackIndex;
public int TrackLength;
public int SectorSz
@ -30,9 +30,9 @@ namespace GameBuilder.Cue
internal CueTrack(string binFile)
{
TrackIndex = new CueIndex[2];
TrackIndex = new DiscIndex[2];
for (int i = 0; i < TrackIndex.Length; i++)
TrackIndex[i] = new CueIndex(Convert.ToByte(i));
TrackIndex[i] = new DiscIndex(Convert.ToByte(i));
binFileName = binFile;
binFileSz = new FileInfo(binFileName).Length;
@ -47,7 +47,7 @@ namespace GameBuilder.Cue
tocEntry[0] = Convert.ToByte(this.TrackType);
tocEntry[1] = 0;
tocEntry[2] = CueReader.BinaryDecimalConv(this.TrackNo);
tocEntry[2] = CueReader.DecimalToBinaryDecimal(this.TrackNo);
tocEntry[3] = this.TrackIndex[0].M;

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace GameBuilder.Cue
{
public class CueIndex
public class DiscIndex
{
public byte IndexNumber;
public short Mrel;
@ -73,7 +73,7 @@ namespace GameBuilder.Cue
{
get
{
return CueReader.BinaryDecimalConv(m);
return CueReader.DecimalToBinaryDecimal(m);
}
}
@ -81,7 +81,7 @@ namespace GameBuilder.Cue
{
get
{
return CueReader.BinaryDecimalConv(s);
return CueReader.DecimalToBinaryDecimal(s);
}
}
@ -89,11 +89,11 @@ namespace GameBuilder.Cue
{
get
{
return CueReader.BinaryDecimalConv(f);
return CueReader.DecimalToBinaryDecimal(f);
}
}
internal CueIndex(byte indexNumber)
internal DiscIndex(byte indexNumber)
{
IndexNumber = indexNumber;
Mrel = 0;

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder.Cue
{
public class SbiEntry
{
public DiscIndex MSF;
byte[] TOC;
public int Sector
{
get
{
return CueReader.IdxToSector(MSF);
}
}
public SbiEntry(DiscIndex Msf, byte[] Toc)
{
MSF = Msf;
TOC = Toc;
}
}
}

View File

@ -0,0 +1,58 @@
using Li.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder.Cue
{
public class SbiReader
{
private StreamUtil sbiUtil;
private List<SbiEntry> sbiEntries;
public SbiEntry[] Entries
{
get
{
return sbiEntries.ToArray();
}
}
private void init(Stream sbiFile)
{
sbiEntries = new List<SbiEntry>();
sbiUtil = new StreamUtil(sbiFile);
string magic = sbiUtil.ReadStrLen(3);
if (magic != "SBI")
throw new Exception("Invalid SBI Sub Channel file.");
sbiUtil.ReadByte();
do
{
byte m = CueReader.BinaryDecimalToDecimal(sbiUtil.ReadByte());
byte s = CueReader.BinaryDecimalToDecimal(sbiUtil.ReadByte());
byte f = CueReader.BinaryDecimalToDecimal(sbiUtil.ReadByte());
byte i = CueReader.BinaryDecimalToDecimal(sbiUtil.ReadByte());
byte[] toc = sbiUtil.ReadBytes(0xA);
DiscIndex idx = new DiscIndex(i);
idx.Mrel = m;
idx.Srel = s;
idx.Frel = f;
sbiEntries.Add(new SbiEntry(idx, toc));
} while (sbiFile.Position < sbiFile.Length);
}
public SbiReader(string sbiFileName)
{
using (FileStream fsbi = File.OpenRead(sbiFileName))
{
init(fsbi);
}
}
public SbiReader(Stream sbiFile)
{
init(sbiFile);
}
}
}

View File

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Li.Utilities;
using GameBuilder.Pops.LibCrypt;
namespace GameBuilder.Pops
{
@ -25,6 +26,9 @@ namespace GameBuilder.Pops
this.disc = disc;
this.cue = new CueReader(disc.CueFile);
if (disc.SbiFile is not null)
this.sbi = new SbiReader(disc.SbiFile);
this.IsoHeader = new MemoryStream();
this.CompressedIso = new MemoryStream();
@ -115,6 +119,7 @@ namespace GameBuilder.Pops
writeTOC();
writeIsoLocation();
writeName();
writeLibCryptData();
writeCompressedIso();
@ -129,6 +134,28 @@ namespace GameBuilder.Pops
IsoHeader.Seek(0xE20, SeekOrigin.Begin);
isoHeaderUtil.WriteInt64(location);
}
private int obfuscateMagicWord()
{
int magicWord = 0;
if (sbi is not null) magicWord = MagicWord.GenMagicWord(sbi.Entries);
return magicWord ^ 0x72d0ee59;
}
private void writeLibCryptData()
{
// obfuscated libcrypt magic word
int obfuscatedMagicWord = obfuscateMagicWord();
isoHeaderUtil.WriteInt32(obfuscatedMagicWord);
isoHeaderUtil.WriteInt32(0);
isoHeaderUtil.WriteInt32(0);
isoHeaderUtil.WriteInt32(0);
isoHeaderUtil.WritePadding(0, 0x2D40);
}
private void writeName()
{
// copied from crash bandicoot warped
@ -137,14 +164,9 @@ namespace GameBuilder.Pops
isoHeaderUtil.WriteInt32(2047); // unk
isoHeaderUtil.WriteStrWithPadding(disc.DiscName, 0x00, 0x80);
isoHeaderUtil.WriteInt32(3); // unk
isoHeaderUtil.WriteInt32(3); // PARENTAL_LEVEL ?
isoHeaderUtil.WriteInt32(0x72d0ee59); // appears to be constant?
isoHeaderUtil.WriteInt32(0);
isoHeaderUtil.WriteInt32(0);
isoHeaderUtil.WriteInt32(0);
isoHeaderUtil.WritePadding(0, 0x2D40);
}
private void writeCDAEntry(int position, int length, uint key)
@ -229,6 +251,7 @@ namespace GameBuilder.Pops
private DiscInfo disc;
private CueReader cue;
private SbiReader sbi;
private PopsImg srcImg;
public MemoryStream IsoHeader;

View File

@ -1,4 +1,5 @@
using DiscUtils.Iso9660Ps1;
using DiscUtils.Raw;
using DiscUtils.Streams;
using GameBuilder.Cue;
using Li.Utilities;
@ -16,6 +17,15 @@ namespace GameBuilder.Pops
private string discName;
private string discId;
public string? SbiFile
{
get
{
string sbiFileName = Path.ChangeExtension(CueFile, ".sbi");
if(File.Exists(sbiFileName)) return sbiFileName;
else return null;
}
}
public string CueFile
{
get

View File

@ -0,0 +1,53 @@
using GameBuilder.Cue;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder.Pops.LibCrypt
{
public class MagicWord
{
private static int[][] LIBCRYPT_PAIRS = new int[16][] { new int[2] { 14105, 14110 },
new int[2] { 14231, 14236 },
new int[2] { 14485, 14490 },
new int[2] { 14579, 14584 },
new int[2] { 14649, 14654 },
new int[2] { 14899, 14904 },
new int[2] { 15056, 15061 },
new int[2] { 15130, 15135 },
new int[2] { 15242, 15247 },
new int[2] { 15312, 15317 },
new int[2] { 15378, 15383 },
new int[2] { 15628, 15633 },
new int[2] { 15919, 15924 },
new int[2] { 16031, 16036 },
new int[2] { 16101, 16106 },
new int[2] { 16167, 16172 }
};
public static int GenMagicWord(SbiEntry[] Sbi)
{
bool[] bits = new bool[16];
int sbiI = 0;
if (Sbi.Length % 2 != 0) return 0;
int magicWord = 0;
for (int i = 0; i < bits.Length; i++)
{
bool isSet = (Sbi[sbiI].Sector == LIBCRYPT_PAIRS[i][0] && Sbi[sbiI + 1].Sector == LIBCRYPT_PAIRS[i][1]);
if (isSet) sbiI += 2;
bits[i] = isSet;
if (isSet) magicWord |= 1;
if (i + 1 < bits.Length)
magicWord <<= 1;
}
return magicWord;
}
}
}

View File

@ -1038,7 +1038,7 @@ namespace PspCrypto
return -0x7f78ffff;
}
Span<byte> secureTick = BitConverter.GetBytes(ksceRtcGetCurrentSecureTick());
Span<byte> secureTick = BitConverter.GetBytes(SceRtc.ksceRtcGetCurrentSecureTick());
ebootSig.Fill(0);
sceEbootPbp.SwVer = swVer;
@ -1281,14 +1281,6 @@ namespace PspCrypto
return 0;
}
private static ulong ksceRtcGetCurrentSecureTick()
{
DateTime epoch = new DateTime(1, 1, 1, 0, 0, 0);
DateTime now = DateTime.UtcNow;
TimeSpan ts = now.Subtract(epoch);
return Convert.ToUInt64(Math.Floor(ts.TotalMilliseconds)) * 1000;
}
private static int SceSblGcAuthMgrDrmBBForDriver_4B506BE7(ReadOnlySpan<byte> digest, ReadOnlySpan<byte> sig, int keyType)
{
byte[] pubx;

28
PspCrypto/SceRtc.cs Normal file
View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PspCrypto
{
public class SceRtc
{
public static ulong ksceRtcGetCurrentTick()
{
return ksceRtcGetCurrentSecureTick();
}
public static ulong ksceRtcGetCurrentNetworkTick()
{
return ksceRtcGetCurrentSecureTick();
}
public static ulong ksceRtcGetCurrentSecureTick()
{
DateTime epoch = new DateTime(1, 1, 1, 0, 0, 0);
DateTime now = DateTime.UtcNow;
TimeSpan ts = now.Subtract(epoch);
return Convert.ToUInt64(Math.Floor(ts.TotalMilliseconds)) * 1000;
}
}
}