251 lines
8.0 KiB
C#
251 lines
8.0 KiB
C#
using GameBuilder.Atrac3;
|
|
using Li.Progress;
|
|
using GameBuilder.Cue;
|
|
using GameBuilder.Psp;
|
|
using PspCrypto;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Li.Utilities;
|
|
using GameBuilder.Pops.LibCrypt;
|
|
|
|
namespace GameBuilder.Pops
|
|
{
|
|
public class DiscCompressor : ProgressTracker, IDisposable
|
|
{
|
|
const int COMPRESS_BLOCK_SZ = 0x9300;
|
|
const int DEFAULT_ISO_OFFSET = 0x100000;
|
|
public int IsoOffset;
|
|
|
|
internal DiscCompressor(PopsImg srcImg, PSInfo disc, IAtracEncoderBase encoder, int offset = DEFAULT_ISO_OFFSET)
|
|
{
|
|
this.srcImg = srcImg;
|
|
this.disc = disc;
|
|
this.cue = new CueReader(disc.CueFile);
|
|
|
|
this.IsoHeader = new MemoryStream();
|
|
this.CompressedIso = new MemoryStream();
|
|
|
|
this.isoHeaderUtil = new StreamUtil(IsoHeader);
|
|
this.atrac3Encoder = encoder;
|
|
this.IsoOffset = offset;
|
|
}
|
|
|
|
|
|
private void writeCompressedIsoBlock(Stream s)
|
|
{
|
|
byte[] isoBlock = new byte[COMPRESS_BLOCK_SZ];
|
|
int read = s.Read(isoBlock, 0, isoBlock.Length);
|
|
|
|
byte[] compressed = Lz.compress(isoBlock);
|
|
|
|
ushort sz = Convert.ToUInt16(compressed.Length);
|
|
int ptr = Convert.ToInt32(CompressedIso.Position);
|
|
writeIsoTblEntry(ptr, sz, compressed);
|
|
|
|
CompressedIso.Write(compressed, 0, compressed.Length);
|
|
|
|
}
|
|
|
|
|
|
private void writeIsoTblEntry(int ptr, ushort sz, byte[] data)
|
|
{
|
|
isoHeaderUtil.WriteInt32(ptr);
|
|
isoHeaderUtil.WriteUInt16(sz);
|
|
|
|
isoHeaderUtil.WriteInt16(1); // mark that this is part of the image.
|
|
|
|
isoHeaderUtil.WriteBytes(calculatePs1CompressedIsoSegmentChecksum(data));
|
|
|
|
isoHeaderUtil.WritePadding(0x00, 0x8);
|
|
}
|
|
|
|
|
|
private void writeHeader()
|
|
{
|
|
isoHeaderUtil.WriteStrWithPadding(disc.DiscIdHdr, 0x00, 0x400);
|
|
}
|
|
private void writeIsoLocation()
|
|
{
|
|
isoHeaderUtil.WriteInt32(0);
|
|
isoHeaderUtil.WriteInt32(0);
|
|
isoHeaderUtil.WriteInt32(0);
|
|
|
|
isoHeaderUtil.WriteInt32(IsoOffset); // always 0x100000 on single disc game
|
|
|
|
isoHeaderUtil.WritePadding(0x00, 0x628);
|
|
}
|
|
|
|
private void writeCompressedIso()
|
|
{
|
|
using (CueStream cueStr = cue.OpenTrack(cue.FirstDataTrackNo))
|
|
{
|
|
using (EccRemoverStream eccRem = new EccRemoverStream(cueStr))
|
|
{
|
|
while (eccRem.Position < eccRem.Length)
|
|
{
|
|
writeCompressedIsoBlock(eccRem);
|
|
updateProgress(Convert.ToInt32(eccRem.Position), Convert.ToInt32(eccRem.Length), "Compress & Encrypt Disc");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void writeSubChannelPgd()
|
|
{
|
|
if(disc.LibCrypt.Method == LibCryptMethod.METHOD_SUB_CHANNEL)
|
|
{
|
|
byte[] subChannelsData = disc.LibCrypt.Subchannels;
|
|
|
|
int sz = subChannelsData.Length / 0xC;
|
|
uint location = Convert.ToUInt32(IsoOffset + CompressedIso.Position);
|
|
writeSubchannelDatLocation(location, sz);
|
|
|
|
byte[] pgdData = srcImg.CreatePgd(subChannelsData);
|
|
|
|
CompressedIso.Write(pgdData, 0, pgdData.Length);
|
|
}
|
|
}
|
|
|
|
public void GenerateIsoHeaderAndCompress()
|
|
{
|
|
writeHeader();
|
|
writeTOC();
|
|
writeIsoLocation();
|
|
writeDiscInfo();
|
|
writeLibCryptData();
|
|
|
|
writeCompressedIso();
|
|
|
|
isoHeaderUtil.PadUntil(0x0, 0xb3880);
|
|
|
|
// write CD Audio data.
|
|
writeCompressedCDATracks();
|
|
|
|
// write subchannels
|
|
writeSubChannelPgd();
|
|
}
|
|
|
|
private void writeSubchannelDatLocation(uint location, int totalSubchannels)
|
|
{
|
|
isoHeaderUtil.WriteUInt32At(location, 0xED4);
|
|
isoHeaderUtil.WriteInt32At(totalSubchannels, 0xED8);
|
|
}
|
|
|
|
public void WriteSimpleDatLocation(uint location)
|
|
{
|
|
isoHeaderUtil.WriteUInt32At(location, 0xE20);
|
|
}
|
|
|
|
private void writeLibCryptData()
|
|
{
|
|
isoHeaderUtil.WriteInt32(disc.LibCrypt.ObfuscatedMagicWord);
|
|
isoHeaderUtil.WriteInt32(0);
|
|
isoHeaderUtil.WriteInt32(0);
|
|
isoHeaderUtil.WriteInt32(0);
|
|
|
|
isoHeaderUtil.WritePadding(0, 0x2D40);
|
|
|
|
}
|
|
private void writeDiscInfo()
|
|
{
|
|
isoHeaderUtil.WriteUInt32(Convert.ToUInt32(disc.LibCrypt.Method)); // libcrypt method
|
|
isoHeaderUtil.WriteStrWithPadding(disc.DiscName, 0x00, 0x80); // disc title
|
|
isoHeaderUtil.WriteInt32(3); // PARENTAL_LEVEL ?
|
|
}
|
|
|
|
private void writeCDAEntry(int position, int length, uint key)
|
|
{
|
|
isoHeaderUtil.WriteInt32(position);
|
|
isoHeaderUtil.WriteInt32(length);
|
|
isoHeaderUtil.WriteInt32(0);
|
|
isoHeaderUtil.WriteUInt32(key);
|
|
}
|
|
|
|
private void writeCompressedCDATracks()
|
|
{
|
|
|
|
IsoHeader.Seek(0x800, SeekOrigin.Begin); // CDA Entries
|
|
|
|
int totalTracks = cue.GetTotalTracks();
|
|
for (int i = 1; i <= totalTracks; i++)
|
|
{
|
|
if (cue.GetTrackNumber(i).TrackType != TrackType.TRACK_CDDA) continue;
|
|
updateProgress(i, totalTracks, "Convert CD Audio tracks to ATRAC3");
|
|
|
|
using (CueStream audioStream = cue.OpenTrack(i))
|
|
{
|
|
uint key = Rng.RandomUInt();
|
|
|
|
Atrac3ToolEncoder enc = new Atrac3ToolEncoder();
|
|
|
|
byte[] pcmData = new byte[audioStream.Length];
|
|
audioStream.Read(pcmData, 0x00, pcmData.Length);
|
|
|
|
byte[] atracData = enc.EncodeToAtrac(pcmData);
|
|
|
|
writeCDAEntry(Convert.ToInt32(CompressedIso.Position), atracData.Length, key);
|
|
|
|
using (MemoryStream atracStream = new MemoryStream(atracData))
|
|
{
|
|
using (MemoryStream encryptedAtracStream = new MemoryStream())
|
|
{
|
|
AtracCrypto.ScrambleAtracData(atracStream, encryptedAtracStream, key);
|
|
encryptedAtracStream.Seek(0x00, SeekOrigin.Begin);
|
|
encryptedAtracStream.CopyTo(CompressedIso);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
private byte[] calculatePs1CompressedIsoSegmentChecksum(byte[] data)
|
|
{
|
|
byte[] outChecksum = new byte[0x10];
|
|
|
|
Span<byte> mkey = stackalloc byte[Marshal.SizeOf<AMCTRL.MAC_KEY>()];
|
|
|
|
AMCTRL.sceDrmBBMacInit(mkey, 3);
|
|
AMCTRL.sceDrmBBMacUpdate(mkey, data, data.Length);
|
|
Span<byte> checksum = new byte[20 + 0x10];
|
|
AMCTRL.sceDrmBBMacFinal(mkey, checksum[20..], srcImg.DrmInfo.VersionKey);
|
|
|
|
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;
|
|
KIRKEngine.sceUtilsBufferCopyWithRange(checksum, 0x10, checksum, 0x10, KIRKEngine.KIRK_CMD_ENCRYPT_IV_0);
|
|
|
|
checksum.Slice(20, 0x10).CopyTo(outChecksum);
|
|
|
|
return outChecksum;
|
|
}
|
|
|
|
private void writeTOC()
|
|
{
|
|
isoHeaderUtil.WriteBytes(cue.CreateToc());
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
IsoHeader.Dispose();
|
|
CompressedIso.Dispose();
|
|
cue.Dispose();
|
|
}
|
|
|
|
private PSInfo disc;
|
|
private CueReader cue;
|
|
private PopsImg srcImg;
|
|
|
|
public MemoryStream IsoHeader;
|
|
public MemoryStream CompressedIso;
|
|
|
|
private StreamUtil isoHeaderUtil;
|
|
private IAtracEncoderBase atrac3Encoder;
|
|
}
|
|
}
|