2017-07-23 08:11:01 -04:00
// Define BINARY_SAVE before release so the player has
// to put some effort into cheating ;)
// During development, leave it undefined to use the
// easily modifiable JSON serialised format
//#define BINARY_SAVE
using System ;
2017-05-07 20:04:31 -04:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using Newtonsoft.Json ;
2017-07-04 17:12:25 -04:00
using System.Diagnostics ;
2017-07-21 17:14:23 -04:00
using System.Windows.Forms ;
2017-07-23 08:11:01 -04:00
using System.Runtime.CompilerServices ;
2017-07-27 00:25:37 -04:00
using System.Drawing ;
using System.Text.RegularExpressions ;
using System.Runtime.InteropServices ;
2017-05-07 20:04:31 -04:00
namespace TimeHACK.Engine
{
public static class SaveSystem
{
2017-06-10 07:22:50 -04:00
public static Save CurrentSave { get ; set ; }
2017-07-05 14:41:23 -04:00
public static bool DevMode = false ;
2017-07-21 17:14:23 -04:00
public static Form troubleshooter ;
2017-05-07 20:04:31 -04:00
2017-07-05 14:41:23 -04:00
public static Theme currentTheme { get ; set ; }
2017-07-23 08:11:01 -04:00
#if BINARY_SAVE
private static readonly byte [ ] magic = Encoding . UTF8 . GetBytes ( "THSv" ) ;
private static readonly IOrderedEnumerable < System . Reflection . PropertyInfo > properties = typeof ( Save ) . GetProperties ( ) . OrderBy ( p = > ( p . GetCustomAttributes ( typeof ( OrderAttribute ) , false ) . SingleOrDefault ( ) as OrderAttribute ) . Order ) ;
#endif
2017-05-07 20:04:31 -04:00
public static string GameDirectory
{
get
{
2017-05-20 06:49:25 -04:00
return Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) , "TimeHACK" ) ;
2017-05-07 20:04:31 -04:00
}
}
2017-07-22 15:35:30 -04:00
public static string DataDirectory
{
get
{
return Path . Combine ( GameDirectory , "Data" ) ;
}
}
2017-05-20 08:33:32 -04:00
public static string AllProfilesDirectory
2017-05-07 20:04:31 -04:00
{
get
{
return Path . Combine ( GameDirectory , "Profiles" ) ;
}
}
2017-05-20 08:33:32 -04:00
public static string ProfileName = "" ;
public static string ProfileFile = "main.save" ;
public static string ProfileDirectory
2017-05-07 20:04:31 -04:00
{
2017-05-20 08:33:32 -04:00
get
2017-05-07 20:04:31 -04:00
{
2017-05-20 08:33:32 -04:00
return Path . Combine ( GameDirectory , Path . Combine ( "Profiles" , ProfileName ) ) ;
2017-05-07 20:04:31 -04:00
}
}
2017-05-24 16:42:48 -04:00
public static string ProfileFileSystemDirectory
{
get
{
return Path . Combine ( ProfileDirectory , "folders" ) ;
}
}
public static string ProfileMyComputerDirectory
{
get
{
2017-07-22 06:41:27 -04:00
return Path . Combine ( ProfileFileSystemDirectory , "CDrive" ) ;
2017-05-24 16:42:48 -04:00
}
}
public static string ProfileSettingsDirectory
{
get
{
2017-08-01 10:28:47 -04:00
return Path . Combine ( ProfileMyComputerDirectory , "Documents and Settings" ) ;
2017-05-24 16:42:48 -04:00
}
}
public static string ProfileDocumentsDirectory
{
get
{
2017-08-01 10:28:47 -04:00
return Path . Combine ( ProfileMyComputerDirectory , "My Documents" ) ;
2017-05-24 16:42:48 -04:00
}
}
public static string ProfileProgramsDirectory
{
get
{
2017-08-01 10:28:47 -04:00
return Path . Combine ( ProfileMyComputerDirectory , "Program Files" ) ;
2017-05-24 16:42:48 -04:00
}
}
public static string ProfileWindowsDirectory
{
get
{
2017-08-01 10:28:47 -04:00
return Path . Combine ( ProfileMyComputerDirectory , "Windows" ) ;
2017-05-24 16:42:48 -04:00
}
}
2017-05-07 20:04:31 -04:00
public static void NewGame ( )
2017-07-22 05:17:11 -04:00
{
2017-05-24 16:42:48 -04:00
var save = new Save ( ) ;
save . ExperiencedStories = new List < string > ( ) ;
2017-07-04 17:12:25 -04:00
if ( DevMode = = true )
{
if ( ProfileName = = "98" )
{
save . CurrentOS = "98" ;
2017-07-05 14:41:23 -04:00
save . ThemeName = "default98" ;
2017-07-22 05:17:11 -04:00
currentTheme = new Default98Theme ( ) ;
2017-07-04 18:24:33 -04:00
}
else
{
save . CurrentOS = "95" ;
2017-07-05 14:41:23 -04:00
save . ThemeName = "default95" ;
2017-07-28 09:01:37 -04:00
save . BytesLeft = 536870912 ;
2017-07-05 14:41:23 -04:00
currentTheme = new Default95Theme ( ) ;
2017-07-04 17:12:25 -04:00
}
}
2017-07-04 18:24:33 -04:00
else
{
save . CurrentOS = "95" ;
2017-07-05 14:41:23 -04:00
save . ThemeName = "default95" ;
2017-07-28 09:01:37 -04:00
save . BytesLeft = 536870912 ;
2017-07-05 14:41:23 -04:00
currentTheme = new Default95Theme ( ) ;
2017-07-04 18:24:33 -04:00
}
2017-05-24 16:42:48 -04:00
CurrentSave = save ;
2017-07-01 17:17:57 -04:00
2017-06-10 07:22:50 -04:00
CheckFiles ( ) ;
2017-05-24 16:42:48 -04:00
SaveGame ( ) ;
}
public static void CheckFiles ( )
{
2017-07-28 09:01:37 -04:00
Directory . CreateDirectory ( GameDirectory ) ;
2017-07-30 00:10:22 -04:00
Directory . CreateDirectory ( DataDirectory ) ;
2017-07-28 09:01:37 -04:00
Directory . CreateDirectory ( AllProfilesDirectory ) ;
Directory . CreateDirectory ( ProfileDirectory ) ;
Directory . CreateDirectory ( ProfileFileSystemDirectory ) ;
2017-06-10 07:22:50 -04:00
2017-07-27 15:58:05 -04:00
SaveDirectoryInfo ( ProfileDirectory , "folders" , false , "My Computer" , false ) ;
2017-07-27 23:58:39 -04:00
SaveDirectoryInfo ( ProfileFileSystemDirectory , "CDrive" , false , "C:" , true ) ;
2017-08-01 10:28:47 -04:00
if ( CurrentSave . CurrentOS = = "95" | | CurrentSave . CurrentOS = = "98" ) SaveDirectoryInfo ( ProfileMyComputerDirectory , "My Documents" , false , "My Documents" , true ) ;
if ( CurrentSave . CurrentOS = = "2000" | | CurrentSave . CurrentOS = = "ME" ) SaveDirectoryInfo ( ProfileMyComputerDirectory , "Documents and Settings" , false , "Documents and Settings" , true ) ;
SaveDirectoryInfo ( ProfileMyComputerDirectory , "Program Files" , true , "Program Files" , true ) ;
2017-07-27 15:58:05 -04:00
SaveDirectoryInfo ( ProfileProgramsDirectory , "Accessories" , false , "Accessories" , true ) ;
SaveDirectoryInfo ( ProfileProgramsDirectory , "Internet Explorer" , true , "Internet Explorer" , true ) ;
SaveDirectoryInfo ( ProfileProgramsDirectory , "The Microsoft Network" , true , "The Microsoft Network" , true ) ;
2017-08-01 10:28:47 -04:00
SaveDirectoryInfo ( ProfileMyComputerDirectory , "Windows" , true , "Windows" , true ) ;
2017-07-01 17:17:57 -04:00
2017-08-05 08:29:30 -04:00
CreateWindowsFile ( Path . Combine ( ProfileProgramsDirectory , "Accessories" ) , "wordpad.exe" , "wordpad" , 16 , 183296 ) ;
CreateWindowsFile ( Path . Combine ( ProfileProgramsDirectory , "Internet Explorer" ) , "ie20.exe" , "ie" , 8 , 512 ) ;
CreateWindowsFile ( Path . Combine ( ProfileProgramsDirectory , "Internet Explorer" ) , "lnfinst.exe" , "iebrokeninstaller" , 8 , 512 ) ;
CreateWindowsFile ( Path . Combine ( ProfileProgramsDirectory , "The Microsoft Network" ) , "msnver.txt" , "5900" , 12 , 4 ) ;
2017-07-23 08:37:18 -04:00
2017-07-01 17:17:57 -04:00
CreateWindowsDirectory ( ) ;
}
public static void CreateWindowsDirectory ( )
{
2017-07-27 15:58:05 -04:00
SaveDirectoryInfo ( ProfileWindowsDirectory , "System" , true , "System" , true ) ;
SaveDirectoryInfo ( ProfileWindowsDirectory , "Config" , true , "Config" , true ) ;
SaveDirectoryInfo ( ProfileWindowsDirectory , "Cursors" , true , "Cursors" , true ) ;
SaveDirectoryInfo ( ProfileWindowsDirectory , "Fonts" , true , "Fonts" , true ) ;
SaveDirectoryInfo ( ProfileWindowsDirectory , "Help" , true , "Help" , true ) ;
SaveDirectoryInfo ( ProfileWindowsDirectory , "Temp" , true , "Temp" , true ) ;
SaveDirectoryInfo ( ProfileWindowsDirectory , "Desktop" , true , "Desktop" , true ) ;
2017-08-05 08:29:30 -04:00
2017-08-01 10:28:47 -04:00
CreateWindowsFile ( ProfileWindowsDirectory , "calc.exe" , "calc" , 13 , 59392 ) ;
2017-08-01 13:05:37 -04:00
CreateWindowsFile ( ProfileWindowsDirectory , "emm386.exe" , "emm386" , 10 , 125495 ) ;
2017-08-01 10:28:47 -04:00
CreateWindowsFile ( ProfileWindowsDirectory , "explorer.exe" , "explorer" , 0 , 204288 ) ;
CreateWindowsFile ( ProfileWindowsDirectory , "notepad.exe" , "notepad" , 14 , 34034 ) ;
CreateWindowsFile ( ProfileWindowsDirectory , "regedit.exe" , "regedit" , 15 , 120320 ) ;
2017-08-01 13:05:37 -04:00
CreateWindowsFile ( ProfileWindowsDirectory , "win.com" , "" , 10 , 22679 ) ;
CreateWindowsFile ( ProfileWindowsDirectory , "write.exe" , "wordpad" , 16 , 5120 ) ;
2017-07-01 17:17:57 -04:00
}
2017-08-05 08:29:30 -04:00
public static void CreateWindowsFile ( string filepath , string filename , string contents , int fileicon , int bytes )
2017-07-01 17:17:57 -04:00
{
2017-07-27 00:25:37 -04:00
File . WriteAllText ( Path . Combine ( filepath , filename ) , contents ) ;
THFileInfo info = new THFileInfo ( ) ;
info . Name = filename ;
info . FileIcon = fileicon ;
2017-07-27 23:58:39 -04:00
info . ByteSize = bytes ;
2017-07-28 09:01:37 -04:00
CurrentSave . BytesLeft - = bytes ;
2017-07-27 00:25:37 -04:00
UpdateDirectoryInfo ( filepath , info ) ;
}
public static void UpdateDirectoryInfo ( string path , THFileInfo newfile )
{
2017-07-27 10:23:57 -04:00
newfile . DOSName = newfile . Name . ToUpper ( ) . Replace ( "*" , "" ) . Replace ( "+" , "" ) . Replace ( ":" , "" ) . Replace ( ";" , "" ) . Replace ( " " , "" ) ;
2017-08-01 13:05:37 -04:00
if ( newfile . DOSName . Contains ( "." ) )
2017-07-27 10:23:57 -04:00
{
2017-08-01 13:05:37 -04:00
string [ ] dos = newfile . DOSName . Split ( '.' ) ;
if ( dos . Count ( ) > 2 )
{
List < string > dosb = dos . ToList ( ) ;
dosb . RemoveRange ( 1 , dos . Count ( ) - 2 ) ;
dos = dosb . ToArray ( ) ;
}
dos [ 1 ] = dos [ 1 ] . Substring ( 0 , 3 ) ;
if ( dos [ 0 ] . Length > 8 ) dos [ 0 ] = dos [ 0 ] . Substring ( 0 , 6 ) + "~1" ;
2017-07-27 10:23:57 -04:00
2017-08-01 13:05:37 -04:00
newfile . DOSName = dos [ 0 ] + "." + dos [ 1 ] ;
}
else if ( newfile . DOSName . Length > 8 ) newfile . DOSName = newfile . DOSName . Substring ( 0 , 6 ) + "~1" ;
2017-07-27 10:23:57 -04:00
2017-07-27 00:25:37 -04:00
if ( File . ReadAllText ( Path . Combine ( path , "_data.info" ) ) . Contains ( newfile . DOSName ) ) return ;
FileSystemFolderInfo fsfi = JsonConvert . DeserializeObject < FileSystemFolderInfo > ( File . ReadAllText ( Path . Combine ( path , "_data.info" ) ) ) ;
2017-07-27 10:23:57 -04:00
fsfi . Files . Add ( newfile ) ;
2017-07-27 23:58:39 -04:00
fsfi . ByteSize + = newfile . ByteSize ;
2017-07-27 00:25:37 -04:00
string toWrite = JsonConvert . SerializeObject ( fsfi , Formatting . Indented ) ;
File . WriteAllText ( Path . Combine ( path , "_data.info" ) , toWrite ) ;
2017-05-07 20:04:31 -04:00
}
2017-07-22 04:27:28 -04:00
public static void UpgradeFileSystem ( string oldOS , string newOS )
{
switch ( oldOS )
{
case "95" :
if ( newOS = = "98" | | newOS = = "2000" | | newOS = = "ME" )
{
// We are upgrading from the old WinClassic file System to the new WinClassic filesystem!
// All the above OSes share basically the same file layout!
// (Excluding Documents And Settings) which is 2000 and ME only
2017-07-22 07:45:53 -04:00
// Add Address Book into existance!
2017-07-27 15:58:05 -04:00
SaveDirectoryInfo ( ProfileProgramsDirectory , "Outlook Express" , false , "Outlook Express" , true ) ;
2017-08-05 08:29:30 -04:00
CreateWindowsFile ( Path . Combine ( ProfileProgramsDirectory , "Outlook Express" ) , "WAB.exe" , "addressbook" , 8 , 512 ) ;
2017-07-23 08:37:18 -04:00
// There is no "The Microsoft Network" folder!
2017-07-23 12:42:07 -04:00
if ( Directory . Exists ( Path . Combine ( ProfileProgramsDirectory , "The Microsoft Network" ) ) ) Directory . Delete ( Path . Combine ( ProfileProgramsDirectory , "The Microsoft Network" ) , true ) ;
2017-08-09 18:02:34 -04:00
FileSystemFolderInfo fsfi = JsonConvert . DeserializeObject < FileSystemFolderInfo > ( File . ReadAllText ( Path . Combine ( ProfileProgramsDirectory , "_data.info" ) ) ) ;
foreach ( THDirInfo dir in fsfi . SubDirs )
{
if ( dir . Name = = "The Microsoft Network" )
{
fsfi . SubDirs . Remove ( dir ) ;
break ;
}
}
2017-07-22 04:27:28 -04:00
}
break ;
}
}
2017-07-27 15:58:05 -04:00
public static void SaveDirectoryInfo ( string parent , string dirname , bool isProtected , string label , bool allowback )
2017-05-24 16:42:48 -04:00
{
2017-08-13 15:18:46 -04:00
if ( File . Exists ( Path . Combine ( parent , dirname , "_data.info" ) ) & & Path . Combine ( parent , dirname ) ! = ProfileFileSystemDirectory ) return ;
2017-07-27 23:58:39 -04:00
Directory . CreateDirectory ( Path . Combine ( parent , dirname ) ) ;
2017-06-10 07:22:50 -04:00
2017-05-24 16:42:48 -04:00
FileSystemFolderInfo info = new FileSystemFolderInfo ( ) ;
2017-07-27 00:25:37 -04:00
info . IsProtected = isProtected ;
info . Label = label ;
2017-07-27 10:33:02 -04:00
info . DOSLabel = info . Label . ToUpper ( ) . Replace ( "*" , "" ) . Replace ( "+" , "" ) . Replace ( ":" , "" ) . Replace ( ";" , "" ) . Replace ( "." , "" ) . Replace ( " " , "" ) ;
if ( info . DOSLabel . Length > 8 ) info . DOSLabel = info . DOSLabel . Substring ( 0 , 6 ) + "~1" ;
2017-07-31 21:04:18 -04:00
if ( label = = "C:" ) info . DOSLabel = "C:" ;
2017-07-27 00:25:37 -04:00
info . AllowBack = allowback ;
2017-07-27 10:23:57 -04:00
info . Files = new List < THFileInfo > ( 256 ) ;
2017-07-27 15:58:05 -04:00
info . SubDirs = new List < THDirInfo > ( 256 ) ;
2017-07-27 23:58:39 -04:00
info . ByteSize = 0 ;
2017-07-27 15:58:05 -04:00
if ( parent ! = ProfileDirectory )
{
FileSystemFolderInfo fsfi = JsonConvert . DeserializeObject < FileSystemFolderInfo > ( File . ReadAllText ( Path . Combine ( parent , "_data.info" ) ) ) ;
THDirInfo thd = new THDirInfo ( ) ;
thd . Name = info . Label ;
thd . DOSName = info . DOSLabel ;
fsfi . SubDirs . Add ( thd ) ;
File . WriteAllText ( Path . Combine ( parent , "_data.info" ) , JsonConvert . SerializeObject ( fsfi , Formatting . Indented ) ) ;
}
2017-05-24 16:42:48 -04:00
string toWrite = JsonConvert . SerializeObject ( info , Formatting . Indented ) ;
2017-07-27 15:58:05 -04:00
File . WriteAllText ( Path . Combine ( Path . Combine ( parent , dirname ) , "_data.info" ) , toWrite ) ;
2017-05-24 16:42:48 -04:00
}
2017-07-23 08:11:01 -04:00
#if BINARY_SAVE
// Be careful with this... it trusts that the calling code has already checked
// that T can be written by BinaryWriter.
// No generics, because that'd be near-impossible to read back.
private static void WriteList < T > ( BinaryWriter write , List < T > list )
{
if ( list = = null )
write . Write ( 0 ) ;
else
{
write . Write ( list . Count ) ;
foreach ( T obj in list )
( ( dynamic ) write ) . Write ( obj ) ;
}
}
private static List < T > ReadList < T > ( BinaryReader read , string reader )
{
int count = read . ReadInt32 ( ) ;
var ret = new List < T > ( count ) ;
var function = typeof ( BinaryReader ) . GetMethod ( reader ) ;
for ( int i = 0 ; i < count ; i + + )
ret . Add ( ( T ) function . Invoke ( read , new object [ ] { } ) ) ;
return ret ;
}
private static void WriteBitfield ( Stream fobj , IEnumerable < bool > bools )
{
sbyte bit = 7 ;
int cur = 0 ;
var bitfields = new byte [ bools . Count ( ) / 8 + 1 ] ;
foreach ( bool mybool in bools )
{
if ( mybool )
bitfields [ cur ] | = ( byte ) ( 1 < < bit ) ;
bit - - ;
if ( bit < 0 )
{
bit = 7 ;
cur + + ;
}
}
fobj . Write ( bitfields , 0 , bitfields . Length ) ;
}
private static List < bool > ReadBitfield ( Stream fobj , int count )
{
sbyte bit = 7 ;
int cur = 0 ;
var bitfields = new byte [ count / 8 + 1 ] ;
var bools = new List < bool > ( count ) ;
byte val = ( byte ) fobj . ReadByte ( ) ;
fobj . Read ( bitfields , 0 , bitfields . Length ) ;
for ( int i = 0 ; i < count ; i + + )
{
bools . Add ( ( ( val > > bit ) & 1 ) = = 1 ) ;
bit - - ;
if ( bit < 0 )
{
bit = 7 ;
cur + + ;
}
}
return bools ;
}
#endif
public static Save ReadSave ( string fname )
{
#if BINARY_SAVE
using ( var fobj = File . OpenRead ( fname ) )
{
var save = new Save ( ) ;
var header = new byte [ magic . Length ] ;
var read = new BinaryReader ( fobj ) ;
fobj . Read ( header , 0 , magic . Length ) ;
if ( ! magic . SequenceEqual ( header ) )
throw new InvalidDataException ( "This is not a TimeHACK binary save" ) ;
int numprops = read . ReadInt32 ( ) ;
var bools = new List < System . Reflection . PropertyInfo > ( ) ;
// Holy code duplication, Batman.
// If you know a better way to get C# to do this, I'm all ears.
foreach ( var property in properties . Take ( numprops ) )
{
if ( property . PropertyType = = typeof ( string ) )
property . SetValue ( save , read . ReadString ( ) ) ;
else if ( property . PropertyType = = typeof ( int ) )
property . SetValue ( save , read . ReadInt32 ( ) ) ;
else if ( property . PropertyType = = typeof ( uint ) )
property . SetValue ( save , read . ReadUInt32 ( ) ) ;
else if ( property . PropertyType = = typeof ( long ) )
property . SetValue ( save , read . ReadInt64 ( ) ) ;
else if ( property . PropertyType = = typeof ( ulong ) )
property . SetValue ( save , read . ReadUInt64 ( ) ) ;
else if ( property . PropertyType = = typeof ( short ) )
property . SetValue ( save , read . ReadInt16 ( ) ) ;
else if ( property . PropertyType = = typeof ( ushort ) )
property . SetValue ( save , read . ReadUInt16 ( ) ) ;
else if ( property . PropertyType = = typeof ( byte ) )
property . SetValue ( save , read . ReadByte ( ) ) ;
else if ( property . PropertyType = = typeof ( sbyte ) )
property . SetValue ( save , read . ReadSByte ( ) ) ;
else if ( property . PropertyType = = typeof ( char ) )
property . SetValue ( save , read . ReadChar ( ) ) ;
else if ( property . PropertyType = = typeof ( float ) )
property . SetValue ( save , read . ReadSingle ( ) ) ;
else if ( property . PropertyType = = typeof ( double ) )
property . SetValue ( save , read . ReadDouble ( ) ) ;
else if ( property . PropertyType = = typeof ( decimal ) )
property . SetValue ( save , read . ReadDecimal ( ) ) ;
else if ( property . PropertyType = = typeof ( List < string > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadString" ) ) ;
else if ( property . PropertyType = = typeof ( List < int > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadInt32" ) ) ;
else if ( property . PropertyType = = typeof ( List < uint > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadUInt32" ) ) ;
else if ( property . PropertyType = = typeof ( List < long > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadInt64" ) ) ;
else if ( property . PropertyType = = typeof ( List < ulong > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadUInt64" ) ) ;
else if ( property . PropertyType = = typeof ( List < short > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadInt16" ) ) ;
else if ( property . PropertyType = = typeof ( List < ushort > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadUInt16" ) ) ;
else if ( property . PropertyType = = typeof ( List < byte > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadByte" ) ) ;
else if ( property . PropertyType = = typeof ( List < sbyte > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadSByte" ) ) ;
else if ( property . PropertyType = = typeof ( List < char > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadChar" ) ) ;
else if ( property . PropertyType = = typeof ( List < float > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadSingle" ) ) ;
else if ( property . PropertyType = = typeof ( List < double > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadDouble" ) ) ;
else if ( property . PropertyType = = typeof ( List < decimal > ) )
property . SetValue ( save , ReadList < string > ( read , "ReadDecimal" ) ) ;
// Remember to read this boolean from the bitfield at the end.
else if ( property . PropertyType = = typeof ( bool ) )
bools . Add ( property ) ;
else if ( property . PropertyType = = typeof ( List < bool > ) )
property . SetValue ( save , ReadBitfield ( fobj , read . ReadInt32 ( ) ) ) ;
// RIP
else
throw new InvalidDataException ( "There is no deserialisation method specified for " + property . PropertyType . ToString ( ) ) ;
}
// Let's read the ultra tiny bitfield.
var loaded = ReadBitfield ( fobj , bools . Count ) ;
foreach ( var item in bools . Zip ( loaded , ( p , b ) = > new { Property = p , Value = b } ) )
item . Property . SetValue ( save , item . Value ) ;
return save ;
}
#else
return JsonConvert . DeserializeObject < Save > ( File . ReadAllText ( fname ) ) ;
#endif
}
public static void WriteSave ( string fname , Save save )
{
#if BINARY_SAVE
using ( var fobj = File . OpenWrite ( fname ) )
{
var write = new BinaryWriter ( fobj ) ;
var bools = new List < bool > ( ) ;
fobj . Write ( magic , 0 , magic . Length ) ;
write . Write ( properties . Count ( ) ) ; // The number of properties basically acts as the version number.
foreach ( var property in properties )
{
if ( property = = null )
continue ;
// Types that can be written by BinaryWriter, except booleans.
if ( property . PropertyType = = typeof ( string ) )
{
var val = property . GetValue ( save ) as string ;
if ( val = = null )
write . Write ( "" ) ;
else
write . Write ( val ) ;
}
else if ( property . PropertyType = = typeof ( int ) )
write . Write ( ( int ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( uint ) )
write . Write ( ( uint ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( long ) )
write . Write ( ( long ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( ulong ) )
write . Write ( ( ulong ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( short ) )
write . Write ( ( short ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( ushort ) )
write . Write ( ( ushort ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( byte ) )
write . Write ( ( byte ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( sbyte ) )
write . Write ( ( sbyte ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( char ) )
write . Write ( ( char ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( float ) )
write . Write ( ( float ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( double ) )
write . Write ( ( double ) property . GetValue ( save ) ) ;
else if ( property . PropertyType = = typeof ( decimal ) )
write . Write ( ( double ) property . GetValue ( save ) ) ;
// ... and their lists.
else if ( property . PropertyType = = typeof ( List < string > ) )
WriteList ( write , property . GetValue ( save ) as List < string > ) ;
else if ( property . PropertyType = = typeof ( List < int > ) )
WriteList ( write , property . GetValue ( save ) as List < int > ) ;
else if ( property . PropertyType = = typeof ( List < uint > ) )
WriteList ( write , property . GetValue ( save ) as List < uint > ) ;
else if ( property . PropertyType = = typeof ( List < long > ) )
WriteList ( write , property . GetValue ( save ) as List < long > ) ;
else if ( property . PropertyType = = typeof ( List < ulong > ) )
WriteList ( write , property . GetValue ( save ) as List < ulong > ) ;
else if ( property . PropertyType = = typeof ( List < short > ) )
WriteList ( write , property . GetValue ( save ) as List < short > ) ;
else if ( property . PropertyType = = typeof ( List < ushort > ) )
WriteList ( write , property . GetValue ( save ) as List < ushort > ) ;
else if ( property . PropertyType = = typeof ( List < byte > ) )
WriteList ( write , property . GetValue ( save ) as List < byte > ) ;
else if ( property . PropertyType = = typeof ( List < sbyte > ) )
WriteList ( write , property . GetValue ( save ) as List < sbyte > ) ;
else if ( property . PropertyType = = typeof ( List < char > ) )
WriteList ( write , property . GetValue ( save ) as List < char > ) ;
else if ( property . PropertyType = = typeof ( List < float > ) )
WriteList ( write , property . GetValue ( save ) as List < float > ) ;
else if ( property . PropertyType = = typeof ( List < double > ) )
WriteList ( write , property . GetValue ( save ) as List < double > ) ;
else if ( property . PropertyType = = typeof ( List < decimal > ) )
WriteList ( write , property . GetValue ( save ) as List < decimal > ) ;
// Booleans - they go in the bitfield at the end.
else if ( property . PropertyType = = typeof ( bool ) )
bools . Add ( ( bool ) property . GetValue ( save ) ) ;
// List of booleans - it gets its own bitfield.
else if ( property . PropertyType = = typeof ( List < bool > ) )
{
var val = property . GetValue ( save ) as List < bool > ;
if ( val = = null )
write . Write ( 0 ) ;
else
{
write . Write ( val . Count ( ) ) ;
WriteBitfield ( fobj , val ) ;
}
}
// Now what?
else
throw new InvalidDataException ( "There is no serialisation method specified for " + property . PropertyType . ToString ( ) ) ;
}
// In order to save space, we store bools in a bitfield at the end.
// One byte can store 8 bools, saving a whopping 7 bytes which can then be used for
// extremely short text documents or something.
WriteBitfield ( fobj , bools ) ;
}
#else
// Serialize the save to JSON.
File . WriteAllText ( fname , JsonConvert . SerializeObject ( save , Formatting . Indented ) ) ;
#endif
}
2017-05-24 16:42:48 -04:00
2017-05-07 20:04:31 -04:00
public static void SaveGame ( )
{
2017-07-23 08:11:01 -04:00
WriteSave ( Path . Combine ( ProfileDirectory , ProfileFile ) , CurrentSave ) ;
}
public static bool LoadSave ( )
{
string savefile = Path . Combine ( ProfileDirectory , ProfileFile ) ;
try
{
CurrentSave = ReadSave ( savefile ) ;
}
catch
{
MessageBox . Show ( "WARNING! It looks like this save is corrupt! We will now open the Save troubleshooter" ) ;
troubleshooter . ShowDialog ( ) ;
}
return true ;
2017-05-07 20:04:31 -04:00
}
2017-07-05 17:25:36 -04:00
2017-07-22 15:35:30 -04:00
public static byte [ ] GetAchievements ( )
{
2017-08-14 13:13:53 -04:00
byte [ ] byt = new byte [ ] { 0 , // Piracy Ending
0 , // End of Internet Ending
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } ;
2017-07-22 15:35:30 -04:00
if ( DevMode ) File . WriteAllBytes ( Path . Combine ( DataDirectory , "achieved.thack" ) , byt ) ;
if ( File . Exists ( Path . Combine ( DataDirectory , "achieved.thack" ) ) ) byt = File . ReadAllBytes ( Path . Combine ( DataDirectory , "achieved.thack" ) ) ;
else File . WriteAllBytes ( Path . Combine ( DataDirectory , "achieved.thack" ) , byt ) ;
return byt ;
}
2017-08-05 21:14:30 -04:00
public static void SaveAchievement ( int achievementID )
{
2017-08-06 19:10:09 -04:00
if ( ! File . Exists ( Path . Combine ( DataDirectory , "achieved.thack" ) ) ) File . WriteAllBytes ( Path . Combine ( DataDirectory , "achieved.thack" ) , new byte [ ] { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } ) ;
2017-08-05 21:14:30 -04:00
byte [ ] byt = File . ReadAllBytes ( Path . Combine ( DataDirectory , "achieved.thack" ) ) ;
byt [ achievementID ] = 1 ;
File . WriteAllBytes ( Path . Combine ( DataDirectory , "achieved.thack" ) , byt ) ;
}
2017-07-05 17:25:36 -04:00
public static void SetTheme ( )
{
switch ( CurrentSave . ThemeName )
{
case "default95" :
currentTheme = new Default95Theme ( ) ;
break ;
2017-07-22 05:17:11 -04:00
case "default98" :
currentTheme = new Default98Theme ( ) ;
break ;
2017-07-05 17:25:36 -04:00
case "dangeranimals" :
currentTheme = new DangerousCreaturesTheme ( ) ;
break ;
2017-07-05 18:41:55 -04:00
case "insidepc" :
currentTheme = new InsideComputerTheme ( ) ;
break ;
2017-07-05 17:25:36 -04:00
}
}
2017-05-07 20:04:31 -04:00
}
2017-07-23 08:11:01 -04:00
// This lets us preserve the order of properties.
// Thanks to "ghord" from StackOverflow.
public sealed class OrderAttribute : Attribute
{
private readonly int order_ ;
public OrderAttribute ( [ CallerLineNumber ] int order = 0 )
{
order_ = order ;
}
public int Order { get { return order_ ; } }
}
2017-05-07 20:04:31 -04:00
public class Save
{
2017-07-23 08:11:01 -04:00
// To maintain binary save compatibility,
// add all new properties to the end and don't remove any.
// Also, every property needs an "Order" attribute.
[Order]
2017-05-07 20:04:31 -04:00
public string Username { get ; set ; }
2017-06-10 07:22:50 -04:00
2017-07-23 08:11:01 -04:00
[Order]
2017-06-10 07:22:50 -04:00
public string CurrentOS { get ; set ; }
2017-07-23 08:11:01 -04:00
2017-06-10 07:22:50 -04:00
// public Dictionary<string, bool> InstalledPrograms { get; set; } InstallProgram is no longer needed... we have that data in the FileSystem
2017-07-23 08:11:01 -04:00
[Order]
2017-05-07 20:04:31 -04:00
public List < string > ExperiencedStories { get ; set ; }
2017-07-23 08:11:01 -04:00
[Order]
2017-07-05 12:54:34 -04:00
public bool FTime95 { get ; set ; }
2017-07-23 08:11:01 -04:00
2017-08-02 22:03:57 -04:00
[Order]
public int mineSweepE { get ; set ; } = 999 ;
[Order]
public int mineSweepI { get ; set ; } = 999 ;
[Order]
public int mineSweepH { get ; set ; } = 999 ;
2017-07-23 08:11:01 -04:00
[Order]
2017-07-04 18:24:33 -04:00
public string ThemeName { get ; set ; }
2017-07-28 09:01:37 -04:00
[Order]
public int BytesLeft { get ; set ; }
2017-07-31 15:40:59 -04:00
[Order]
public Theme customTheme { get ; set ; }
2017-05-07 20:04:31 -04:00
}
2017-05-24 16:42:48 -04:00
public class FileSystemFolderInfo
{
2017-07-27 00:25:37 -04:00
public bool IsProtected { get ; set ; }
public string Label { get ; set ; }
2017-07-27 10:33:02 -04:00
public string DOSLabel { get ; set ; }
2017-07-27 00:25:37 -04:00
public bool AllowBack { get ; set ; }
2017-07-27 23:58:39 -04:00
public int ByteSize { get ; set ; }
2017-07-27 10:23:57 -04:00
public List < THFileInfo > Files { get ; set ; }
2017-07-27 15:58:05 -04:00
public List < THDirInfo > SubDirs { get ; set ; }
2017-07-27 00:25:37 -04:00
}
public class THFileInfo
{
public string Name { get ; set ; }
public string DOSName { get ; set ; }
public int FileIcon { get ; set ; }
2017-07-27 23:58:39 -04:00
public int ByteSize { get ; set ; }
2017-05-24 16:42:48 -04:00
}
2017-07-27 15:58:05 -04:00
public class THDirInfo
{
public string Name { get ; set ; }
public string DOSName { get ; set ; }
}
2017-05-07 20:04:31 -04:00
}