489 lines
16 KiB
C#
489 lines
16 KiB
C#
using Org.BouncyCastle.Crypto.Digests;
|
|
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using static PSVIMGTOOLS.SceIoStat;
|
|
|
|
namespace PSVIMGTOOLS
|
|
{
|
|
class PSVIMGBuilder
|
|
{
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
}
|
|
|
|
private void memset(byte[] buf, byte content, long length)
|
|
{
|
|
for(int i = 0; i < length; i++)
|
|
{
|
|
buf[i] = content;
|
|
}
|
|
}
|
|
|
|
private 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 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 void writePadding(Stream dst, byte paddingByte, long paddingLen)
|
|
{
|
|
byte[] paddingData = new byte[paddingLen];
|
|
memset(paddingData, paddingByte, paddingLen);
|
|
dst.Write(paddingData, 0x00, paddingData.Length);
|
|
}
|
|
private 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();
|
|
}
|
|
}
|
|
|
|
private void startNewBlock()
|
|
{
|
|
blockData = new byte[PSVIMGConstants.FULL_PSVIMG_SIZE];
|
|
blockStream = new MemoryStream(blockData, 0x00, PSVIMGConstants.FULL_PSVIMG_SIZE);
|
|
}
|
|
|
|
|
|
private 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;
|
|
}
|
|
|
|
private 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();
|
|
}
|
|
|
|
private int remainingBlockSize()
|
|
{
|
|
return Convert.ToInt32((PSVIMGConstants.PSVIMG_BLOCK_SIZE - blockStream.Position));
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
}
|
|
}
|
|
private 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();
|
|
}
|
|
}
|
|
|
|
private 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();
|
|
}
|
|
}
|
|
|
|
private void writeStream(Stream dst)
|
|
{
|
|
while(dst.Position < dst.Length)
|
|
{
|
|
byte[] work_buf;
|
|
Int64 bytes_remain = (dst.Length - dst.Position);
|
|
if (bytes_remain > 0x33554432)
|
|
{
|
|
work_buf = new byte[0x33554432];
|
|
}
|
|
else
|
|
{
|
|
work_buf = new byte[bytes_remain];
|
|
}
|
|
dst.Read(work_buf, 0x00, work_buf.Length);
|
|
writeBlock(work_buf, true);
|
|
}
|
|
}
|
|
|
|
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 AddFileAsync(string FilePath, string ParentPath, string PathRel)
|
|
{
|
|
finished = false;
|
|
new Thread(() =>
|
|
{
|
|
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;
|
|
}).Start();
|
|
|
|
}
|
|
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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|