chovy-sign/GameBuilder/Pops/DiscCompressor.cs

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