some shit got corrupted, will probably have to revert some stuff

This commit is contained in:
Li 2023-04-18 07:00:52 +12:00
parent f14b166dec
commit 03be164a0e
36 changed files with 1831 additions and 596 deletions

View File

@ -1,8 +1,10 @@
using GameBuilder.Pops;
using GameBuilder.Progress;
using Li.Progress;
using GameBuilder.Pops;
using GameBuilder.Psp;
using GameBuilder.VersionKey;
using PspCrypto;
using System.Reflection.PortableExecutable;
using System.IO.Pipes;
namespace ChovySign_CLI
{
@ -36,7 +38,11 @@ namespace ChovySign_CLI
}
public static int Error(string errorMsg, int ret)
{
ConsoleColor prevColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.Error.WriteLine("ERROR: "+errorMsg);
Console.ForegroundColor = prevColor;
return ret;
}
public static byte[] StringToByteArray(string hex)
@ -46,7 +52,12 @@ namespace ChovySign_CLI
private static void onProgress(ProgressInfo info)
{
Console.Write(info.CurrentProcess + " " + info.ProgressInt.ToString() + "% (" + info.Done + "/" + info.Remain + ") \r");
string msg = info.CurrentProcess + " " + info.ProgressInt.ToString() + "% (" + info.Done + "/" + info.Remain + ")";
int spaceLen = (Console.WindowWidth - msg.Length) - 2;
string emptySpace = " ";
for (int i = 0; i < spaceLen; i++)
emptySpace += " ";
Console.Write(msg + emptySpace + "\r");
}
private static int complete()
@ -98,7 +109,8 @@ namespace ChovySign_CLI
}
public static int Main(string[] args)
{
if(args.Length == 0)
if (args.Length == 0)
{
Console.WriteLine("Chovy-Sign v2 (CLI)");
Console.WriteLine("--pops [disc1.cue] [disc2.cue] [disc3.cue] ... (up to 5)");
@ -209,49 +221,33 @@ namespace ChovySign_CLI
psfo.AddKey("PSP_SYSTEM_VER", "6.60", 8);
psfo.AddKey("REGION", 32768, 4);
psfo.AddKey("TITLE", popsDiscName, 128);
byte[] sfo = psfo.WriteSfo();
if (discs.Length == 1)
{
using (PsIsoImg psIsoImg = new PsIsoImg(drmInfo, discInfs.First()))
{
psIsoImg.RegisterCallback(onProgress);
psIsoImg.CreatePsar();
PbpBuilder.CreatePbp(psfo.WriteSfo(),
File.ReadAllBytes(popsIcon0File),
null,
(popsPic0File is not null) ? File.ReadAllBytes(popsPic0File) : null,
Resources.PIC1,
null,
psIsoImg,
"EBOOT.PBP",
0);
using (PbpBuilder pbpBuilder = new PbpBuilder(sfo, File.ReadAllBytes(popsIcon0File), null, (popsPic0File is not null) ? File.ReadAllBytes(popsPic0File) : null, Resources.PIC1, null, new PsIsoImg(drmInfo, discInfs.First()), 0))
{
pbpBuilder.RegisterCallback(onProgress);
pbpBuilder.CreatePsarAndPbp();
pbpBuilder.PbpStream.Seek(0x00, SeekOrigin.Begin);
byte[] ebootsig = new byte[0x200];
SceNpDrm.KsceNpDrmEbootSigGenPs1("EBOOT.PBP", ebootsig, 0x3600000);
SceNpDrm.KsceNpDrmEbootSigGenPs1(pbpBuilder.PbpStream, ebootsig, 0x3674000);
File.WriteAllBytes("__sce_ebootpbp", ebootsig.ToArray());
pbpBuilder.WritePbpToFile("EBOOT.PBP");
}
}
else
{
using (PsTitleImg psTitleImg = new PsTitleImg(drmInfo, discInfs))
using (PbpBuilder pbpBuilder = new PbpBuilder(sfo, File.ReadAllBytes(popsIcon0File), null, (popsPic0File is not null) ? File.ReadAllBytes(popsPic0File) : null, Resources.PIC1, null, new PsTitleImg(drmInfo, discInfs), 0))
{
psTitleImg.RegisterCallback(onProgress);
psTitleImg.CreatePsar();
PbpBuilder.CreatePbp(psfo.WriteSfo(),
File.ReadAllBytes(popsIcon0File),
null,
(popsPic0File is not null) ? File.ReadAllBytes(popsPic0File) : null,
Resources.PIC1,
null,
psTitleImg,
"EBOOT.PBP",
0);
pbpBuilder.RegisterCallback(onProgress);
pbpBuilder.CreatePsarAndPbp();
pbpBuilder.PbpStream.Seek(0x00, SeekOrigin.Begin);
byte[] ebootsig = new byte[0x200];
SceNpDrm.KsceNpDrmEbootSigGenPs1("EBOOT.PBP", ebootsig, 0x3600000);
pbpBuilder.WritePbpToFile("EBOOT.PBP");
SceNpDrm.KsceNpDrmEbootSigGenPs1(pbpBuilder.PbpStream, ebootsig, 0x3674000);
File.WriteAllBytes("__sce_ebootpbp", ebootsig.ToArray());
}
}
@ -260,25 +256,16 @@ namespace ChovySign_CLI
{
using (UmdInfo umd = new UmdInfo(discs.First()))
{
using (NpUmdImg npUmd = new NpUmdImg(drmInfo, umd, pspCompress))
using (PbpBuilder pbpBuilder = new PbpBuilder(umd.DataFiles["PARAM.SFO"], umd.DataFiles["ICON0.PNG"], umd.DataFiles["ICON1.PMF"], umd.DataFiles["PIC0.PNG"], umd.DataFiles["PIC1.PNG"], umd.DataFiles["SND0.AT3"], new NpUmdImg(drmInfo, umd, pspCompress), 1))
{
npUmd.RegisterCallback(onProgress);
npUmd.CreatePsar();
PbpBuilder.CreatePbp(umd.DataFiles["PARAM.SFO"],
umd.DataFiles["ICON0.PNG"],
umd.DataFiles["ICON1.PMF"],
umd.DataFiles["PIC0.PNG"],
umd.DataFiles["PIC1.PNG"],
umd.DataFiles["SND0.AT3"],
npUmd,
"EBOOT.PBP",
1);
pbpBuilder.RegisterCallback(onProgress);
pbpBuilder.CreatePsarAndPbp();
pbpBuilder.PbpStream.Seek(0x00, SeekOrigin.Begin);
byte[] ebootsig = new byte[0x200];
SceNpDrm.KsceNpDrmEbootSigGenPsp("EBOOT.PBP", ebootsig, 0x3600000);
SceNpDrm.KsceNpDrmEbootSigGenPsp(pbpBuilder.PbpStream, ebootsig, 0x3674000);
File.WriteAllBytes("__sce_ebootpbp", ebootsig.ToArray());
pbpBuilder.WritePbpToFile("EBOOT.PBP");
}
}
}

View File

@ -2,7 +2,8 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder
namespace Li.Utilities
{
public static class MathUtil
{

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder.Progress
namespace Li.Progress
{
public class ProgressInfo
{

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder.Progress
namespace Li.Progress
{
public class ProgressTracker
{
@ -15,9 +15,9 @@ namespace GameBuilder.Progress
progressCallbacks.Add(cb);
}
public void UpdateProgress(int total, int remain, string what)
protected void updateProgress(int done, int remain, string what)
{
ProgressInfo inf = new ProgressInfo(total, remain, what);
ProgressInfo inf = new ProgressInfo(done, remain, what);
foreach (Action<ProgressInfo> cb in progressCallbacks)
cb(inf);
}

View File

@ -1,11 +1,10 @@
using PspCrypto;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder
namespace Li.Utilities
{
public class StreamUtil
{

View File

@ -13,7 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\PopsBuilder\GameBuilder.csproj" />
<ProjectReference Include="..\PspCrypto\PspCrypto.csproj" />
<ProjectReference Include="..\PsvImage\PsvImage.csproj" />
<ProjectReference Include="..\PsvImgTools\Vita.csproj" />
</ItemGroup>
</Project>

Binary file not shown.

View File

@ -1,4 +1,5 @@
using System;
using Li.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

View File

@ -8,6 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\DiscUtils\DiscUtils.csproj" />
<ProjectReference Include="..\LiGeneralUtilities\LiLib.csproj" />
<ProjectReference Include="..\PspCrypto\PspCrypto.csproj" />
</ItemGroup>

View File

@ -1,6 +1,6 @@
using GameBuilder.Atrac3;
using Li.Progress;
using GameBuilder.Cue;
using GameBuilder.Progress;
using GameBuilder.Psp;
using PspCrypto;
using System;
@ -9,10 +9,11 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Li.Utilities;
namespace GameBuilder.Pops
{
public class DiscCompressor : ProgressTracker
public class DiscCompressor : ProgressTracker, IDisposable
{
const int COMPRESS_BLOCK_SZ = 0x9300;
const int DEFAULT_ISO_OFFSET = 0x100000;
@ -103,7 +104,7 @@ namespace GameBuilder.Pops
while (eccRem.Position < eccRem.Length)
{
writeCompressedIsoBlock(eccRem);
UpdateProgress(Convert.ToInt32(eccRem.Position), Convert.ToInt32(eccRem.Length), "Compress & Encrypt Disc");
updateProgress(Convert.ToInt32(eccRem.Position), Convert.ToInt32(eccRem.Length), "Compress & Encrypt Disc");
}
}
}
@ -163,7 +164,7 @@ namespace GameBuilder.Pops
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");
updateProgress(i, totalTracks, "Convert CD Audio tracks to ATRAC3");
using (CueStream audioStream = cue.OpenTrack(i))
{
@ -219,6 +220,12 @@ namespace GameBuilder.Pops
isoHeaderUtil.WriteBytes(cue.CreateToc());
}
public void Dispose()
{
IsoHeader.Dispose();
CompressedIso.Dispose();
cue.Dispose();
}
private DiscInfo disc;
private CueReader cue;

View File

@ -1,5 +1,6 @@
using GameBuilder;
using GameBuilder.Psp;
using Li.Utilities;
using PspCrypto;
using System;
using System.Collections.Generic;
@ -10,7 +11,7 @@ using System.Threading.Tasks;
namespace GameBuilder.Pops
{
public class PopsImg : NpDrmPsar
public abstract class PopsImg : NpDrmPsar
{
public PopsImg(NpDrmInfo versionKey) : base(versionKey)
@ -28,6 +29,7 @@ namespace GameBuilder.Pops
private StreamUtil simpleUtil;
public byte[] StartDat;
public byte[] SimplePgd;
private void createSimpleDat()
{
simpleUtil.WriteStr("SIMPLE ");
@ -39,9 +41,6 @@ namespace GameBuilder.Pops
simpleUtil.WriteBytes(Resources.SIMPLE);
}
private byte[] generateSimplePgd()
{
simple.Seek(0x0, SeekOrigin.Begin);

View File

@ -1,14 +1,14 @@
using GameBuilder.Atrac3;
using Li.Progress;
using GameBuilder.Atrac3;
using GameBuilder.Cue;
using GameBuilder.Psp;
using PspCrypto;
using System;
using System.Net;
using GameBuilder.Progress;
namespace GameBuilder.Pops
{
public class PsIsoImg : PopsImg
public class PsIsoImg : PopsImg, IDisposable
{
internal PsIsoImg(NpDrmInfo versionKey, DiscCompressor discCompressor) : base(versionKey)
{
@ -26,11 +26,9 @@ namespace GameBuilder.Pops
this.compressor = new DiscCompressor(this, disc, new Atrac3ToolEncoder());
this.compressor.RegisterCallback(onProgress);
}
public void CreatePsar(bool isPartOfMultiDisc=false)
{
compressor.GenerateIsoHeaderAndCompress();
if (!isPartOfMultiDisc) compressor.WriteSimpleDatLocation((compressor.IsoOffset + compressor.CompressedIso.Length) + StartDat.Length);
internal void generatePsIsoHeader()
{
psarUtil.WriteStr("PSISOIMG0000");
psarUtil.WriteInt64(0x00); // location of STARTDAT
@ -39,12 +37,23 @@ namespace GameBuilder.Pops
byte[] isoHdrPgd = compressor.GenerateIsoPgd();
psarUtil.WriteBytes(isoHdrPgd);
psarUtil.PadUntil(0x00, compressor.IsoOffset);
}
public override void CreatePsar()
{
// compress ISO and generate header.
compressor.GenerateIsoHeaderAndCompress();
// write STARTDAT location
compressor.WriteSimpleDatLocation((compressor.IsoOffset + compressor.CompressedIso.Length) + StartDat.Length);
// write general PSISO header
generatePsIsoHeader();
// Copy compressed ISO to PSAR stream..
compressor.CompressedIso.Seek(0x00, SeekOrigin.Begin);
compressor.CompressedIso.CopyTo(Psar);
if (isPartOfMultiDisc) return;
// write STARTDAT
Int64 startDatLocation = Psar.Position;
psarUtil.WriteBytes(StartDat);
@ -57,10 +66,14 @@ namespace GameBuilder.Pops
psarUtil.WriteInt64(startDatLocation);
}
public override void Dispose()
{
compressor.Dispose();
base.Dispose();
}
private void onProgress(ProgressInfo inf)
{
this.UpdateProgress(inf.Done, inf.Remain, inf.CurrentProcess);
this.updateProgress(inf.Done, inf.Remain, inf.CurrentProcess);
}
private DiscCompressor compressor;

View File

@ -1,5 +1,6 @@
using GameBuilder.Atrac3;
using GameBuilder.Progress;
using Li.Progress;
using Li.Utilities;
using GameBuilder.Atrac3;
using GameBuilder.Psp;
using PspCrypto;
using System;
@ -20,7 +21,7 @@ namespace GameBuilder.Pops
private void onProgress(ProgressInfo inf)
{
this.UpdateProgress(inf.Done, inf.Remain, inf.CurrentProcess + " (disc " + discNumber + ")");
this.updateProgress(inf.Done, inf.Remain, inf.CurrentProcess + " (disc " + discNumber + ")");
}
public PsTitleImg(NpDrmInfo drmInfo, DiscInfo[] discs) : base(drmInfo)
@ -52,7 +53,7 @@ namespace GameBuilder.Pops
}
public void CreatePsar()
public override void CreatePsar()
{
createIsoMap();
@ -119,20 +120,31 @@ namespace GameBuilder.Pops
using (PsIsoImg psIsoImg = new PsIsoImg(this.DrmInfo, compressors[i]))
{
// write location of iso
isoMapUtil.WriteUInt32(Convert.ToUInt32(PSISO_ALIGN + isoPart.Position));
psIsoImg.CreatePsar(true);
// compress current iso and generate headers
compressors[i].GenerateIsoHeaderAndCompress();
// generate PSISOIMG header
psIsoImg.generatePsIsoHeader();
// Copy compressed ISO to PSISOIMG
compressors[i].CompressedIso.Seek(0x00, SeekOrigin.Begin);
compressors[i].CompressedIso.CopyTo(psIsoImg.Psar);
// read 0x400 bytes from PSAR copy iso header after that,.
psIsoImg.Psar.Seek(0x0, SeekOrigin.Begin);
compressors[i].IsoHeader.Seek(0x00, SeekOrigin.Begin);
byte[] isoHdr = new byte[compressors[i].IsoHeader.Length + 0x400];
psIsoImg.Psar.Read(isoHdr, 0x00, 0x400);
compressors[i].IsoHeader.Read(isoHdr, 0x400, Convert.ToInt32(compressors[i].IsoHeader.Length));
// Calculate checksum
byte[] checksum = calculateChecksumForIsoImgTitle(isoHdr);
Array.ConstrainedCopy(checksum, 0, checksums, i * 0x10, 0x10);
// copy psiso to TITLE ..
psIsoImg.Psar.Seek(0x00, SeekOrigin.Begin);
psIsoImg.Psar.CopyTo(isoPart);

View File

@ -1,4 +1,5 @@
using GameBuilder.Progress;
using Li.Progress;
using Li.Utilities;
using PspCrypto;
using System;
using System.Collections.Generic;
@ -24,6 +25,7 @@ namespace GameBuilder.Psp
public NpDrmInfo DrmInfo;
public MemoryStream Psar;
internal StreamUtil psarUtil;
public abstract void CreatePsar();
public abstract byte[] GenerateDataPsp();
public static byte[] CreateStartDat(byte[] image)
{

View File

@ -9,6 +9,7 @@ using System.Net.Security;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Li.Utilities;
namespace GameBuilder.Psp
{
@ -71,7 +72,7 @@ namespace GameBuilder.Psp
npHdrUtil.PadUntil(0x00, 0x100);
}
public void CreatePsar()
public override void CreatePsar()
{
patchSfo();
createNpUmdTbl();
@ -167,7 +168,7 @@ namespace GameBuilder.Psp
isoOffset += wsize;
UpdateProgress(Convert.ToInt32(umdImage.IsoStream.Position), Convert.ToInt32(umdImage.IsoStream.Length), "Compress & Encrypt UMD Image");
updateProgress(Convert.ToInt32(umdImage.IsoStream.Position), Convert.ToInt32(umdImage.IsoStream.Length), "Compress & Encrypt UMD Image");
}
}

View File

@ -1,4 +1,5 @@
using GameBuilder.Progress;
using Li.Progress;
using Li.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
@ -7,75 +8,141 @@ using System.Threading.Tasks;
namespace GameBuilder.Psp
{
public static class PbpBuilder
public class PbpBuilder : ProgressTracker , IDisposable
{
public static void CreatePbp(byte[]? paramSfo, byte[]? icon0Png, byte[]? icon1Png,
byte[]? pic0Png, byte[]? pic1Png, byte[]? snd0At3,
NpDrmPsar dataPsar, string outputFile, short version = 1)
private byte[] paramSfo;
private byte[] icon0Png;
private byte[]? icon1Pmf;
private byte[]? pic0Png;
private byte[]? pic1Png;
private byte[]? snd0At3;
private NpDrmPsar psar;
private short pbpVersion;
private MemoryStream pbpStream;
public PbpBuilder(byte[] paramSfo, byte[] icon0Png, byte[]? icon1Pmf,
byte[]? pic0Png, byte[]? pic1Png, byte[]? snd0At3,
NpDrmPsar dataPsar, short version = 1)
{
this.paramSfo = paramSfo;
this.icon0Png = icon0Png;
this.icon1Pmf = icon1Pmf;
this.pic0Png = pic0Png;
this.pic1Png = pic1Png;
this.snd0At3 = snd0At3;
this.psar = dataPsar;
this.pbpVersion = version;
using (FileStream pbpStream = File.Open(outputFile, FileMode.Create))
{
byte[] dataPsp = dataPsar.GenerateDataPsp();
int padLen = MathUtil.CalculatePaddingAmount(dataPsp.Length, 0x100);
if(version == 1)
Array.Resize(ref dataPsp, dataPsp.Length + padLen);
StreamUtil pbpUtil = new StreamUtil(pbpStream);
pbpUtil.WriteByte(0x00);
pbpUtil.WriteStr("PBP");
pbpUtil.WriteInt16(version);
pbpUtil.WriteInt16(1);
// param location
uint loc = 0x28;
if (paramSfo is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(paramSfo.Length); }
// icon0 location
if (icon0Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(icon0Png.Length); }
// icon1 location
if (icon1Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(icon1Png.Length); }
// pic0 location
if (pic0Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(pic0Png.Length); }
// pic1 location
if (pic1Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(pic1Png.Length); }
// snd0 location
if (snd0At3 is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(snd0At3.Length); }
// datapsp location
pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(dataPsp.Length);
// psar location
pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(dataPsar.Psar.Length);
// write pbp metadata
if (paramSfo is not null) pbpUtil.WriteBytes(paramSfo);
if (icon0Png is not null) pbpUtil.WriteBytes(icon0Png);
if (icon1Png is not null) pbpUtil.WriteBytes(icon1Png);
if (pic0Png is not null) pbpUtil.WriteBytes(pic0Png);
if (pic1Png is not null) pbpUtil.WriteBytes(pic1Png);
if (snd0At3 is not null) pbpUtil.WriteBytes(snd0At3);
// write DATA.PSP
pbpUtil.WriteBytes(dataPsp);
// write DATA.PSAR
dataPsar.Psar.Seek(0x00, SeekOrigin.Begin);
dataPsar.Psar.CopyTo(pbpStream);
}
pbpStream = new MemoryStream();
psar.RegisterCallback(onProgress);
}
public MemoryStream PbpStream
{
get
{
return pbpStream;
}
}
private void onProgress(ProgressInfo inf)
{
updateProgress(inf.Done, inf.Remain, inf.CurrentProcess);
}
public void CreatePsarAndPbp()
{
psar.CreatePsar();
CreatePbp();
}
public void WritePbpToFile(string file)
{
using(FileStream pbpFile = File.OpenWrite(file))
{
pbpStream.Seek(0x00, SeekOrigin.Begin);
copyPsarWithProgress(pbpStream, pbpFile, "Write to Disk");
}
}
public void CreatePbp()
{
byte[] dataPsp = psar.GenerateDataPsp();
int padLen = MathUtil.CalculatePaddingAmount(dataPsp.Length, 0x100);
if(pbpVersion == 1)
Array.Resize(ref dataPsp, dataPsp.Length + padLen);
StreamUtil pbpUtil = new StreamUtil(pbpStream);
pbpUtil.WriteByte(0x00);
pbpUtil.WriteStr("PBP");
pbpUtil.WriteInt16(pbpVersion);
pbpUtil.WriteInt16(1);
// param location
uint loc = 0x28;
if (paramSfo is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(paramSfo.Length); }
// icon0 location
if (icon0Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(icon0Png.Length); }
// icon1 location
if (icon1Pmf is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(icon1Pmf.Length); }
// pic0 location
if (pic0Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(pic0Png.Length); }
// pic1 location
if (pic1Png is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(pic1Png.Length); }
// snd0 location
if (snd0At3 is null) { pbpUtil.WriteUInt32(loc); }
else { pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(snd0At3.Length); }
// datapsp location
pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(dataPsp.Length);
// psar location
pbpUtil.WriteUInt32(loc); loc += Convert.ToUInt32(psar.Psar.Length);
// write pbp metadata
if (paramSfo is not null) pbpUtil.WriteBytes(paramSfo);
if (icon0Png is not null) pbpUtil.WriteBytes(icon0Png);
if (icon1Pmf is not null) pbpUtil.WriteBytes(icon1Pmf);
if (pic0Png is not null) pbpUtil.WriteBytes(pic0Png);
if (pic1Png is not null) pbpUtil.WriteBytes(pic1Png);
if (snd0At3 is not null) pbpUtil.WriteBytes(snd0At3);
// write DATA.PSP
pbpUtil.WriteBytes(dataPsp);
// write DATA.PSAR
copyPsarWithProgress(psar.Psar, pbpStream);
}
private void copyPsarWithProgress(Stream src, Stream dst, string msg = "Build PBP")
{
src.Seek(0, SeekOrigin.Begin);
while (src.Position < src.Length)
{
byte[] readBuffer = new byte[0x30000];
int readAmt = src.Read(readBuffer, 0x00, readBuffer.Length);
dst.Write(readBuffer, 0x00, readAmt);
updateProgress(Convert.ToInt32(src.Position), Convert.ToInt32(src.Length), msg);
}
}
public void Dispose()
{
psar.Dispose();
pbpStream.Dispose();
}
}
}

View File

@ -1,10 +1,11 @@
using System;
using Li.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.IO;
using System.Runtime.CompilerServices;
// A Sfo Parser Written by SilicaAndPina
// A Sfo Parser Written by Li
// Because all the others are overly-complicated for no reason!
// MIT Licensed.

View File

@ -1,4 +1,5 @@
using GameBuilder.Psp;
using Li.Utilities;
using PspCrypto;
using System;
using System.Collections.Generic;

View File

@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameBuilder
{
public class xXEccD3str0yerXx
{
const int SECTOR_SZ = 2352;
const int COMPRESS_BLOCK_SZ = 0x9300;
public static MemoryStream RemoveEcc(string iso)
{
using(FileStream eccIso = File.OpenRead(iso))
{
MemoryStream noEccIso = new MemoryStream();
while (eccIso.Position < eccIso.Length)
{
byte[] sector = new byte[SECTOR_SZ];
eccIso.Read(sector, 0, sector.Length);
// clear current sync
Array.Fill(sector, (byte)0x00, 0x1, 0x0A);
// remove MSF ..
sector[0x0C] = 0x00; // M
sector[0x0D] = 0x00; // S
sector[0x0E] = 0x00; // F
// remove ecc
// (only if this is not form2mode2 sector!)
if (!(sector[0xF] == 0x2 && (sector[0x12] & 0x20) == 0x20))
Array.Fill(sector, (byte)0x00, 0x818, 0x118);
else if(eccIso.Position > 0x9300) // only clear if its past the system section ..
Array.Fill(sector, (byte)0x00, 0x92C, 0x4);
noEccIso.Write(sector, 0, sector.Length);
}
// extend ISO image to compress block sz
int padLen = MathUtil.CalculatePaddingAmount(Convert.ToInt32(noEccIso.Length), COMPRESS_BLOCK_SZ);
byte[] padding = new byte[padLen];
noEccIso.Write(padding, 0x00, padding.Length);
noEccIso.Seek(0, SeekOrigin.Begin);
return noEccIso;
}
}
}
}

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.1.1" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0" />
<PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>

View File

@ -721,6 +721,20 @@ namespace PspCrypto
{
return SceNpDrmEbootSigGenMultiDisc(fileName, sceDiscInfo, ebootSig, swVer, _memory.Span, BLK_SIZE);
}
public static int KsceNpDrmEbootSigGenPs1(Stream file, Span<byte> ebootSig, int swVer)
{
return SceNpDrmEbootSigGen(file, 3, ebootSig, swVer, _memory.Span, BLK_SIZE);
}
public static int KsceNpDrmEbootSigGenPsp(Stream file, Span<byte> ebootSig, int swVer)
{
return SceNpDrmEbootSigGen(file, 2, ebootSig, swVer, _memory.Span, BLK_SIZE);
}
public static int KsceNpDrmPspEbootSigGen(Stream file, Span<byte> ebootSig)
{
return SceNpDrmPspEbootSigGen(file, ebootSig, _memory.Span, BLK_SIZE);
}
public static int KsceNpDrmEbootSigGenPs1(string fileName, Span<byte> ebootSig, int swVer)
{
@ -996,8 +1010,20 @@ namespace PspCrypto
return ret;
}
private static int SceNpDrmEbootSigGen(string fileName, int type, Span<byte> ebootSig, int swVer, Span<byte> buffer,
int blockSize)
private static int SceNpDrmEbootSigGen(string fileName, int type, Span<byte> ebootSig, int swVer, Span<byte> buffer, int blockSize)
{
if(string.IsNullOrWhiteSpace(fileName)) return -0x7f78ffff;
var fi = new FileInfo(fileName);
if (!fi.Exists)
{
return -1;
}
using(FileStream fstream = fi.OpenRead()){
return SceNpDrmEbootSigGen(fstream, type, ebootSig, swVer, buffer, blockSize);
}
}
private static int SceNpDrmEbootSigGen(Stream ebootStream, int type, Span<byte> ebootSig, int swVer, Span<byte> buffer, int blockSize)
{
Span<byte> pbpHdrDigest = stackalloc byte[32];
Span<byte> npUmdImgDigest = stackalloc byte[32];
@ -1008,8 +1034,7 @@ namespace PspCrypto
blockSize &= unchecked((int)0xFFFFFFC0);
}
if (string.IsNullOrWhiteSpace(fileName) || buffer == null || ebootSig == null || blockSize < 0x400 ||
type > 3)
if (ebootStream == null || buffer == null || ebootSig == null || blockSize < 0x400 || type > 3)
{
return -0x7f78ffff;
}
@ -1021,21 +1046,16 @@ namespace PspCrypto
sceEbootPbp.SwVer = swVer;
sceEbootPbp.Aid = Aid;
sceEbootPbp.SecureTick = Utils.AsRef<ulong>(secureTick);
var fi = new FileInfo(fileName);
if (!fi.Exists)
{
return -1;
}
using var stream = fi.OpenRead();
var ret = SceEbootPbpDigest(stream, fi.Length, pbpHdrDigest, npUmdImgDigest, buffer, blockSize);
long flen = ebootStream.Length;
int ret = SceEbootPbpDigest(ebootStream, flen, pbpHdrDigest, npUmdImgDigest, buffer, blockSize);
if (ret < 0)
{
return ret;
}
stream.Close();
sceEbootPbp.KeyType = 1;
sceEbootPbp.PbpSize = fi.Length;
sceEbootPbp.PbpSize = flen;
sceEbootPbp.Type = type;
var psarSig = Encoding.ASCII.GetString(buffer.Slice(0, 8));
if (type == 3)
@ -1178,8 +1198,23 @@ namespace PspCrypto
return ret;
}
private static int SceNpDrmPspEbootSigGen(string fileName, Span<byte> ebootSig, Span<byte> buffer,
int blockSize)
private static int SceNpDrmPspEbootSigGen(string fileName, Span<byte> ebootSig, Span<byte> buffer, int blockSize)
{
if(string.IsNullOrWhiteSpace(fileName))
return -0x7f78ffff;
var fi = new FileInfo(fileName);
if (!fi.Exists)
{
return -1;
}
using(FileStream fs = fi.OpenRead())
{
return SceNpDrmPspEbootSigGen(fs, ebootSig, buffer, blockSize);
}
}
private static int SceNpDrmPspEbootSigGen(Stream ebootStream, Span<byte> ebootSig, Span<byte> buffer,int blockSize)
{
Span<byte> pbpHdrDigest = stackalloc byte[32];
Span<byte> npUmdImgDigest = stackalloc byte[32];
@ -1190,23 +1225,17 @@ namespace PspCrypto
blockSize &= unchecked((int)0xFFFFFFC0);
}
if (string.IsNullOrWhiteSpace(fileName) || buffer == null || ebootSig == null || blockSize < 0x400)
if (ebootStream == null || buffer == null || ebootSig == null || blockSize < 0x400)
{
return -0x7f78ffff;
}
var fi = new FileInfo(fileName);
if (!fi.Exists)
{
return -1;
}
using var stream = fi.OpenRead();
var ret = SceEbootPbpDigest(stream, fi.Length, pbpHdrDigest, npUmdImgDigest, buffer, blockSize);
long fileSize = ebootStream.Length;
int ret = SceEbootPbpDigest(ebootStream, fileSize, pbpHdrDigest, npUmdImgDigest, buffer, blockSize);
if (ret < 0)
{
return ret;
}
stream.Close();
if (Encoding.ASCII.GetString(buffer.Slice(0, 8)) != "NPUMDIMG")
{

View File

@ -6,11 +6,13 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PspCrypto", "PspCrypto\PspCrypto.csproj", "{A60BE4C2-C0D2-42FD-BCFF-631B9AFC62FE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PbpResign", "PbpResign\PbpResign.csproj", "{834B9F97-1B63-48EB-922E-0FC155BB2481}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PsvImage", "PsvImage\PsvImage.csproj", "{0F4A59D9-FF92-4B4F-BDC4-4BC473F9AA5C}"
ProjectSection(ProjectDependencies) = postProject
{75BBF537-8ED8-42A3-AA8B-80A730F1B3F2} = {75BBF537-8ED8-42A3-AA8B-80A730F1B3F2}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameBuilder", "PopsBuilder\GameBuilder.csproj", "{D1DF66DB-52BE-489C-A292-525BC71F2BB2}"
ProjectSection(ProjectDependencies) = postProject
{63475285-AAD2-4DE9-B698-AFC9FAA58AC8} = {63475285-AAD2-4DE9-B698-AFC9FAA58AC8}
{BF155D74-2923-432F-8566-26D83B15EEE8} = {BF155D74-2923-432F-8566-26D83B15EEE8}
EndProjectSection
EndProject
@ -19,7 +21,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChovySign-CLI", "ChovySign-
{A60BE4C2-C0D2-42FD-BCFF-631B9AFC62FE} = {A60BE4C2-C0D2-42FD-BCFF-631B9AFC62FE}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscUtils", "DiscUtils\DiscUtils.csproj", "{BF155D74-2923-432F-8566-26D83B15EEE8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils", "DiscUtils\DiscUtils.csproj", "{BF155D74-2923-432F-8566-26D83B15EEE8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vita", "PsvImgTools\Vita.csproj", "{75BBF537-8ED8-42A3-AA8B-80A730F1B3F2}"
ProjectSection(ProjectDependencies) = postProject
{63475285-AAD2-4DE9-B698-AFC9FAA58AC8} = {63475285-AAD2-4DE9-B698-AFC9FAA58AC8}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiLib", "LiGeneralUtilities\LiLib.csproj", "{63475285-AAD2-4DE9-B698-AFC9FAA58AC8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -35,10 +44,6 @@ Global
{834B9F97-1B63-48EB-922E-0FC155BB2481}.Debug|Any CPU.Build.0 = Debug|Any CPU
{834B9F97-1B63-48EB-922E-0FC155BB2481}.Release|Any CPU.ActiveCfg = Release|Any CPU
{834B9F97-1B63-48EB-922E-0FC155BB2481}.Release|Any CPU.Build.0 = Release|Any CPU
{0F4A59D9-FF92-4B4F-BDC4-4BC473F9AA5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F4A59D9-FF92-4B4F-BDC4-4BC473F9AA5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F4A59D9-FF92-4B4F-BDC4-4BC473F9AA5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F4A59D9-FF92-4B4F-BDC4-4BC473F9AA5C}.Release|Any CPU.Build.0 = Release|Any CPU
{D1DF66DB-52BE-489C-A292-525BC71F2BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1DF66DB-52BE-489C-A292-525BC71F2BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1DF66DB-52BE-489C-A292-525BC71F2BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -51,6 +56,14 @@ Global
{BF155D74-2923-432F-8566-26D83B15EEE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF155D74-2923-432F-8566-26D83B15EEE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF155D74-2923-432F-8566-26D83B15EEE8}.Release|Any CPU.Build.0 = Release|Any CPU
{75BBF537-8ED8-42A3-AA8B-80A730F1B3F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75BBF537-8ED8-42A3-AA8B-80A730F1B3F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75BBF537-8ED8-42A3-AA8B-80A730F1B3F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75BBF537-8ED8-42A3-AA8B-80A730F1B3F2}.Release|Any CPU.Build.0 = Release|Any CPU
{63475285-AAD2-4DE9-B698-AFC9FAA58AC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63475285-AAD2-4DE9-B698-AFC9FAA58AC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63475285-AAD2-4DE9-B698-AFC9FAA58AC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63475285-AAD2-4DE9-B698-AFC9FAA58AC8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace PsvImage
{
static class AesHelper
{
public static byte[] CbcEncrypt(byte[] plainText, byte[] iv, byte[] key, int size = -1)
{
if (size < 0)
{
size = plainText.Length;
}
using var aes = Aes.Create();
aes.Padding = PaddingMode.None;
aes.Key = key;
aes.IV = iv;
using var encrypt = aes.CreateEncryptor();
var cipherText = encrypt.TransformFinalBlock(plainText, 0, size);
return cipherText;
}
public static byte[] AesEcbDecrypt(byte[] cipherText, byte[] key, int size = -1)
{
if (size < 0)
{
size = cipherText.Length;
}
using var aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
aes.Key = key;
using var decrypt = aes.CreateDecryptor();
var plainText = decrypt.TransformFinalBlock(cipherText, 0, size);
return plainText;
}
}
}

View File

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace PsvImage
{
public class CmaKeys
{
private static readonly byte[] Passphrase = Encoding.ASCII.GetBytes("Sri Jayewardenepura Kotte");
private static readonly byte[] Key = { 0xA9, 0xFA, 0x5A, 0x62, 0x79, 0x9F, 0xCC, 0x4C, 0x72, 0x6B, 0x4E, 0x2C, 0xE3, 0x50, 0x6D, 0x38 };
public static byte[] GenerateKey(string aid)
{
var longlong = Convert.ToUInt64(aid, 16);
var aidBytes = BitConverter.GetBytes(longlong);
Array.Reverse(aidBytes);
return GenerateKey(aidBytes);
}
public static byte[] GenerateKey(byte[] aid)
{
var derviedKey = aid.Concat(Passphrase).ToArray();
using var sha = SHA256.Create();
derviedKey = sha.ComputeHash(derviedKey);
using var aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
aes.Key = Key;
using var decryptor = aes.CreateDecryptor();
derviedKey = decryptor.TransformFinalBlock(derviedKey, 0, derviedKey.Length);
return derviedKey;
}
}
}

View File

@ -1,119 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace PsvImage
{
class PSVIMGBuilder
{
private byte[] iv;
private byte[] key;
private Stream mainStream;
private SHA256 sha256;
private Memory<byte> blockData;
private int blockPosition;
private long contentSize = 0;
private int blocksWritten = 0;
private bool finished = false;
public long ContentSize => contentSize;
public int BlocksWritten => blocksWritten;
public bool HasFinished => finished;
//Footer
private long totalBytes = 0;
public PSVIMGBuilder(Stream dst, byte[] Key)
{
totalBytes = 0;
contentSize = 0;
sha256 = SHA256.Create();
mainStream = dst;
key = Key;
RandomNumberGenerator.Fill(iv);
iv = AesHelper.AesEcbDecrypt(iv, key);
mainStream.Write(iv.AsSpan());
totalBytes += iv.Length;
startNewBlock();
}
public void AddFile(string FilePath, string ParentPath, string PathRel)
{
var sz = Utils.ToSceIoStat(FilePath).Size;
}
private void startNewBlock()
{
blockData = new byte[PSVIMGConstants.FULL_PSVIMG_SIZE];
blockPosition = 0;
}
private byte[] shaBlock(int length = PSVIMGConstants.PSVIMG_BLOCK_SIZE)
{
return sha256.ComputeHash(blockData.ToArray(), 0, length);
}
private void finishBlock(bool final = false)
{
int len = blockPosition;
var shaBytes = shaBlock(len);
shaBytes.CopyTo(blockData.Slice(blockPosition));
len += PSVIMGConstants.SHA256_BLOCK_SIZE;
//Get next IV
var encryptedBlock = AesHelper.CbcEncrypt(blockData.ToArray(), iv, key, len);
for (int i = 0; i < iv.Length; i++)
{
int encBlockOffset = (encryptedBlock.Length - iv.Length) + i;
iv[i] = encryptedBlock[encBlockOffset];
}
mainStream.Write(encryptedBlock.AsSpan());
totalBytes += encryptedBlock.Length;
}
private int remainingBlockSize()
{
return (int)(PSVIMGConstants.PSVIMG_BLOCK_SIZE - blockPosition);
}
private void writeBlock(Span<byte> date, bool update = false)
{
var dLen = date.Length;
var writeTotal = 0;
while (dLen > 0)
{
int remaining = remainingBlockSize();
if (dLen > remaining)
{
date.Slice(writeTotal, remaining).CopyTo(blockData.Span.Slice(blockPosition));
blockPosition += remaining;
writeTotal += remaining;
dLen -= remaining;
finishBlock();
startNewBlock();
if (update)
{
blocksWritten += 1;
}
}
else
{
}
}
}
}
}

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace PsvImage
{
class PsvImgStream : Stream
{
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override bool CanRead { get; }
public override bool CanSeek { get; }
public override bool CanWrite { get; }
public override long Length { get; }
public override long Position { get; set; }
}
}

View File

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace PsvImage
{
class PsvmdBuilder
{
public static void CreatePsvmd(Stream OutputStream, Stream EncryptedPsvimg, long ContentSize, string BackupType,
byte[] Key)
{
Span<byte> iv = new byte[PSVIMGConstants.AES_BLOCK_SIZE];
EncryptedPsvimg.Seek(0, SeekOrigin.Begin);
EncryptedPsvimg.Read(iv);
iv = AesHelper.AesEcbDecrypt(iv.ToArray(), Key);
}
}
}

View File

@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace PsvImage
{
static class Utils
{
public static SceDateTime ToSceDateTime(this DateTime dateTime)
{
var sceDateTime = new SceDateTime();
sceDateTime.Year = (ushort)dateTime.Year;
sceDateTime.Month = (ushort)dateTime.Month;
sceDateTime.Day = (ushort)dateTime.Day;
sceDateTime.Hour = (ushort)dateTime.Hour;
sceDateTime.Minute = (ushort)dateTime.Minute;
sceDateTime.Second = (ushort)dateTime.Second;
sceDateTime.Microsecond = (uint)dateTime.Millisecond * 1000;
return sceDateTime;
}
public static SceIoStat ToSceIoStat(string path)
{
var stats = new SceIoStat();
var attributes = File.GetAttributes(path);
if (attributes.HasFlag(FileAttributes.Directory))
{
stats.Mode |= SceIoStat.Modes.Directory;
stats.Size = 0;
}
else
{
stats.Mode |= SceIoStat.Modes.File;
stats.Size = (ulong)(new FileInfo(path).Length);
}
if (attributes.HasFlag(FileAttributes.ReadOnly))
{
stats.Mode |= SceIoStat.Modes.GroupRead;
stats.Mode |= SceIoStat.Modes.OthersRead;
stats.Mode |= SceIoStat.Modes.UserRead;
}
else
{
stats.Mode |= SceIoStat.Modes.GroupRead;
stats.Mode |= SceIoStat.Modes.GroupWrite;
stats.Mode |= SceIoStat.Modes.OthersRead;
stats.Mode |= SceIoStat.Modes.OthersWrite;
stats.Mode |= SceIoStat.Modes.UserRead;
stats.Mode |= SceIoStat.Modes.UserWrite;
}
stats.CreationTime = File.GetCreationTimeUtc(path).ToSceDateTime();
stats.AccessTime = File.GetLastAccessTimeUtc(path).ToSceDateTime();
stats.ModificaionTime = File.GetLastWriteTimeUtc(path).ToSceDateTime();
return stats;
}
}
}

Binary file not shown.

View File

@ -0,0 +1,516 @@
using Li.Progress;
using Org.BouncyCastle.Crypto.Digests;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using static Vita.PsvImgTools.SceIoStat;
namespace Vita.PsvImgTools
{
public class PSVIMGBuilder : ProgressTracker
{
private const Int64 BUFFER_SZ = 0x33554432;
private byte[] IV = new byte[0x10];
private byte[] KEY;
private Random rnd = new Random();
private Stream mainStream;
private Sha256Digest shaCtx;
private byte[] blockData;
private MemoryStream blockStream;
private long contentSize = 0;
//async
private int blocksWritten = 0;
private bool finished = false;
public Int64 ContentSize
{
get
{
return contentSize;
}
}
public Int32 BlocksWritten
{
get
{
return blocksWritten;
}
}
public Boolean HasFinished
{
get
{
return finished;
}
}
//Footer
private long totalBytes = 0;
private byte[] aes_cbc_encrypt(byte[] plainText, byte[] IV, byte[] KEY, int size=-1)
{
if (size < 0)
{
size = plainText.Length;
}
MemoryStream ms = new MemoryStream();
/* DEBUG Disable Encryption
ms.Write(plainText, 0x00, size);
ms.Seek(0x00,SeekOrigin.Begin);
return ms.ToArray();*/
Aes alg = Aes.Create();
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.None;
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = KEY;
alg.IV = IV;
CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(plainText, 0, size);
cs.Close();
byte[] cipherText = ms.ToArray();
return cipherText;
}
private byte[] aes_ecb_encrypt(byte[] plainText, byte[] KEY, int size = -1)
{
if (size < 0)
{
size = plainText.Length;
}
MemoryStream ms = new MemoryStream();
/* DEBUG Disable Encryption
ms.Write(plainText, 0x00, size);
ms.Seek(0x00,SeekOrigin.Begin);
return ms.ToArray();*/
Aes alg = Aes.Create();
alg.Mode = CipherMode.ECB;
alg.Padding = PaddingMode.None;
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = KEY;
CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(plainText, 0, size);
cs.Close();
byte[] cipherText = ms.ToArray();
return cipherText;
}
// TODO: Switch to Li.Utilities.StreamUtil for this .
private void writeUInt64(Stream dst,UInt64 value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x8);
}
private void writeInt64(Stream dst,Int64 value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x8);
}
private void writeUInt16(Stream dst, UInt16 value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x2);
}
private void writeInt16(Stream dst, Int16 value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x2);
}
private void writeInt32(Stream dst, Int32 value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x4);
}
private void writeUInt32(Stream dst, UInt32 value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x4);
}
internal virtual SceDateTime dateTimeToSceDateTime(DateTime dt)
{
SceDateTime sdt = new SceDateTime();
sdt.Day = Convert.ToUInt16(dt.Day);
sdt.Month = Convert.ToUInt16(dt.Month);
sdt.Year = Convert.ToUInt16(dt.Year);
sdt.Hour = Convert.ToUInt16(dt.Hour);
sdt.Minute = Convert.ToUInt16(dt.Minute);
sdt.Second = Convert.ToUInt16(dt.Second);
sdt.Microsecond = Convert.ToUInt32(dt.Millisecond * 1000);
return sdt;
}
internal virtual SceIoStat sceIoStat(Stream str)
{
SceIoStat stats = new SceIoStat();
// streams being a directory doesnt really make sense ..
stats.Mode |= Modes.File;
// set size..
stats.Size = Convert.ToUInt64(str.Length);
// fake the rest--
stats.Mode |= Modes.GroupRead;
stats.Mode |= Modes.GroupWrite;
stats.Mode |= Modes.OthersRead;
stats.Mode |= Modes.OthersWrite;
stats.Mode |= Modes.UserRead;
stats.Mode |= Modes.UserWrite;
stats.CreationTime = dateTimeToSceDateTime(DateTime.Now);
stats.AccessTime = dateTimeToSceDateTime(DateTime.Now);
stats.ModificaionTime = dateTimeToSceDateTime(DateTime.Now);
return stats;
}
internal virtual SceIoStat sceIoStat(string path)
{
SceIoStat stats = new SceIoStat();
FileAttributes attrbutes = File.GetAttributes(path);
if (attrbutes.HasFlag(FileAttributes.Directory))
{
stats.Mode |= Modes.Directory;
stats.Size = 0;
}
else
{
stats.Mode |= Modes.File;
stats.Size = Convert.ToUInt64(new FileInfo(path).Length);
}
if(attrbutes.HasFlag(FileAttributes.ReadOnly))
{
stats.Mode |= Modes.GroupRead;
stats.Mode |= Modes.OthersRead;
stats.Mode |= Modes.UserRead;
}
else
{
stats.Mode |= Modes.GroupRead;
stats.Mode |= Modes.GroupWrite;
stats.Mode |= Modes.OthersRead;
stats.Mode |= Modes.OthersWrite;
stats.Mode |= Modes.UserRead;
stats.Mode |= Modes.UserWrite;
}
stats.CreationTime = dateTimeToSceDateTime(File.GetCreationTimeUtc(path));
stats.AccessTime = dateTimeToSceDateTime(File.GetLastAccessTimeUtc(path));
stats.ModificaionTime = dateTimeToSceDateTime(File.GetLastWriteTimeUtc(path));
return stats;
}
internal virtual void writeSceDateTime(Stream dst,SceDateTime time)
{
writeUInt16(dst, time.Year);
writeUInt16(dst, time.Month);
writeUInt16(dst, time.Day);
writeUInt16(dst, time.Hour);
writeUInt16(dst, time.Minute);
writeUInt16(dst, time.Second);
writeUInt32(dst, time.Microsecond);
}
internal virtual void writeSceIoStat(Stream dst, SceIoStat stats)
{
writeUInt32(dst, Convert.ToUInt32(stats.Mode));
writeUInt32(dst, Convert.ToUInt32(stats.Attributes));
writeUInt64(dst, stats.Size);
writeSceDateTime(dst, stats.CreationTime);
writeSceDateTime(dst, stats.AccessTime);
writeSceDateTime(dst, stats.ModificaionTime);
foreach(UInt32 i in stats.Private)
{
writeUInt32(dst,i);
}
}
internal virtual void memset(byte[] buf, byte content, long length)
{
for(int i = 0; i < length; i++)
{
buf[i] = content;
}
}
internal virtual void writeStringWithPadding(Stream dst, string str, int padSize, byte padByte = 0x78)
{
int StrLen = str.Length;
if(StrLen > padSize)
{
StrLen = padSize;
}
int PaddingLen = (padSize - StrLen)-1;
writeString(dst, str, StrLen);
dst.WriteByte(0x00);
writePadding(dst, padByte, PaddingLen);
}
internal virtual void writeString(Stream dst, string str, int len=-1)
{
if(len < 0)
{
len = str.Length;
}
byte[] StrBytes = Encoding.UTF8.GetBytes(str);
dst.Write(StrBytes, 0x00, len);
}
internal virtual void writePadding(Stream dst, byte paddingByte, long paddingLen)
{
byte[] paddingData = new byte[paddingLen];
memset(paddingData, paddingByte, paddingLen);
dst.Write(paddingData, 0x00, paddingData.Length);
}
internal virtual byte[] getHeader(SceIoStat Stat, string ParentPath, string PathRel)
{
using (MemoryStream Header = new MemoryStream())
{
writeInt64(Header, DateTime.UtcNow.Ticks); // SysTime
writeInt64(Header, 0); // Flags
writeSceIoStat(Header, Stat);
writeStringWithPadding(Header, ParentPath, 256); // Parent Path
writeUInt32(Header, 1); //unk_16C
writeStringWithPadding(Header, PathRel, 256); //Relative Path
writePadding(Header, 0x78, 904); //'x'
writeString(Header, PSVIMGConstants.PSVIMG_HEADER_END); //EndOfHeader
Header.Seek(0x00, SeekOrigin.Begin);
return Header.ToArray();
}
}
internal virtual byte[] getHeader(string FilePath, string ParentPath, string PathRel)
{
using (MemoryStream Header = new MemoryStream())
{
writeInt64(Header, DateTime.UtcNow.Ticks); // SysTime
writeInt64(Header, 0); // Flags
writeSceIoStat(Header, sceIoStat(FilePath));
writeStringWithPadding(Header, ParentPath, 256); // Parent Path
writeUInt32(Header, 1); //unk_16C
writeStringWithPadding(Header, PathRel, 256); //Relative Path
writePadding(Header, 0x78, 904); //'x'
writeString(Header, PSVIMGConstants.PSVIMG_HEADER_END); //EndOfHeader
Header.Seek(0x00, SeekOrigin.Begin);
return Header.ToArray();
}
}
internal virtual void startNewBlock()
{
blockData = new byte[PSVIMGConstants.FULL_PSVIMG_SIZE];
blockStream = new MemoryStream(blockData, 0x00, PSVIMGConstants.FULL_PSVIMG_SIZE);
}
internal virtual byte[] shaBlock(int length = PSVIMGConstants.PSVIMG_BLOCK_SIZE,bool final=false)
{
byte[] outbytes = new byte[PSVIMGConstants.SHA256_BLOCK_SIZE];
shaCtx.BlockUpdate(blockData, 0x00, length);
Sha256Digest shaTmp = (Sha256Digest)shaCtx.Copy();
shaTmp.DoFinal(outbytes,0x00);
return outbytes;
}
internal virtual void finishBlock(bool final = false)
{
int len = Convert.ToInt32(blockStream.Position);
byte[] shaBytes = shaBlock(len, final);
blockStream.Write(shaBytes, 0x00, PSVIMGConstants.SHA256_BLOCK_SIZE);
len += PSVIMGConstants.SHA256_BLOCK_SIZE;
//Get next IV
byte[] encryptedBlock = aes_cbc_encrypt(blockData, IV, KEY, len);
for (int i = 0; i < IV.Length; i++)
{
int encBlockOffset = (encryptedBlock.Length - IV.Length)+i;
IV[i] = encryptedBlock[encBlockOffset];
}
mainStream.Write(encryptedBlock, 0x00, encryptedBlock.Length);
totalBytes += encryptedBlock.Length;
blockStream.Dispose();
}
internal virtual int remainingBlockSize()
{
return Convert.ToInt32((PSVIMGConstants.PSVIMG_BLOCK_SIZE - blockStream.Position));
}
internal virtual void writeBlock(byte[] data, bool update=false)
{
long dLen = data.Length;
long writeTotal = 0;
while (dLen > 0)
{
int remaining = remainingBlockSize();
if (dLen > remaining)
{
byte[] dataRemains = new byte[remaining];
Array.Copy(data, writeTotal, dataRemains, 0, remaining);
blockStream.Write(dataRemains, 0x00, remaining);
writeTotal += remaining;
dLen -= remaining;
finishBlock();
startNewBlock();
if (update)
blocksWritten += 1;
}
else
{
byte[] dataRemains = new byte[dLen];
Array.Copy(data, writeTotal, dataRemains, 0, dLen);
blockStream.Write(dataRemains, 0x00, Convert.ToInt32(dLen));
writeTotal += dLen;
dLen -= dLen;
}
}
}
internal virtual byte[] getPadding(long size)
{
using (MemoryStream ms = new MemoryStream())
{
long paddingSize = PSVIMGPadding.GetPadding(size);
if(paddingSize != 0)
{
writePadding(ms, 0x2B, paddingSize-PSVIMGConstants.PSVIMG_PADDING_END.Length);
writeString(ms, PSVIMGConstants.PSVIMG_PADDING_END);
}
ms.Seek(0x00, SeekOrigin.Begin);
return ms.ToArray();
}
}
internal virtual byte[] getTailer()
{
using (MemoryStream ms = new MemoryStream())
{
writeUInt64(ms, 0x00);
writePadding(ms, 0x7a, 1004);
writeString(ms, PSVIMGConstants.PSVIMG_TAILOR_END);
ms.Seek(0x00, SeekOrigin.Begin);
return ms.ToArray();
}
}
internal virtual void writeStream(Stream dst)
{
while(dst.Position < dst.Length)
{
byte[] work_buf;
Int64 bytes_remain = (dst.Length - dst.Position);
if (bytes_remain > BUFFER_SZ)
work_buf = new byte[BUFFER_SZ];
else
work_buf = new byte[bytes_remain];
dst.Read(work_buf, 0x00, work_buf.Length);
writeBlock(work_buf, true);
updateProgress(Convert.ToInt32(dst.Position), Convert.ToInt32(dst.Length), "PSVIMG Creation");
}
}
private byte[] getFooter()
{
using (MemoryStream ms = new MemoryStream())
{
totalBytes += 0x10; //number of bytes used by this footer.
writeInt32(ms, 0x00); // int padding (idk wht this is)
writeUInt32(ms, 0x00);
writeInt64(ms, totalBytes);
ms.Seek(0x00, SeekOrigin.Begin);
return aes_cbc_encrypt(ms.ToArray(), IV, KEY);
}
}
public void AddFile(Stream sData, string ParentPath, string PathRel)
{
SceIoStat stat = sceIoStat(sData);
long sz = Convert.ToInt64(stat.Size);
writeBlock(getHeader(stat, ParentPath, PathRel));
writeStream(sData);
writeBlock(getPadding(sz));
writeBlock(getTailer());
contentSize += sz;
finished = true;
}
public void AddFile(string FilePath, string ParentPath, string PathRel)
{
long sz = Convert.ToInt64(sceIoStat(FilePath).Size);
writeBlock(getHeader(FilePath, ParentPath, PathRel));
using (FileStream fs = File.OpenRead(FilePath))
writeStream(fs);
writeBlock(getPadding(sz));
writeBlock(getTailer());
contentSize += sz;
finished = true;
}
public void AddDir(string DirPath, string ParentPath, string PathRel)
{
writeBlock(getHeader(DirPath, ParentPath, PathRel));
writeBlock(getPadding(0));
writeBlock(getTailer());
}
public long Finish()
{
finishBlock(true);
byte[] footer = getFooter();
mainStream.Write(footer, 0x00, footer.Length);
blockStream.Dispose();
mainStream.Dispose();
return contentSize;
}
public PSVIMGBuilder(Stream dst, byte[] Key)
{
totalBytes = 0;
contentSize = 0;
shaCtx = new Sha256Digest();
mainStream = dst;
KEY = Key;
rnd.NextBytes(IV);
IV = aes_ecb_encrypt(IV, Key);
mainStream.Write(IV, 0x00, IV.Length);
totalBytes += IV.Length;
startNewBlock();
}
}
}

View File

@ -0,0 +1,314 @@
using System;
using System.IO;
using static Vita.PsvImgTools.SceIoStat;
namespace Vita.PsvImgTools
{
class PSVIMGFileStream : Stream
{
long length = 0;
long startPos = 0;
long endPos = 0;
long position = 0;
PSVIMGStream psvStream;
public PSVIMGFileStream(PSVIMGStream psv, string Path)
{
psvStream = psv;
findFile(Path);
}
public void WriteToFile(string FilePath)
{
using (FileStream fs = File.OpenWrite(FilePath))
{
fs.SetLength(0);
int written = 0;
long left = length - written;
byte[] work_buf;
Seek(0x00, SeekOrigin.Begin);
while (left > 0)
{
left = length - written;
if (left < 0x10000)
{
work_buf = new byte[left];
}
else
{
work_buf = new byte[0x10000];
}
Read(work_buf, 0x00, work_buf.Length);
fs.Write(work_buf, 0x00, work_buf.Length);
written += work_buf.Length;
}
}
}
public override bool CanRead
{
get
{
return true;
}
}
public override bool CanSeek
{
get
{
return true;
}
}
public override bool CanWrite
{
get
{
return false;
}
}
public override long Length
{
get
{
return length;
}
}
public override long Position
{
get
{
return position;
}
set
{
Seek(value, SeekOrigin.Begin);
}
}
public override void Flush()
{
psvStream.Flush();
}
private int _read(byte[] buffer, int offset, int count)
{
using (MemoryStream ms = new MemoryStream())
{
int read = 0;
while (true)
{
int remainBlock = Convert.ToInt32(psvStream.BlockRemaining - PSVIMGConstants.SHA256_BLOCK_SIZE);
int remainRead = count - read;
if (remainRead < remainBlock)
{
byte[] tmp = new byte[remainRead];
psvStream.Read(tmp, 0x00, remainRead);
ms.Write(tmp, 0x00, tmp.Length);
read += remainRead;
break;
}
else
{
byte[] tmp = new byte[remainBlock];
psvStream.Read(tmp, 0x00, remainBlock);
ms.Write(tmp, 0x00, tmp.Length);
psvStream.Seek(PSVIMGConstants.SHA256_BLOCK_SIZE, SeekOrigin.Current);
read += Convert.ToInt32(remainBlock);
}
}
ms.Seek(0x00, SeekOrigin.Begin);
return ms.Read(buffer, offset, count);
}
}
private long _seek(long amount, SeekOrigin orig)
{
long ToSeek = 0;
long SeekAdd = 0;
long remainBlock = psvStream.BlockRemaining - PSVIMGConstants.SHA256_BLOCK_SIZE;
while (true)
{
long remainSeek = amount - ToSeek;
if (remainSeek < remainBlock)
{
ToSeek += remainSeek;
break;
}
else
{
ToSeek += remainBlock;
SeekAdd += PSVIMGConstants.SHA256_BLOCK_SIZE;
}
remainBlock = PSVIMGConstants.PSVIMG_BLOCK_SIZE;
}
ToSeek += SeekAdd;
return psvStream.Seek(ToSeek, orig);
}
public override int Read(byte[] buffer, int offset, int count)
{
if (startPos + count > endPos)
{
count = Convert.ToInt32(endPos);
}
return _read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
if (origin == SeekOrigin.Begin)
{
if (offset <= endPos)
{
psvStream.Seek(startPos, SeekOrigin.Begin);
_seek(offset, SeekOrigin.Current);
position = offset;
return position;
}
else
{
throw new IndexOutOfRangeException("Offset is out of range of file");
}
}
else if (origin == SeekOrigin.Current)
{
if (offset <= endPos)
{
_seek(offset, SeekOrigin.Current);
position += offset;
return position;
}
else
{
throw new IndexOutOfRangeException("Offset is out of range of file");
}
}
else
{
long realOffset = endPos + offset;
if (realOffset <= endPos)
{
_seek(realOffset, SeekOrigin.Begin);
return Position;
}
else
{
throw new IndexOutOfRangeException("Offset is out of range of file");
}
}
}
public override void SetLength(long value)
{
throw new NotImplementedException("PSVFileStream is Read-Only");
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException("PSVFileStream is Read-Only");
}
private ushort readUInt16()
{
byte[] intBuf = new byte[0x2];
_read(intBuf, 0x00, 0x02);
return BitConverter.ToUInt16(intBuf, 0);
}
private uint readUInt32()
{
byte[] intBuf = new byte[0x4];
_read(intBuf, 0x00, 0x04);
return BitConverter.ToUInt32(intBuf, 0);
}
private ulong readUInt64()
{
byte[] intBuf = new byte[0x8];
_read(intBuf, 0x00, 0x08);
return BitConverter.ToUInt64(intBuf, 0);
}
private SceDateTime readDatetime()
{
SceDateTime dateTime = new SceDateTime();
dateTime.Year = readUInt16();
dateTime.Month = readUInt16();
dateTime.Day = readUInt16();
dateTime.Hour = readUInt16();
dateTime.Minute = readUInt16();
dateTime.Second = readUInt16();
dateTime.Microsecond = readUInt32();
return dateTime;
}
private SceIoStat readStats()
{
SceIoStat stat = new SceIoStat();
stat.Mode = (Modes)readUInt32();
stat.Attributes = (AttributesEnum)readUInt32();
stat.Size = readUInt64();
stat.CreationTime = readDatetime();
stat.AccessTime = readDatetime();
stat.ModificaionTime = readDatetime();
for (int i = 0; i < stat.Private.Length; i++)
{
stat.Private[i] = readUInt32();
}
return stat;
}
private PsvImgHeader readHeader()
{
PsvImgHeader header = new PsvImgHeader();
header.SysTime = readUInt64();
header.Flags = readUInt64();
header.Statistics = readStats();
_read(header.bParentPath, 0x00, 256);
header.unk_16C = readUInt32();
_read(header.bPath, 0x00, 256);
_read(header.Padding, 0x00, 904);
_read(header.bEnd, 0x00, 12);
return header;
}
private PsvImgTailer readTailer()
{
PsvImgTailer tailer = new PsvImgTailer();
tailer.Flags = readUInt64();
_read(tailer.Padding, 0x00, 1004);
_read(tailer.bEnd, 0x00, 12);
return tailer;
}
private void findFile(string path)
{
_seek(0x00, SeekOrigin.Begin);
while (psvStream.Position < psvStream.Length)
{
PsvImgHeader header = readHeader();
long size = (long)header.Statistics.Size;
long padding = PSVIMGPadding.GetPadding(size);
if (header.Path == path)
{
length = size;
startPos = psvStream.Position;
endPos = startPos + length;
return;
}
else
{
_seek(size + padding, SeekOrigin.Current);
PsvImgTailer tailer = readTailer();
}
}
throw new FileNotFoundException("Cannot find file specified");
}
}
}

View File

@ -0,0 +1,367 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace Vita.PsvImgTools
{
class PSVIMGStream : Stream
{
private Stream baseStream;
private MemoryStream blockStream;
private byte[] key;
public Stream BaseStream
{
get
{
return baseStream;
}
}
public byte[] Key
{
get
{
return key;
}
set
{
key = value;
}
}
public long BlockNo
{
get
{
return getBlockIndex();
}
set
{
seekToBlock(value);
update();
}
}
public long BlockRemaining
{
get
{
return getRemainingBlock();
}
}
public long BlockPosition
{
get
{
return blockStream.Position;
}
set
{
blockStream.Seek(value, SeekOrigin.Begin);
}
}
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanRead
{
get
{
return true;
}
}
public override long Length
{
get
{
return baseStream.Length - PSVIMGConstants.AES_BLOCK_SIZE;
}
}
public override bool CanSeek
{
get
{
return true;
}
}
public override long Position
{
get
{
return baseStream.Position - PSVIMGConstants.AES_BLOCK_SIZE;
}
set
{
Seek(value, SeekOrigin.Begin);
}
}
public PSVIMGStream(Stream file, byte[] KEY)
{
baseStream = file;
key = KEY;
if (!verifyFooter())
{
throw new Exception("Invalid KEY!");
}
blockStream = new MemoryStream();
baseStream.Seek(PSVIMGConstants.AES_BLOCK_SIZE, SeekOrigin.Begin);
update();
}
public override int Read(byte[] buffer, int offset, int count)
{
int remaining = (int)getRemainingBlock();
int read = 0;
if (count < remaining)
{
read += blockStream.Read(buffer, offset, count);
baseStream.Seek(count, SeekOrigin.Current);
}
else
{
using (MemoryStream ms = new MemoryStream())
{
while (true)
{
update();
remaining = (int)getRemainingBlock();
int curPos = count - read;
if (curPos > remaining)
{
read += remaining;
blockStream.CopyTo(ms, remaining);
baseStream.Seek(remaining, SeekOrigin.Current);
}
else
{
read += curPos;
blockStream.CopyTo(ms, curPos);
baseStream.Seek(curPos, SeekOrigin.Current);
break;
}
}
ms.Seek(0x00, SeekOrigin.Begin);
ms.Read(buffer, offset, count);
}
}
return read;
}
public override void Flush()
{
update();
baseStream.Flush();
blockStream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
long ret = 0;
if (origin == SeekOrigin.Begin)
{
ret = baseStream.Seek(offset + PSVIMGConstants.AES_BLOCK_SIZE, SeekOrigin.Begin);
}
else if (origin == SeekOrigin.Current)
{
long pos = baseStream.Position;
if (pos + offset >= PSVIMGConstants.AES_BLOCK_SIZE)
{
ret = baseStream.Seek(offset, SeekOrigin.Current);
}
else
{
ret = baseStream.Seek(offset + PSVIMGConstants.AES_BLOCK_SIZE, SeekOrigin.Current);
}
}
else if (origin == SeekOrigin.End)
{
long pos = baseStream.Length;
if (pos + offset >= PSVIMGConstants.AES_BLOCK_SIZE)
{
ret = baseStream.Seek(offset, SeekOrigin.End);
}
else
{
ret = baseStream.Seek(offset + PSVIMGConstants.AES_BLOCK_SIZE, SeekOrigin.End);
}
}
update();
return ret;
}
public override void SetLength(long value)
{
throw new NotImplementedException("PSVIMGStream is Read-Only");
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException("PSVIMGStream is Read-Only");
}
public override void Close()
{
blockStream.Close();
blockStream.Dispose();
baseStream.Close();
baseStream.Dispose();
Dispose();
}
private void update()
{
long offset = Position % PSVIMGConstants.FULL_PSVIMG_SIZE;
long blockIndex = getBlockIndex();
byte[] decryptedBlock = getBlock(blockIndex);
blockStream.Seek(0x00, SeekOrigin.Begin);
blockStream.SetLength(decryptedBlock.Length);
blockStream.Write(decryptedBlock, 0x00, decryptedBlock.Length);
seekToBlock(blockIndex);
baseStream.Seek(offset, SeekOrigin.Current);
blockStream.Seek(offset, SeekOrigin.Begin);
}
private long getBlockIndex()
{
long i = 0;
long curPos = baseStream.Position;
long fullBlock;
long blockOffset;
while (true)
{
blockOffset = i * PSVIMGConstants.FULL_PSVIMG_SIZE + PSVIMGConstants.AES_BLOCK_SIZE;
long remaining = getRemainingBase();
if (remaining < PSVIMGConstants.FULL_PSVIMG_SIZE)
{
fullBlock = blockOffset + remaining;
}
else
{
fullBlock = blockOffset + PSVIMGConstants.FULL_PSVIMG_SIZE;
}
if (curPos >= blockOffset && curPos < fullBlock)
{
break;
}
if (blockOffset > baseStream.Length)
{
break;
}
i++;
}
return i;
}
private long getRemainingBase()
{
return baseStream.Length - baseStream.Position;
}
private long getRemainingBlock()
{
return blockStream.Length - blockStream.Position;
}
private byte[] getIV(long blockindex)
{
byte[] iv = new byte[0x10];
seekToBlock(blockindex);
baseStream.Seek(baseStream.Position - PSVIMGConstants.AES_BLOCK_SIZE, SeekOrigin.Begin);
baseStream.Read(iv, 0x00, iv.Length);
return iv;
}
private byte[] aes_cbc_decrypt(byte[] cipherData, byte[] IV)
{
MemoryStream ms = new MemoryStream();
Aes alg = Aes.Create();
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.None;
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = key;
alg.IV = IV;
CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
byte[] decryptedData = ms.ToArray();
return decryptedData;
}
private void seekToBlock(long blockIndex)
{
long blockOffset;
blockOffset = blockIndex * PSVIMGConstants.FULL_PSVIMG_SIZE + PSVIMGConstants.AES_BLOCK_SIZE;
if (blockOffset > baseStream.Length)
{
blockOffset = baseStream.Length;
}
baseStream.Seek(blockOffset, SeekOrigin.Begin);
}
private bool verifyFooter()
{
byte[] Footer = new byte[0x10];
byte[] IV = new byte[PSVIMGConstants.AES_BLOCK_SIZE];
baseStream.Seek(baseStream.Length - (Footer.Length + IV.Length), SeekOrigin.Begin);
baseStream.Read(IV, 0x00, PSVIMGConstants.AES_BLOCK_SIZE);
baseStream.Read(Footer, 0x00, 0x10);
byte[] FooterDec = aes_cbc_decrypt(Footer, IV);
ulong FooterLen;
using (MemoryStream ms = new MemoryStream(FooterDec))
{
ms.Seek(0x4, SeekOrigin.Current);
ms.Seek(0x4, SeekOrigin.Current);
byte[] LenInt = new byte[0x8];
ms.Read(LenInt, 0x00, 0x8);
FooterLen = BitConverter.ToUInt64(LenInt, 0x00);
}
if (Convert.ToUInt64(baseStream.Length) == FooterLen)
{
return true;
}
else
{
return false;
}
}
private byte[] getBlock(long blockIndex)
{
byte[] iv = getIV(blockIndex);
long remaining = getRemainingBase();
byte[] encryptedBlock;
if (PSVIMGConstants.FULL_PSVIMG_SIZE < remaining)
{
encryptedBlock = new byte[PSVIMGConstants.FULL_PSVIMG_SIZE];
}
else
{
encryptedBlock = new byte[remaining];
}
baseStream.Read(encryptedBlock, 0x00, encryptedBlock.Length);
byte[] decryptedBlock = aes_cbc_decrypt(encryptedBlock, iv);
return decryptedBlock;
}
}
}

View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace PsvImage
namespace Vita.PsvImgTools
{
internal class PSVIMGConstants
{
public const int AES_BLOCK_SIZE = 0x10;
@ -19,8 +17,27 @@ namespace PsvImage
public const int FULL_PSVIMG_SIZE = PSVIMG_BLOCK_SIZE + SHA256_BLOCK_SIZE;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SceDateTime
internal class StringReader
{
internal static string ReadUntilTerminator(byte[] StringBytes)
{
string str = "";
foreach (byte sByte in StringBytes)
{
if (sByte != 0x00)
{
str += (char)sByte;
}
else
{
return str;
}
}
return str;
}
}
internal class SceDateTime
{
public ushort Year;
public ushort Month;
@ -29,13 +46,15 @@ namespace PsvImage
public ushort Minute;
public ushort Second;
public uint Microsecond;
public SceDateTime()
{
}
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct SceIoStat
internal class SceIoStat
{
[Flags]
public enum Modes
{
/** Format bits mask */
@ -103,7 +122,7 @@ namespace PsvImage
public Modes Mode;
public AttributesEnum Attributes;
/** Size of the file in bytes. */
public UInt64 Size;
public ulong Size;
/** Creation time. */
public SceDateTime CreationTime;
/** Access time. */
@ -111,26 +130,38 @@ namespace PsvImage
/** Modification time. */
public SceDateTime ModificaionTime;
/** Device-specific data. */
public fixed uint Private[6];
}
public uint[] Private = new uint[6];
public SceIoStat()
{
for (int i = 0; i < Private.Length; i++)
{
Private[i] = 0;
}
}
};
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PsvImgTailer
internal class PsvImgTailer
{
public ulong Flags;
public fixed byte Padding[1004];
public fixed byte bEnd[12];
}
public byte[] Padding = new byte[1004];
public byte[] bEnd = new byte[12];
public string End
{
get
{
return StringReader.ReadUntilTerminator(bEnd);
}
}
}
internal class PSVIMGPadding
{
public static long GetPadding(long size)
{
long padding;
if ((size & (PSVIMGConstants.PSVIMG_ENTRY_ALIGN - 1)) >= 1)
if ((size & PSVIMGConstants.PSVIMG_ENTRY_ALIGN - 1) >= 1)
{
padding = (PSVIMGConstants.PSVIMG_ENTRY_ALIGN - (size & (PSVIMGConstants.PSVIMG_ENTRY_ALIGN - 1)));
padding = PSVIMGConstants.PSVIMG_ENTRY_ALIGN - (size & PSVIMGConstants.PSVIMG_ENTRY_ALIGN - 1);
}
else
{
@ -140,15 +171,43 @@ namespace PsvImage
}
}
internal unsafe struct PsvImgHeader
internal class PsvImgHeader
{
public ulong SysTime;
public ulong Flags;
public SceIoStat Statistics;
public fixed byte bParentPath[256];
public uint unk_16C;
public fixed byte bPath[256];
public fixed byte Padding[904];
public fixed byte bEnd[12];
public byte[] bParentPath = new byte[256];
public uint unk_16C; // set to 1
public byte[] bPath = new byte[256];
public byte[] Padding = new byte[904];
public byte[] bEnd = new byte[12];
public string Path
{
get
{
return StringReader.ReadUntilTerminator(bPath);
}
}
public string End
{
get
{
return StringReader.ReadUntilTerminator(bEnd);
}
}
public string ParentPath
{
get
{
return StringReader.ReadUntilTerminator(bParentPath);
}
}
public PsvImgHeader()
{
}
}
}

View File

@ -0,0 +1,208 @@
using Ionic.Zlib;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Vita.PsvImgTools
{
class PSVMDBuilder
{
private static void memset(byte[] buf, byte content, long length)
{
for (int i = 0; i < length; i++)
{
buf[i] = content;
}
}
private static void writeUInt64(Stream dst, ulong value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x8);
}
private static void writeInt64(Stream dst, long value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x8);
}
private static void writeUInt16(Stream dst, ushort value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x2);
}
private static void writeInt16(Stream dst, short value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x2);
}
private static void writeInt32(Stream dst, int value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x4);
}
private static void writeUInt32(Stream dst, uint value)
{
byte[] ValueBytes = BitConverter.GetBytes(value);
dst.Write(ValueBytes, 0x00, 0x4);
}
private static void writeString(Stream dst, string str, int len = -1)
{
if (len < 0)
{
len = str.Length;
}
byte[] StrBytes = Encoding.UTF8.GetBytes(str);
dst.Write(StrBytes, 0x00, len);
}
private static void writePadding(Stream dst, byte paddingByte, long paddingLen)
{
byte[] paddingData = new byte[paddingLen];
memset(paddingData, paddingByte, paddingLen);
dst.Write(paddingData, 0x00, paddingData.Length);
}
private static void writeStringWithPadding(Stream dst, string str, int padSize, byte padByte = 0x78)
{
int StrLen = str.Length;
if (StrLen > padSize)
{
StrLen = padSize;
}
int PaddingLen = padSize - StrLen - 1;
writeString(dst, str, StrLen);
dst.WriteByte(0x00);
writePadding(dst, padByte, PaddingLen);
}
private static byte[] aes_ecb_decrypt(byte[] cipherText, byte[] KEY, int size = -1)
{
if (size < 0)
{
size = cipherText.Length;
}
MemoryStream ms = new MemoryStream();
Aes alg = Aes.Create();
alg.Mode = CipherMode.ECB;
alg.Padding = PaddingMode.None;
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = KEY;
CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherText, 0, size);
cs.Close();
byte[] plainText = ms.ToArray();
return plainText;
}
private static byte[] aes_cbc_decrypt(byte[] cipherData, byte[] IV, byte[] Key)
{
MemoryStream ms = new MemoryStream();
Aes alg = Aes.Create();
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.None;
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = Key;
alg.IV = IV;
CryptoStream cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
byte[] decryptedData = ms.ToArray();
return decryptedData;
}
private static byte[] aes_cbc_encrypt(byte[] plainText, byte[] IV, byte[] KEY, int size = -1)
{
if (size < 0)
{
size = plainText.Length;
}
MemoryStream ms = new MemoryStream();
Aes alg = Aes.Create();
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.None;
alg.KeySize = 256;
alg.BlockSize = 128;
alg.Key = KEY;
alg.IV = IV;
CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(plainText, 0, size);
cs.Close();
byte[] cipherText = ms.ToArray();
return cipherText;
}
public static void CreatePsvmd(Stream OutputStream, Stream EncryptedPsvimg, long ContentSize, string BackupType, byte[] Key)
{
byte[] IV = new byte[PSVIMGConstants.AES_BLOCK_SIZE];
EncryptedPsvimg.Seek(0x00, SeekOrigin.Begin);
EncryptedPsvimg.Read(IV, 0x00, IV.Length);
IV = aes_ecb_decrypt(IV, Key);
MemoryStream ms = new MemoryStream();
writeUInt32(ms, 0xFEE1900D); // magic
writeUInt32(ms, 0x2); // type
writeUInt64(ms, 0x03000000); // fw ver
ms.Write(new byte[0x10], 0x00, 0x10); // PSID
writeStringWithPadding(ms, BackupType, 0x40, 0x00); //backup type
writeInt64(ms, EncryptedPsvimg.Length); // total size
writeUInt64(ms, 0x2); //version
writeInt64(ms, ContentSize); // content size
ms.Write(IV, 0x00, 0x10); // IV
writeUInt64(ms, 0x00); //ux0 info
writeUInt64(ms, 0x00); //ur0 info
writeUInt64(ms, 0x00); //unused 98
writeUInt64(ms, 0x00); //unused A0
writeUInt32(ms, 0x1); //add data
ms.Seek(0x00, SeekOrigin.Begin);
byte[] psvMd = ms.ToArray();
ms.Close();
ms = new MemoryStream();
byte[] psvMdCompressed = ZlibStream.CompressBuffer(psvMd);
psvMdCompressed[0x1] = 0x9C;
ms.Write(psvMdCompressed, 0x00, psvMdCompressed.Length);
SHA256 sha = SHA256.Create();
byte[] shadata = sha.ComputeHash(psvMdCompressed);
ms.Write(shadata, 0x00, shadata.Length);
int PaddingLen = Convert.ToInt32(PSVIMGConstants.AES_BLOCK_SIZE - (ms.Length & PSVIMGConstants.AES_BLOCK_SIZE - 1));
writePadding(ms, 0x00, PaddingLen);
writeInt32(ms, PaddingLen); //Padding Length
writeUInt32(ms, 0x00);
writeInt64(ms, ms.Length + 0x8 + IV.Length);
ms.Seek(0x00, SeekOrigin.Begin);
byte[] toEncrypt = ms.ToArray();
ms.Close();
byte[] EncryptedData = aes_cbc_encrypt(toEncrypt, IV, Key);
OutputStream.Write(IV, 0x00, IV.Length);
OutputStream.Write(EncryptedData, 0x00, EncryptedData.Length);
return;
}
public static byte[] DecryptPsvmd(Stream PsvMdFile, byte[] Key)
{
byte[] IV = new byte[PSVIMGConstants.AES_BLOCK_SIZE];
PsvMdFile.Read(IV, 0x00, IV.Length);
byte[] remaining = new byte[PsvMdFile.Length - IV.Length];
PsvMdFile.Read(remaining, 0x00, remaining.Length);
byte[] zlibCompressed = aes_cbc_decrypt(remaining, IV, Key);
return zlibCompressed;
// return ZlibStream.UncompressBuffer(zlibCompressed);
}
}
}

18
PsvImgTools/Vita.csproj Normal file
View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0" />
<PackageReference Include="DotNetZip" Version="1.16.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LiGeneralUtilities\LiLib.csproj" />
</ItemGroup>
</Project>