2019-08-05 08:43:00 +00:00
using System ;
using System.IO ;
using System.Linq ;
using System.Security.Cryptography ;
using System.Windows.Forms ;
namespace Conv2PSV
{
class Program
{
2024-04-12 06:41:14 +00:00
static void CopyTo ( Stream src , Stream dst )
{
byte [ ] buffer = new byte [ 0x8000 ] ;
int totalRead = 0 ;
do
{
totalRead = src . Read ( buffer , 0x00 , buffer . Length ) ;
dst . Write ( buffer , 0x00 , totalRead ) ;
} while ( totalRead > 0 ) ;
}
2019-08-05 08:43:00 +00:00
static string GetString ( Stream fs )
{
String str = "" ;
byte by = 0xFF ;
while ( true )
{
by = ( byte ) fs . ReadByte ( ) ;
if ( by = = 0x00 )
break ;
str + = ( char ) by ;
}
return str ;
2022-04-03 11:23:24 +00:00
}
2019-08-05 08:43:00 +00:00
static void WriteMagic ( Stream fs )
{
fs . WriteByte ( 0x00 ) ;
WriteString ( fs , "VSP" ) ;
}
static void WriteString ( Stream fs , String str )
{
char [ ] CharArray = str . ToArray ( ) ;
foreach ( char Chr in CharArray )
{
fs . WriteByte ( ( byte ) Chr ) ;
}
}
static String GetTitle ( String FileName )
{
2022-04-03 11:23:24 +00:00
if ( Path . GetExtension ( FileName ) . ToLower ( ) = = ".mcs" | | Path . GetExtension ( FileName ) . ToLower ( ) = = ".ps1" )
2019-08-05 08:43:00 +00:00
{
FileStream fs = File . OpenRead ( FileName ) ;
2019-08-05 11:22:59 +00:00
fs . Seek ( 0xA , SeekOrigin . Begin ) ;
String Name = GetString ( fs ) ;
2019-08-05 08:43:00 +00:00
fs . Close ( ) ;
2019-08-05 11:22:59 +00:00
return Name ;
}
2022-04-03 11:23:24 +00:00
else if ( Path . GetExtension ( FileName ) . ToLower ( ) = = ".mcb" | | Path . GetExtension ( FileName ) . ToLower ( ) = = ".psx" | | Path . GetExtension ( FileName ) . ToLower ( ) = = ".mcx" | | Path . GetExtension ( FileName ) . ToLower ( ) = = ".pda" )
2019-08-05 11:22:59 +00:00
{
FileStream fs = File . OpenRead ( FileName ) ;
2022-04-03 11:23:24 +00:00
fs . Seek ( 0x0 , SeekOrigin . Begin ) ;
2019-08-05 11:22:59 +00:00
String Name = GetString ( fs ) ;
fs . Close ( ) ;
return Name ;
2019-08-05 08:43:00 +00:00
}
else
{
2022-04-03 11:23:24 +00:00
return Path . GetFileNameWithoutExtension ( FileName ) ;
2019-08-05 08:43:00 +00:00
}
}
static byte [ ] SignatureGen ( Stream fs )
{
byte [ ] HMAC_KEY = { 0x02 , 0x62 , 0x8C , 0x13 , 0x94 , 0x71 , 0xF5 , 0x0B , 0xC8 , 0x94 , 0x4E , 0x6B , 0xF0 , 0xDC , 0xBC , 0x50 , 0x30 , 0x3F , 0x1F , 0x5B } ; // Key for this specific seed,
fs . Seek ( 0x00 , SeekOrigin . Begin ) ;
Console . WriteLine ( "Generating HMAC signature..." ) ;
HMACSHA1 hmac = new HMACSHA1 ( HMAC_KEY ) ;
byte [ ] Signature = hmac . ComputeHash ( fs ) ;
Console . WriteLine ( BitConverter . ToString ( Signature ) . Replace ( "-" , " " ) ) ;
return Signature ;
}
static String GetPsvName ( String SaveName )
{
String PsvName = "" ;
char [ ] SaveArray = SaveName . ToArray ( ) ;
2019-08-05 12:38:23 +00:00
for ( int i = 0 ; i < SaveArray . Length ; i + + )
2019-08-05 08:43:00 +00:00
{
2019-08-05 12:38:23 +00:00
if ( i < 0xC )
2019-08-05 08:43:00 +00:00
{
2019-08-05 12:38:23 +00:00
PsvName + = SaveArray [ i ] ;
2019-08-05 08:43:00 +00:00
}
else
{
2019-08-05 12:38:23 +00:00
PsvName + = String . Format ( "{0:X}" , Convert . ToInt32 ( SaveArray [ i ] ) ) ;
2019-08-05 08:43:00 +00:00
}
}
2019-08-05 12:38:23 +00:00
2019-08-05 08:43:00 +00:00
PsvName + = ".PSV" ;
return PsvName ;
}
2022-04-03 11:23:24 +00:00
static int GetScLen ( String FilePath )
{
if ( Path . GetExtension ( FilePath ) . ToLower ( ) = = ".mcs" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".ps1" )
{
FileStream MCS = File . OpenRead ( FilePath ) ;
int SCLen = Convert . ToInt32 ( MCS . Length - 0x80 ) ;
MCS . Close ( ) ;
return SCLen ;
}
else if ( Path . GetExtension ( FilePath ) . ToLower ( ) = = ".mcb" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".psx" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".mcx" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".pda" )
{
FileStream PSX = File . OpenRead ( FilePath ) ;
int SCLen = Convert . ToInt32 ( PSX . Length - 0x36 ) ;
PSX . Close ( ) ;
return SCLen ;
}
else
{
FileStream RAW = File . OpenRead ( FilePath ) ;
int SCLen = Convert . ToInt32 ( RAW . Length ) ;
RAW . Close ( ) ;
return SCLen ;
}
}
2019-08-05 11:22:59 +00:00
static void WriteSC ( String FilePath , Stream PSV )
2022-04-03 11:23:24 +00:00
{
2019-08-05 11:22:59 +00:00
Console . WriteLine ( "Writing SC Image" ) ;
2022-04-03 11:23:24 +00:00
if ( Path . GetExtension ( FilePath ) . ToLower ( ) = = ".mcs" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".ps1" )
2019-08-05 11:22:59 +00:00
{
FileStream MCS = File . OpenRead ( FilePath ) ;
MCS . Seek ( 0x80 , SeekOrigin . Begin ) ;
2024-04-12 06:41:14 +00:00
CopyTo ( MCS , PSV ) ;
2019-08-05 11:22:59 +00:00
MCS . Close ( ) ;
}
2022-04-03 11:23:24 +00:00
else if ( Path . GetExtension ( FilePath ) . ToLower ( ) = = ".mcb" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".psx" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".mcx" | | Path . GetExtension ( FilePath ) . ToLower ( ) = = ".pda" )
2019-08-05 11:22:59 +00:00
{
2022-04-03 11:23:24 +00:00
FileStream PSX = File . OpenRead ( FilePath ) ;
PSX . Seek ( 0x36 , SeekOrigin . Begin ) ;
2024-04-12 06:41:14 +00:00
CopyTo ( PSX , PSV ) ;
2022-04-03 11:23:24 +00:00
PSX . Close ( ) ;
2019-08-05 11:22:59 +00:00
}
else
2022-04-03 11:23:24 +00:00
{
FileStream RAW = File . OpenRead ( FilePath ) ;
2024-04-12 06:41:14 +00:00
CopyTo ( RAW , PSV ) ;
2022-04-03 11:23:24 +00:00
RAW . Close ( ) ;
2019-08-05 11:22:59 +00:00
}
}
2019-08-05 08:43:00 +00:00
[STAThread]
static void Main ( string [ ] args )
{
if ( args . Length = = 0 )
{
2019-08-05 11:22:59 +00:00
Console . WriteLine ( "Select Save" ) ;
2019-08-05 08:43:00 +00:00
OpenFileDialog ofd = new OpenFileDialog ( ) ;
ofd . InitialDirectory = Directory . GetCurrentDirectory ( ) ;
2022-04-03 11:23:24 +00:00
ofd . Filter = "PlayStation One Single Save Files (*.mcs, *.ps1, *.mcb, *.psx, *.mcx, *.mcb, *.mcx *.pda, *.raw)|*.mcs;*.ps1;*.mcb;*.psx;*.mcx;*.pda;*.raw|PSXGameEdit Single Save (*.mcs)|*.mcs|Memory Card Juggler Single Save (*.ps1)|*.ps1|Smart Link Single Save (*.mcb)|*.mcb|Action Replay, Caetla or Game Shark Single Save (*.psx)|*.psx|Datel Single Save (*.mcx, *.pda)|*.mcx;*.pda|RAW Single Save (*, *.raw)|*;*.raw" ;
2019-08-05 08:43:00 +00:00
DialogResult res = ofd . ShowDialog ( ) ;
if ( res = = DialogResult . OK )
{
args = new String [ ] { ofd . FileName } ;
}
}
if ( args . Length ! = 0 )
{
String FilePath = args [ 0 ] ;
String SaveTitle = GetTitle ( FilePath ) ;
Console . Write ( "PsvName = " ) ;
2024-04-12 06:41:14 +00:00
String PsvName = Path . Combine ( Path . Combine ( Path . Combine ( Path . GetDirectoryName ( FilePath ) , "PS3" ) , "EXPORT" ) , "PSV" ) ;
2019-08-05 11:22:59 +00:00
Directory . CreateDirectory ( PsvName ) ;
PsvName = Path . Combine ( PsvName , GetPsvName ( SaveTitle ) ) ;
Console . WriteLine ( Path . GetFileName ( PsvName ) ) ;
2019-08-05 08:43:00 +00:00
FileStream PSV = File . Open ( PsvName , FileMode . OpenOrCreate , FileAccess . ReadWrite ) ;
PSV . SetLength ( 0 ) ;
BinaryWriter BW = new BinaryWriter ( PSV ) ;
WriteMagic ( PSV ) ;
2022-04-03 11:23:24 +00:00
// Write reserved space.
BW . Write ( ( UInt32 ) 0 ) ;
BW . Write ( ( UInt64 ) 0x00 ) ;
2019-08-05 08:43:00 +00:00
2022-04-03 11:23:24 +00:00
// Am lazy and dont want to implement sonys broken AES-CBC
// Just do a seed i know the hmac key for
// ps3 wont care.
2019-08-05 08:43:00 +00:00
Byte [ ] StaticSeeed = { 0x42 , 0x6C , 0x65 , 0x73 , 0x73 , 0x65 , 0x64 , 0x20 , 0x42 , 0x65 , 0x20 , 0x7E , 0x20 , 0x57 , 0x69 , 0x63 , 0x63 , 0x61 , 0x6E , 0x73 } ;
2022-04-03 11:23:24 +00:00
//"Blessed Be ~ Wiccans" - Allways nice to add personality to your code where-ever possible.
2019-08-05 08:43:00 +00:00
PSV . Write ( StaticSeeed , 0x00 , 0x14 ) ;
2022-04-03 11:23:24 +00:00
int SCSize = GetScLen ( FilePath ) ;
Console . WriteLine ( "SC Image Size: " + SCSize . ToString ( ) ) ;
2019-08-05 08:43:00 +00:00
PSV . Write ( new Byte [ 0x14 ] , 0x00 , 0x14 ) ;
2022-04-03 11:23:24 +00:00
Console . WriteLine ( "Writing Header... " ) ;
BW . Write ( ( UInt32 ) 0x14 ) ; // 0x14 - PS1 Save, 0x2C PS2 Save.
BW . Write ( ( UInt32 ) 0x01 ) ; // 0x01 - PS1 Save, 0x02 PS2 Save.
BW . Write ( ( UInt32 ) SCSize ) ; // Fileisze
BW . Write ( ( UInt32 ) 0x84 ) ; // Unknown 2
BW . Write ( ( UInt32 ) 0x200 ) ; // Unknown 4
BW . Write ( ( UInt64 ) 0x00 ) ; // Reserved 2
BW . Write ( ( UInt64 ) 0x00 ) ; // Reserved 3
BW . Write ( ( UInt32 ) 0x2000 ) ; // Unknown 5
BW . Write ( ( UInt32 ) 0x9003 ) ; // Unknown 6
if ( SaveTitle . Length > 20 )
SaveTitle = SaveTitle . Substring ( 0 , 20 ) ;
2019-08-05 08:43:00 +00:00
int Padding = ( 0x20 - SaveTitle . Length ) ;
2022-04-03 11:23:24 +00:00
Console . WriteLine ( "Writing " + SaveTitle + " + 0x00*" + Padding . ToString ( ) ) ;
2019-08-05 08:43:00 +00:00
WriteString ( PSV , SaveTitle ) ;
BW . Write ( new Byte [ Padding ] , 0x00 , Padding ) ;
2022-04-03 11:23:24 +00:00
Console . WriteLine ( "Writing SC Image ..." ) ;
2019-08-05 11:22:59 +00:00
WriteSC ( FilePath , PSV ) ;
2019-08-05 08:43:00 +00:00
byte [ ] Signature = SignatureGen ( PSV ) ;
2022-04-03 11:23:24 +00:00
Console . WriteLine ( "Signing..." ) ;
2019-08-05 08:43:00 +00:00
PSV . Seek ( 0x1C , SeekOrigin . Begin ) ;
PSV . Write ( Signature , 0x00 , 0x14 ) ;
Console . WriteLine ( "Done!\n\nBlessed Be ~" ) ;
PSV . Close ( ) ;
2019-08-05 11:22:59 +00:00
2019-08-05 08:43:00 +00:00
}
}
}
}