chovy-trans/CHOVY-TRANSFER/PSVIMGBuilder.cs

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