diff --git a/Histacom2.Engine/Histacom2.Engine.csproj b/Histacom2.Engine/Histacom2.Engine.csproj index 1d36223..69443b1 100644 --- a/Histacom2.Engine/Histacom2.Engine.csproj +++ b/Histacom2.Engine/Histacom2.Engine.csproj @@ -44,6 +44,9 @@ + + ..\packages\Whoa.1.5.0\lib\net45\Whoa.dll + diff --git a/Histacom2.Engine/SaveSystem.cs b/Histacom2.Engine/SaveSystem.cs index a3fd2ef..b4cd56f 100644 --- a/Histacom2.Engine/SaveSystem.cs +++ b/Histacom2.Engine/SaveSystem.cs @@ -2,7 +2,7 @@ // to put some effort into cheating ;) // During development, leave it undefined to use the // easily modifiable JSON serialised format -//#define BINARY_SAVE +#define BINARY_SAVE using System; using System.Collections.Generic; @@ -18,6 +18,10 @@ using System.Text.RegularExpressions; using System.Runtime.InteropServices; +#if BINARY_SAVE +using Whoa; +#endif + namespace Histacom2.Engine { public static class SaveSystem @@ -36,8 +40,7 @@ public static class SaveSystem #endif #if BINARY_SAVE - private static readonly byte[] magic = Encoding.UTF8.GetBytes("THSv"); - private static readonly IOrderedEnumerable properties = typeof(Save).GetProperties().OrderBy(p => (p.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault() as OrderAttribute).Order); + private static readonly int magic = 0x76534854; // 'THSv' #endif public static string GameDirectory @@ -65,7 +68,11 @@ public static string AllProfilesDirectory } public static string ProfileName = ""; - public static string ProfileFile = "main.save"; +#if BINARY_SAVE + public static string ProfileFile = "main.whoa"; +#else + public static string ProfileFile = "main.json"; +#endif public static string ProfileDirectory { @@ -404,162 +411,15 @@ public static void SaveDirectoryInfo(string parent, string dirname, bool isProte File.WriteAllText(Path.Combine(Path.Combine(parent, dirname), "_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(BinaryWriter write, List list) - { - if (list == null) - write.Write(0); - else - { - write.Write(list.Count); - foreach (T obj in list) - ((dynamic)write).Write(obj); - } - } - - private static List ReadList(BinaryReader read, string reader) - { - int count = read.ReadInt32(); - var ret = new List(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 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 ReadBitfield(Stream fobj, int count) - { - sbyte bit = 7; - int cur = 0; - var bitfields = new byte[count / 8 + 1]; - var bools = new List(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)) + using (var read = new BinaryReader(fobj)) { - 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)) + if (read.ReadInt32() != magic) throw new InvalidDataException("This is not a Histacom2 binary save"); - int numprops = read.ReadInt32(); - var bools = new List(); - // 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)) - property.SetValue(save, ReadList(read, "ReadString")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadInt32")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadUInt32")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadInt64")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadUInt64")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadInt16")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadUInt16")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadByte")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadSByte")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadChar")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadSingle")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(read, "ReadDouble")); - else if (property.PropertyType == typeof(List)) - property.SetValue(save, ReadList(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)) - 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; + return Whoa.Whoa.DeserialiseObject(fobj, SerialisationOptions.NonSerialized); } #else return JsonConvert.DeserializeObject(File.ReadAllText(fname)); @@ -570,105 +430,10 @@ public static void WriteSave(string fname, Save save) { #if BINARY_SAVE using (var fobj = File.OpenWrite(fname)) + using (var write = new BinaryWriter(fobj)) { - var write = new BinaryWriter(fobj); - var bools = new List(); - 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)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - else if (property.PropertyType == typeof(List)) - WriteList(write, property.GetValue(save) as List); - - // 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)) - { - var val = property.GetValue(save) as List; - 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); + write.Write(magic); + Whoa.Whoa.SerialiseObject(fobj, save, SerialisationOptions.NonSerialized); } #else // Serialize the save to JSON. @@ -772,61 +537,25 @@ public static void SetTheme() } } - - // 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. + // add all new properties/fields to the end and don't remove any. - [Order] public string Username { get; set; } - - [Order] public string CurrentOS { get; set; } // public Dictionary InstalledPrograms { get; set; } InstallProgram is no longer needed... we have that data in the FileSystem - [Order] public List ExperiencedStories { get; set; } - - [Order] public bool FTime95 { get; set; } - - [Order] public int mineSweepE { get; set; } = 999; - - [Order] public int mineSweepI { get; set; } = 999; - - [Order] public int mineSweepH { get; set; } = 999; - - [Order] public string ThemeName { get; set; } - - [Order] public int BytesLeft { get; set; } - - [Order] public Theme customTheme { get; set; } - - [Order] public bool FTime98 { get; set; } - - [Order] public bool[] installed95 { get; set; } = new bool[7]; // 0: WC98, 1: FTP, 2: SR, 3: EB, 4: SKNDWS, 5: TD0.1, 6: GTN } diff --git a/Histacom2.Engine/packages.config b/Histacom2.Engine/packages.config index 810e559..de52fcd 100644 --- a/Histacom2.Engine/packages.config +++ b/Histacom2.Engine/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Histacom2/SaveDialogs/SaveFileTroubleShooter.cs b/Histacom2/SaveDialogs/SaveFileTroubleShooter.cs index 3283ca1..7fe258f 100644 --- a/Histacom2/SaveDialogs/SaveFileTroubleShooter.cs +++ b/Histacom2/SaveDialogs/SaveFileTroubleShooter.cs @@ -42,12 +42,12 @@ void BeginScan() // Check if the main.save file exists - string savefile = Path.Combine(SaveSystem.ProfileDirectory, "main.save"); - string oldsavefile = Path.Combine(SaveSystem.ProfileDirectory, "oldmain.save"); + string savefile = Path.Combine(SaveSystem.ProfileDirectory, SaveSystem.ProfileFile); + string oldsavefile = Path.Combine(SaveSystem.ProfileDirectory, "old" + SaveSystem.ProfileFile); if (!File.Exists(savefile)) { - WriteToLog("ISSUE FOUND! File main.save doesn't exist"); + WriteToLog($"ISSUE FOUND! File {SaveSystem.ProfileFile} doesn't exist"); WriteToLog("Creating one..."); @@ -58,7 +58,7 @@ void BeginScan() EndScan(true); return; } else { - WriteToLog("File main.save does exist - checking contents"); + WriteToLog($"File {SaveSystem.ProfileFile} does exist - checking contents"); bool readable = false; try @@ -136,11 +136,11 @@ void BeginScan() } catch { // It's unusable... - WriteToLog("ISSUE FOUND! File main.save is unreadable"); + WriteToLog($"ISSUE FOUND! File {SaveSystem.ProfileFile} is unreadable"); WriteToLog("Sorry, there is no repairing it easily, your data will be lost"); - string backupfile = Path.Combine(SaveSystem.ProfileDirectory, "main.backup"); + string backupfile = savefile + ".bak"; if (Directory.Exists(backupfile)) Directory.Delete(backupfile);