Created
August 26, 2017 15:00
-
-
Save DeclanHoare/c342c2c7ed31842556cd70e6951f4bb5 to your computer and use it in GitHub Desktop.
Holey Moley Code Dupe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Newtonsoft.Json; | |
using System.Diagnostics; | |
using System.Windows.Forms; | |
using System.Runtime.CompilerServices; | |
namespace TimeHACK.Engine | |
{ | |
public static class SaveSystem | |
{ | |
public static Save CurrentSave { get; set; } | |
public static FileSystemFolderInfo filesystemflinfo { get; set; } | |
public static bool DevMode = false; | |
public static Form troubleshooter; | |
public static Theme currentTheme { get; set; } | |
#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 | |
public static string GameDirectory | |
{ | |
get | |
{ | |
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TimeHACK"); | |
} | |
} | |
public static string DataDirectory | |
{ | |
get | |
{ | |
return Path.Combine(GameDirectory, "Data"); | |
} | |
} | |
public static string AllProfilesDirectory | |
{ | |
get | |
{ | |
return Path.Combine(GameDirectory, "Profiles"); | |
} | |
} | |
public static string ProfileName = ""; | |
public static string ProfileFile = "main.save"; | |
public static string ProfileDirectory | |
{ | |
get | |
{ | |
return Path.Combine(GameDirectory, Path.Combine("Profiles", ProfileName)); | |
} | |
} | |
public static string ProfileFileSystemDirectory | |
{ | |
get | |
{ | |
return Path.Combine(ProfileDirectory, "folders"); | |
} | |
} | |
public static string ProfileMyComputerDirectory | |
{ | |
get | |
{ | |
return Path.Combine(ProfileFileSystemDirectory, "CDrive"); | |
} | |
} | |
public static string ProfileSettingsDirectory | |
{ | |
get | |
{ | |
return Path.Combine(ProfileMyComputerDirectory, "Settings"); | |
} | |
} | |
public static string ProfileDocumentsDirectory | |
{ | |
get | |
{ | |
return Path.Combine(ProfileMyComputerDirectory, "Doc"); | |
} | |
} | |
public static string ProfileProgramsDirectory | |
{ | |
get | |
{ | |
return Path.Combine(ProfileMyComputerDirectory, "Prog"); | |
} | |
} | |
public static string ProfileWindowsDirectory | |
{ | |
get | |
{ | |
return Path.Combine(ProfileMyComputerDirectory, "Win"); | |
} | |
} | |
public static void NewGame() | |
{ | |
var save = new Save(); | |
save.ExperiencedStories = new List<string>(); | |
if (DevMode == true) | |
{ | |
if (ProfileName == "98") | |
{ | |
save.CurrentOS = "98"; | |
save.ThemeName = "default98"; | |
currentTheme = new Default98Theme(); | |
} | |
else | |
{ | |
save.CurrentOS = "95"; | |
save.ThemeName = "default95"; | |
currentTheme = new Default95Theme(); | |
} | |
} | |
else | |
{ | |
save.CurrentOS = "95"; | |
save.ThemeName = "default95"; | |
currentTheme = new Default95Theme(); | |
} | |
CurrentSave = save; | |
CheckFiles(); | |
SaveGame(); | |
} | |
public static void CheckFiles() | |
{ | |
if (!Directory.Exists(GameDirectory)) | |
Directory.CreateDirectory(GameDirectory); | |
if (!Directory.Exists(AllProfilesDirectory)) | |
Directory.CreateDirectory(AllProfilesDirectory); | |
if (!Directory.Exists(ProfileDirectory)) | |
Directory.CreateDirectory(ProfileDirectory); | |
if (!Directory.Exists(ProfileFileSystemDirectory)) | |
Directory.CreateDirectory(ProfileFileSystemDirectory); | |
SaveDirectoryInfo(ProfileFileSystemDirectory, false, "My Computer", false); | |
SaveDirectoryInfo(ProfileMyComputerDirectory, false, "Win95 (C:)", true); | |
if (CurrentSave.CurrentOS == "95" || CurrentSave.CurrentOS == "98") SaveDirectoryInfo(ProfileDocumentsDirectory, false, "My Documents", true); | |
if (CurrentSave.CurrentOS == "2000" || CurrentSave.CurrentOS == "ME") SaveDirectoryInfo(ProfileSettingsDirectory, false, "Documents and Settings", true); | |
SaveDirectoryInfo(Path.Combine(ProfileProgramsDirectory, "Accessories"), false, "Accessories", true); | |
SaveDirectoryInfo(ProfileProgramsDirectory, true, "Program Files", true); | |
SaveDirectoryInfo(ProfileWindowsDirectory, true, "Windows", true); | |
CreateWindowsFile(Path.Combine(ProfileProgramsDirectory, "Accessories", "wordpad.exe"), "wordpad"); | |
CreateWindowsDirectory(); | |
} | |
public static void CreateWindowsDirectory() | |
{ | |
SaveDirectoryInfo(Path.Combine(ProfileWindowsDirectory, "System"), true, "System", true); | |
SaveDirectoryInfo(Path.Combine(ProfileWindowsDirectory, "Config"), true, "Config", true); | |
SaveDirectoryInfo(Path.Combine(ProfileWindowsDirectory, "Cursors"), true, "Cursors", true); | |
SaveDirectoryInfo(Path.Combine(ProfileWindowsDirectory, "Fonts"), true, "Fonts", true); | |
SaveDirectoryInfo(Path.Combine(ProfileWindowsDirectory, "Help"), true, "Help", true); | |
SaveDirectoryInfo(Path.Combine(ProfileWindowsDirectory, "Temp"), true, "Temp", true); | |
CreateWindowsFile(Path.Combine(ProfileWindowsDirectory, "calc.exe"), "calc"); | |
CreateWindowsFile(Path.Combine(ProfileWindowsDirectory, "explorer.exe"), "explorer"); | |
} | |
public static void CreateWindowsFile(string filepath, string contents) | |
{ | |
File.WriteAllText(filepath, contents); | |
} | |
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 | |
// Rename the C Drive to Win98 | |
SaveDirectoryInfo(ProfileMyComputerDirectory, false, "Win98 (C:)", true); | |
// Add Address Book into existance! | |
SaveDirectoryInfo(Path.Combine(ProfileProgramsDirectory, "Outlook Express"), false, "Outlook Express", true); | |
CreateWindowsFile(Path.Combine(ProfileProgramsDirectory, "Outlook Express", "WAB.exe"), "addressbook"); | |
} | |
break; | |
} | |
} | |
public static void SaveDirectoryInfo(string directory, bool isProtected, string label, bool allowback) | |
{ | |
if (!Directory.Exists(directory)) | |
Directory.CreateDirectory(directory); | |
FileSystemFolderInfo info = new FileSystemFolderInfo(); | |
info.Isprotected = isProtected; | |
info.label = label; | |
info.allowback = allowback; | |
string toWrite = JsonConvert.SerializeObject(info, Formatting.Indented); | |
File.WriteAllText(Path.Combine(directory, "_data.info"), toWrite); | |
} | |
#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 | |
} | |
public static void SaveGame() | |
{ | |
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; | |
} | |
public static byte[] GetAchievements() | |
{ | |
byte[] byt = new byte[] { 0, 0 }; | |
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; | |
} | |
public static void SetTheme() | |
{ | |
switch (CurrentSave.ThemeName) | |
{ | |
case "default95": | |
currentTheme = new Default95Theme(); | |
break; | |
case "default98": | |
currentTheme = new Default98Theme(); | |
break; | |
case "dangeranimals": | |
currentTheme = new DangerousCreaturesTheme(); | |
break; | |
case "insidepc": | |
currentTheme = new InsideComputerTheme(); | |
break; | |
} | |
} | |
} | |
// 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_; } } | |
} | |
public class Save | |
{ | |
// 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] | |
public string Username { get; set; } | |
[Order] | |
public string CurrentOS { get; set; } | |
// public Dictionary<string, bool> InstalledPrograms { get; set; } InstallProgram is no longer needed... we have that data in the FileSystem | |
[Order] | |
public List<string> ExperiencedStories { get; set; } | |
[Order] | |
public bool FTime95 { get; set; } | |
[Order] | |
public string ThemeName { get; set; } | |
} | |
public class FileSystemFolderInfo | |
{ | |
public bool Isprotected { get; set; } | |
public string label { get; set; } | |
public bool allowback { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment