BS 0.0.1 EasySave初步

This commit is contained in:
ninemine
2025-06-27 19:51:53 +08:00
parent 5aa5998d22
commit e5bc6a2592
60 changed files with 8785 additions and 29 deletions

View File

@@ -31,6 +31,26 @@ namespace Convention
{
return MainThreadID == Thread.CurrentThread.ManagedThreadId;
}
//var filePath = Environment.ExpandEnvironmentVariables(@"%USERPROFILE%\AppData\Local\Temp");
public static string CompanyName = "DefaultCom";
public static string ProductName = "DefaultProject";
public static string PersistentDataPath
{
get
{
if (IsPlatformWindows)
return Environment.ExpandEnvironmentVariables($@"%userprofile%\AppData\LocalLow\{CompanyName}\{ProductName}\");
else if (IsPlatformLinux)
return Environment.ExpandEnvironmentVariables(@"$HOME/.config/");
return "";
}
}
public static string DataPath => "Assets/";
}
public static partial class Utility

View File

@@ -0,0 +1,7 @@
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
public class EasySaved : Attribute{}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
public class EasySaveIgnored : Attribute { }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,208 @@
#if !DISABLE_ENCRYPTION
using System.IO;
using System.Security.Cryptography;
#if NETFX_CORE
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
using System.Runtime.InteropServices.WindowsRuntime;
#endif
namespace Convention.EasySave.Internal
{
public static class EasySaveHash
{
#if NETFX_CORE
public static string SHA1Hash(string input)
{
return System.Text.Encoding.UTF8.GetString(UnityEngine.Windows.Crypto.ComputeSHA1Hash(System.Text.Encoding.UTF8.GetBytes(input)));
}
#else
public static string SHA1Hash(string input)
{
using (SHA1Managed sha1 = new SHA1Managed())
return System.Text.Encoding.UTF8.GetString(sha1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input)));
}
#endif
}
public abstract class EncryptionAlgorithm
{
public abstract byte[] Encrypt(byte[] bytes, string password, int bufferSize);
public abstract byte[] Decrypt(byte[] bytes, string password, int bufferSize);
public abstract void Encrypt(Stream input, Stream output, string password, int bufferSize);
public abstract void Decrypt(Stream input, Stream output, string password, int bufferSize);
protected static void CopyStream(Stream input, Stream output, int bufferSize)
{
byte[] buffer = new byte[bufferSize];
int read;
while ((read = input.Read(buffer, 0, bufferSize)) > 0)
output.Write(buffer, 0, read);
}
}
public class AESEncryptionAlgorithm : EncryptionAlgorithm
{
private const int ivSize = 16;
private const int keySize = 16;
private const int pwIterations = 100;
public override byte[] Encrypt(byte[] bytes, string password, int bufferSize)
{
using (var input = new MemoryStream(bytes))
{
using (var output = new MemoryStream())
{
Encrypt(input, output, password, bufferSize);
return output.ToArray();
}
}
}
public override byte[] Decrypt(byte[] bytes, string password, int bufferSize)
{
using (var input = new MemoryStream(bytes))
{
using (var output = new MemoryStream())
{
Decrypt(input, output, password, bufferSize);
return output.ToArray();
}
}
}
public override void Encrypt(Stream input, Stream output, string password, int bufferSize)
{
input.Position = 0;
#if NETFX_CORE
// Generate an IV and write it to the output.
var iv = CryptographicBuffer.GenerateRandom(ivSize);
output.Write(iv.ToArray(), 0, ivSize);
var pwBuffer = CryptographicBuffer.ConvertStringToBinary(password, BinaryStringEncoding.Utf8);
var keyDerivationProvider = KeyDerivationAlgorithmProvider.OpenAlgorithm("PBKDF2_SHA1");
KeyDerivationParameters pbkdf2Parms = KeyDerivationParameters.BuildForPbkdf2(iv, pwIterations);
// Create a key based on original key and derivation parmaters
CryptographicKey keyOriginal = keyDerivationProvider.CreateKey(pwBuffer);
IBuffer keyMaterial = CryptographicEngine.DeriveKeyMaterial(keyOriginal, pbkdf2Parms, keySize);
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);
var key = provider.CreateSymmetricKey(keyMaterial);
// Get the input stream as an IBuffer.
IBuffer msg;
using(var ms = new MemoryStream())
{
input.CopyTo(ms);
msg = ms.ToArray().AsBuffer();
}
var buffEncrypt = CryptographicEngine.Encrypt(key, msg, iv);
output.Write(buffEncrypt.ToArray(), 0, (int)buffEncrypt.Length);
output.Dispose();
#else
using (var alg = Aes.Create())
{
alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.PKCS7;
alg.GenerateIV();
var key = new Rfc2898DeriveBytes(password, alg.IV, pwIterations);
alg.Key = key.GetBytes(keySize);
// Write the IV to the output stream.
output.Write(alg.IV, 0, ivSize);
using(var encryptor = alg.CreateEncryptor())
using(var cs = new CryptoStream(output, encryptor, CryptoStreamMode.Write))
CopyStream(input, cs, bufferSize);
}
#endif
}
public override void Decrypt(Stream input, Stream output, string password, int bufferSize)
{
#if NETFX_CORE
var thisIV = new byte[ivSize];
input.Read(thisIV, 0, ivSize);
var iv = thisIV.AsBuffer();
var pwBuffer = CryptographicBuffer.ConvertStringToBinary(password, BinaryStringEncoding.Utf8);
var keyDerivationProvider = KeyDerivationAlgorithmProvider.OpenAlgorithm("PBKDF2_SHA1");
KeyDerivationParameters pbkdf2Parms = KeyDerivationParameters.BuildForPbkdf2(iv, pwIterations);
// Create a key based on original key and derivation parameters.
CryptographicKey keyOriginal = keyDerivationProvider.CreateKey(pwBuffer);
IBuffer keyMaterial = CryptographicEngine.DeriveKeyMaterial(keyOriginal, pbkdf2Parms, keySize);
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);
var key = provider.CreateSymmetricKey(keyMaterial);
// Get the input stream as an IBuffer.
IBuffer msg;
using(var ms = new MemoryStream())
{
input.CopyTo(ms);
msg = ms.ToArray().AsBuffer();
}
var buffDecrypt = CryptographicEngine.Decrypt(key, msg, iv);
output.Write(buffDecrypt.ToArray(), 0, (int)buffDecrypt.Length);
#else
using (var alg = Aes.Create())
{
var thisIV = new byte[ivSize];
input.Read(thisIV, 0, ivSize);
alg.IV = thisIV;
var key = new Rfc2898DeriveBytes(password, alg.IV, pwIterations);
alg.Key = key.GetBytes(keySize);
using(var decryptor = alg.CreateDecryptor())
using(var cryptoStream = new CryptoStream(input, decryptor, CryptoStreamMode.Read))
CopyStream(cryptoStream, output, bufferSize);
}
#endif
output.Position = 0;
}
}
public class UnbufferedCryptoStream : MemoryStream
{
private readonly Stream stream;
private readonly bool isReadStream;
private string password;
private int bufferSize;
private EncryptionAlgorithm alg;
private bool disposed = false;
public UnbufferedCryptoStream(Stream stream, bool isReadStream, string password, int bufferSize, EncryptionAlgorithm alg) : base()
{
this.stream = stream;
this.isReadStream = isReadStream;
this.password = password;
this.bufferSize = bufferSize;
this.alg = alg;
if (isReadStream)
alg.Decrypt(stream, this, password, bufferSize);
}
protected override void Dispose(bool disposing)
{
if (disposed)
return;
disposed = true;
if (!isReadStream)
alg.Encrypt(this, stream, password, bufferSize);
stream.Dispose();
base.Dispose(disposing);
}
}
}
#endif

View File

@@ -0,0 +1,515 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;
using Convention.EasySave.Types;
using Convention.EasySave.Internal;
using System.Linq;
/// <summary>Represents a cached file which can be saved to and loaded from, and commited to storage when necessary.</summary>
public class EasySaveFile
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static Dictionary<string, EasySaveFile> cachedFiles = new Dictionary<string, EasySaveFile>();
public EasySaveSettings settings;
private Dictionary<string, EasySaveData> cache = new Dictionary<string, EasySaveData>();
private bool syncWithFile = false;
private DateTime timestamp = DateTime.UtcNow;
/// <summary>Creates a new EasySaveFile and loads the default file into the EasySaveFile if there is data to load.</summary>
public EasySaveFile() : this(new EasySaveSettings(), true) { }
/// <summary>Creates a new EasySaveFile and loads the specified file into the EasySaveFile if there is data to load.</summary>
/// <param name="filepath">The relative or absolute path of the file in storage our EasySaveFile is associated with.</param>
public EasySaveFile(string filePath) : this(new EasySaveSettings(filePath), true) { }
/// <summary>Creates a new EasySaveFile and loads the specified file into the EasySaveFile if there is data to load.</summary>
/// <param name="filepath">The relative or absolute path of the file in storage our EasySaveFile is associated with.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public EasySaveFile(string filePath, EasySaveSettings settings) : this(new EasySaveSettings(filePath, settings), true) { }
/// <summary>Creates a new EasySaveFile and loads the specified file into the EasySaveFile if there is data to load.</summary>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public EasySaveFile(EasySaveSettings settings) : this(settings, true) { }
/// <summary>Creates a new EasySaveFile and only loads the default file into it if syncWithFile is set to true.</summary>
/// <param name="syncWithFile">Whether we should sync this EasySaveFile with the one in storage immediately after creating it.</param>
public EasySaveFile(bool syncWithFile) : this(new EasySaveSettings(), syncWithFile) { }
/// <summary>Creates a new EasySaveFile and only loads the specified file into it if syncWithFile is set to true.</summary>
/// <param name="filepath">The relative or absolute path of the file in storage our EasySaveFile is associated with.</param>
/// <param name="syncWithFile">Whether we should sync this EasySaveFile with the one in storage immediately after creating it.</param>
public EasySaveFile(string filePath, bool syncWithFile) : this(new EasySaveSettings(filePath), syncWithFile) { }
/// <summary>Creates a new EasySaveFile and only loads the specified file into it if syncWithFile is set to true.</summary>
/// <param name="filepath">The relative or absolute path of the file in storage our EasySaveFile is associated with.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
/// <param name="syncWithFile">Whether we should sync this EasySaveFile with the one in storage immediately after creating it.</param>
public EasySaveFile(string filePath, EasySaveSettings settings, bool syncWithFile) : this(new EasySaveSettings(filePath, settings), syncWithFile) { }
/// <summary>Creates a new EasySaveFile and loads the specified file into the EasySaveFile if there is data to load.</summary>
/// <param name="settings">The settings we want to use to override the default settings.</param>
/// <param name="syncWithFile">Whether we should sync this EasySaveFile with the one in storage immediately after creating it.</param>
public EasySaveFile(EasySaveSettings settings, bool syncWithFile)
{
this.settings = settings;
this.syncWithFile = syncWithFile;
if (syncWithFile)
{
// Type checking must be enabled when syncing.
var settingsWithTypeChecking = (EasySaveSettings)settings.Clone();
settingsWithTypeChecking.typeChecking = true;
using (var reader = EasySaveReader.Create(settingsWithTypeChecking))
{
if (reader == null)
return;
foreach (KeyValuePair<string, EasySaveData> kvp in reader.RawEnumerator)
cache[kvp.Key] = kvp.Value;
}
timestamp = EasySave.GetTimestamp(settingsWithTypeChecking);
}
}
/// <summary>Creates a new EasySaveFile and loads the bytes into the EasySaveFile. Note the bytes must represent that of a file.</summary>
/// <param name="bytes">The bytes representing our file.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
/// <param name="syncWithFile">Whether we should sync this EasySaveFile with the one in storage immediately after creating it.</param>
public EasySaveFile(byte[] bytes, EasySaveSettings settings = null)
{
if (settings == null)
this.settings = new EasySaveSettings();
else
this.settings = settings;
syncWithFile = true; // This ensures that the file won't be merged, which would prevent deleted keys from being deleted.
SaveRaw(bytes, settings);
}
/// <summary>Synchronises this EasySaveFile with a file in storage.</summary>
public void Sync()
{
Sync(this.settings);
}
/// <summary>Synchronises this EasySaveFile with a file in storage.</summary>
/// <param name="filepath">The relative or absolute path of the file in storage we want to synchronise with.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public void Sync(string filePath, EasySaveSettings settings = null)
{
Sync(new EasySaveSettings(filePath, settings));
}
/// <summary>Synchronises this EasySaveFile with a file in storage.</summary>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public void Sync(EasySaveSettings settings = null)
{
if (settings == null)
settings = new EasySaveSettings();
if (cache.Count == 0)
{
EasySave.DeleteFile(settings);
return;
}
using (var baseWriter = EasySaveWriter.Create(settings, true, !syncWithFile, false))
{
foreach (var kvp in cache)
{
// If we change the name of a type, the type may be null.
// In this case, use System.Object as the type.
Type type;
if (kvp.Value.type == null)
type = typeof(System.Object);
else
type = kvp.Value.type.type;
baseWriter.Write(kvp.Key, type, kvp.Value.bytes);
}
baseWriter.Save(!syncWithFile);
}
}
/// <summary>Removes the data stored in this EasySaveFile. The EasySaveFile will be empty after calling this method.</summary>
public void Clear()
{
cache.Clear();
}
/// <summary>Returns an array of all of the key names in this EasySaveFile.</summary>
public string[] GetKeys()
{
var keyCollection = cache.Keys;
var keys = new string[keyCollection.Count];
keyCollection.CopyTo(keys, 0);
return keys;
}
#region Save Methods
/// <summary>Saves a value to a key in this EasySaveFile.</summary>
/// <param name="key">The key we want to use to identify our value in the file.</param>
/// <param name="value">The value we want to save.</param>
public void Save<T>(string key, T value)
{
var unencryptedSettings = (EasySaveSettings)settings.Clone();
unencryptedSettings.encryptionType = EasySave.EncryptionType.None;
unencryptedSettings.compressionType = EasySave.CompressionType.None;
// If T is object, use the value to get it's type. Otherwise, use T so that it works with inheritence.
Type type;
if (value == null)
type = typeof(T);
else
type = value.GetType();
cache[key] = new EasySaveData(EasySaveTypeMgr.GetOrCreateEasySaveType(type), EasySave.Serialize(value, unencryptedSettings));
}
/// <summary>Merges the data specified by the bytes parameter into this EasySaveFile.</summary>
/// <param name="bytes">The bytes we want to merge with this EasySaveFile.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public void SaveRaw(byte[] bytes, EasySaveSettings settings = null)
{
if (settings == null)
settings = new EasySaveSettings();
// Type checking must be enabled when syncing.
var settingsWithTypeChecking = (EasySaveSettings)settings.Clone();
settingsWithTypeChecking.typeChecking = true;
using (var reader = EasySaveReader.Create(bytes, settingsWithTypeChecking))
{
if (reader == null)
return;
foreach (KeyValuePair<string, EasySaveData> kvp in reader.RawEnumerator)
cache[kvp.Key] = kvp.Value;
}
}
/// <summary>Merges the data specified by the bytes parameter into this EasySaveFile.</summary>
/// <param name="bytes">The bytes we want to merge with this EasySaveFile.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public void AppendRaw(byte[] bytes, EasySaveSettings settings = null)
{
if (settings == null)
settings = new EasySaveSettings();
// AppendRaw just does the same thing as SaveRaw in EasySaveFile.
SaveRaw(bytes, settings);
}
#endregion
#region Load Methods
/* Standard load methods */
/// <summary>Loads the value from this EasySaveFile with the given key.</summary>
/// <param name="key">The key which identifies the value we want to load.</param>
public object Load(string key)
{
return Load<object>(key);
}
/// <summary>Loads the value from this EasySaveFile with the given key.</summary>
/// <param name="key">The key which identifies the value we want to load.</param>
/// <param name="defaultValue">The value we want to return if the key does not exist in this EasySaveFile.</param>
public object Load(string key, object defaultValue)
{
return Load<object>(key, defaultValue);
}
/// <summary>Loads the value from this EasySaveFile with the given key.</summary>
/// <param name="key">The key which identifies the value we want to load.</param>
public T Load<T>(string key)
{
EasySaveData es3Data;
if (!cache.TryGetValue(key, out es3Data))
throw new KeyNotFoundException("Key \"" + key + "\" was not found in this EasySaveFile. Use Load<T>(key, defaultValue) if you want to return a default value if the key does not exist.");
var unencryptedSettings = (EasySaveSettings)this.settings.Clone();
unencryptedSettings.encryptionType = EasySave.EncryptionType.None;
unencryptedSettings.compressionType = EasySave.CompressionType.None;
if (typeof(T) == typeof(object))
return (T)EasySave.Deserialize(es3Data.type, es3Data.bytes, unencryptedSettings);
return EasySave.Deserialize<T>(es3Data.bytes, unencryptedSettings);
}
/// <summary>Loads the value from this EasySaveFile with the given key.</summary>
/// <param name="key">The key which identifies the value we want to load.</param>
/// <param name="defaultValue">The value we want to return if the key does not exist in this EasySaveFile.</param>
public T Load<T>(string key, T defaultValue)
{
EasySaveData es3Data;
if (!cache.TryGetValue(key, out es3Data))
return defaultValue;
var unencryptedSettings = (EasySaveSettings)this.settings.Clone();
unencryptedSettings.encryptionType = EasySave.EncryptionType.None;
unencryptedSettings.compressionType = EasySave.CompressionType.None;
if (typeof(T) == typeof(object))
return (T)EasySave.Deserialize(es3Data.type, es3Data.bytes, unencryptedSettings);
return EasySave.Deserialize<T>(es3Data.bytes, unencryptedSettings);
}
/// <summary>Loads the value from this EasySaveFile with the given key into an existing object.</summary>
/// <param name="key">The key which identifies the value we want to load.</param>
/// <param name="obj">The object we want to load the value into.</param>
public void LoadInto<T>(string key, T obj) where T : class
{
EasySaveData es3Data;
if (!cache.TryGetValue(key, out es3Data))
throw new KeyNotFoundException("Key \"" + key + "\" was not found in this EasySaveFile. Use Load<T>(key, defaultValue) if you want to return a default value if the key does not exist.");
var unencryptedSettings = (EasySaveSettings)this.settings.Clone();
unencryptedSettings.encryptionType = EasySave.EncryptionType.None;
unencryptedSettings.compressionType = EasySave.CompressionType.None;
if (typeof(T) == typeof(object))
EasySave.DeserializeInto(es3Data.type, es3Data.bytes, obj, unencryptedSettings);
else
EasySave.DeserializeInto(es3Data.bytes, obj, unencryptedSettings);
}
#endregion
#region Load Raw Methods
/// <summary>Loads the EasySaveFile as a raw, unencrypted, uncompressed byte array.</summary>
public byte[] LoadRawBytes()
{
var newSettings = (EasySaveSettings)settings.Clone();
if (!newSettings.postprocessRawCachedData)
{
newSettings.encryptionType = EasySave.EncryptionType.None;
newSettings.compressionType = EasySave.CompressionType.None;
}
return GetBytes(newSettings);
}
/// <summary>Loads the EasySaveFile as a raw, unencrypted, uncompressed string, using the encoding defined in the EasySaveFile's settings variable.</summary>
public string LoadRawString()
{
if (cache.Count == 0)
return "";
return settings.encoding.GetString(LoadRawBytes());
}
/*
* Same as LoadRawString, except it will return an encrypted/compressed file if these are enabled.
*/
internal byte[] GetBytes(EasySaveSettings settings = null)
{
if (cache.Count == 0)
return new byte[0];
if (settings == null)
settings = this.settings;
using (var ms = new System.IO.MemoryStream())
{
var memorySettings = (EasySaveSettings)settings.Clone();
memorySettings.location = EasySave.Location.InternalMS;
// Ensure we return unencrypted bytes.
if (!memorySettings.postprocessRawCachedData)
{
memorySettings.encryptionType = EasySave.EncryptionType.None;
memorySettings.compressionType = EasySave.CompressionType.None;
}
using (var baseWriter = EasySaveWriter.Create(EasySaveStream.CreateStream(ms, memorySettings, EasySaveFileMode.Write), memorySettings, true, false))
{
foreach (var kvp in cache)
baseWriter.Write(kvp.Key, kvp.Value.type.type, kvp.Value.bytes);
baseWriter.Save(false);
}
return ms.ToArray();
}
}
#endregion
#region Other EasySave Methods
/// <summary>Deletes a key from this EasySaveFile.</summary>
/// <param name="key">The key we want to delete.</param>
public void DeleteKey(string key)
{
cache.Remove(key);
}
/// <summary>Checks whether a key exists in this EasySaveFile.</summary>
/// <param name="key">The key we want to check the existence of.</param>
/// <returns>True if the key exists, otherwise False.</returns>
public bool KeyExists(string key)
{
return cache.ContainsKey(key);
}
/// <summary>Gets the size of the cached data in bytes.</summary>
public int Size()
{
int size = 0;
foreach (var kvp in cache)
size += kvp.Value.bytes.Length;
return size;
}
public Type GetKeyType(string key)
{
EasySaveData es3data;
if (!cache.TryGetValue(key, out es3data))
throw new KeyNotFoundException("Key \"" + key + "\" was not found in this EasySaveFile. Use Load<T>(key, defaultValue) if you want to return a default value if the key does not exist.");
return es3data.type.type;
}
#endregion
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static EasySaveFile GetOrCreateCachedFile(EasySaveSettings settings)
{
EasySaveFile cachedFile;
if (!cachedFiles.TryGetValue(settings.path, out cachedFile))
{
cachedFile = new EasySaveFile(settings, false);
cachedFiles.Add(settings.path, cachedFile);
cachedFile.syncWithFile = true; // This ensures that the file won't be merged, which would prevent deleted keys from being deleted.
}
// Settings might refer to the same file, but might have changed.
// To account for this, we update the settings of the EasySaveFile each time we access it.
cachedFile.settings = settings;
return cachedFile;
}
internal static void CacheFile(EasySaveSettings settings)
{
// If we're still using cached settings, set it to the default location.
if (settings.location == EasySave.Location.Cache)
{
settings = (EasySaveSettings)settings.Clone();
// If the default settings are also set to cache, assume EasySave.Location.File. Otherwise, set it to the default location.
settings.location = EasySaveSettings.defaultSettings.location == EasySave.Location.Cache ? EasySave.Location.File : EasySaveSettings.defaultSettings.location;
}
if (!EasySave.FileExists(settings))
return;
// Disable compression and encryption when loading the raw bytes, and the EasySaveFile constructor will expect encrypted/compressed bytes.
var loadSettings = (EasySaveSettings)settings.Clone();
loadSettings.compressionType = EasySave.CompressionType.None;
loadSettings.encryptionType = EasySave.EncryptionType.None;
cachedFiles[settings.path] = new EasySaveFile(EasySave.LoadRawBytes(loadSettings), settings);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static void Store(EasySaveSettings settings = null)
{
if (settings == null)
settings = new EasySaveSettings(EasySave.Location.File);
// If we're still using cached settings, set it to the default location.
else if (settings.location == EasySave.Location.Cache)
{
settings = (EasySaveSettings)settings.Clone();
// If the default settings are also set to cache, assume EasySave.Location.File. Otherwise, set it to the default location.
settings.location = EasySaveSettings.defaultSettings.location == EasySave.Location.Cache ? EasySave.Location.File : EasySaveSettings.defaultSettings.location;
}
EasySaveFile cachedFile;
if (!cachedFiles.TryGetValue(settings.path, out cachedFile))
throw new FileNotFoundException("The file '" + settings.path + "' could not be stored because it could not be found in the cache.");
cachedFile.Sync(settings);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static void RemoveCachedFile(EasySaveSettings settings)
{
cachedFiles.Remove(settings.path);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static void CopyCachedFile(EasySaveSettings oldSettings, EasySaveSettings newSettings)
{
EasySaveFile cachedFile;
if (!cachedFiles.TryGetValue(oldSettings.path, out cachedFile))
throw new FileNotFoundException("The file '" + oldSettings.path + "' could not be copied because it could not be found in the cache.");
if (cachedFiles.ContainsKey(newSettings.path))
throw new InvalidOperationException("Cannot copy file '" + oldSettings.path + "' to '" + newSettings.path + "' because '" + newSettings.path + "' already exists");
cachedFiles.Add(newSettings.path, (EasySaveFile)cachedFile.MemberwiseClone());
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static void DeleteKey(string key, EasySaveSettings settings)
{
EasySaveFile cachedFile;
if (cachedFiles.TryGetValue(settings.path, out cachedFile))
cachedFile.DeleteKey(key);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static bool KeyExists(string key, EasySaveSettings settings)
{
EasySaveFile cachedFile;
if (cachedFiles.TryGetValue(settings.path, out cachedFile))
return cachedFile.KeyExists(key);
return false;
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static bool FileExists(EasySaveSettings settings)
{
return cachedFiles.ContainsKey(settings.path);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static string[] GetKeys(EasySaveSettings settings)
{
EasySaveFile cachedFile;
if (!cachedFiles.TryGetValue(settings.path, out cachedFile))
throw new FileNotFoundException("Could not get keys from the file '" + settings.path + "' because it could not be found in the cache.");
return cachedFile.cache.Keys.ToArray();
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal static string[] GetFiles()
{
return cachedFiles.Keys.ToArray();
}
internal static DateTime GetTimestamp(EasySaveSettings settings)
{
EasySaveFile cachedFile;
if (!cachedFiles.TryGetValue(settings.path, out cachedFile))
return new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
return cachedFile.timestamp;
}
}
namespace Convention.EasySave.Internal
{
public struct EasySaveData
{
public EasySaveType type;
public byte[] bytes;
public EasySaveData(Type type, byte[] bytes)
{
this.type = type == null ? null : EasySaveTypeMgr.GetOrCreateEasySaveType(type);
this.bytes = bytes;
}
public EasySaveData(EasySaveType type, byte[] bytes)
{
this.type = type;
this.bytes = bytes;
}
}
}

View File

@@ -0,0 +1,165 @@
using System.IO;
using System;
using Convention;
namespace Convention.EasySave.Internal
{
public static class EasySaveIO
{
internal static string persistentDataPath => PlatformIndicator.PersistentDataPath;
internal static string dataPath => PlatformIndicator.DataPath;
internal const string backupFileSuffix = ".bac";
internal const string temporaryFileSuffix = ".tmp";
public enum EasySaveFileMode { Read, Write, Append }
public static DateTime GetTimestamp(string filePath)
{
if (!FileExists(filePath))
return new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
return File.GetLastWriteTime(filePath).ToUniversalTime();
}
public static string GetExtension(string path)
{
return Path.GetExtension(path);
}
public static void DeleteFile(string filePath)
{
if (FileExists(filePath))
File.Delete(filePath);
}
public static bool FileExists(string filePath) { return File.Exists(filePath); }
public static void MoveFile(string sourcePath, string destPath) { File.Move(sourcePath, destPath); }
public static void CopyFile(string sourcePath, string destPath) { File.Copy(sourcePath, destPath); }
public static void MoveDirectory(string sourcePath, string destPath) { Directory.Move(sourcePath, destPath); }
public static void CreateDirectory(string directoryPath) { Directory.CreateDirectory(directoryPath); }
public static bool DirectoryExists(string directoryPath) { return Directory.Exists(directoryPath); }
/*
* Given a path, it returns the directory that path points to.
* eg. "C:/myFolder/thisFolder/myFile.txt" will return "C:/myFolder/thisFolder".
*/
public static string GetDirectoryPath(string path, char seperator = '/')
{
//return Path.GetDirectoryName(path);
// Path.GetDirectoryName turns forward slashes to backslashes in some cases on Windows, which is why
// Substring is used instead.
char slashChar = UsesForwardSlash(path) ? '/' : '\\';
int slash = path.LastIndexOf(slashChar);
// If this path ends in a slash it is assumed to already be a path to a Directory.
if (slash == path.Length - 1)
return path;
// Ignore trailing slash if necessary.
if (slash == (path.Length - 1))
slash = path.Substring(0, slash).LastIndexOf(slashChar);
if (slash == -1)
throw new IOException("Path provided is not a directory path as it contains no slashes.");
return path[..slash];
}
public static bool UsesForwardSlash(string path)
{
if (path.Contains("/"))
return true;
return false;
}
// Takes a directory path and a file or directory name and combines them into a single path.
public static string CombinePathAndFilename(string directoryPath, string fileOrDirectoryName)
{
if (directoryPath[directoryPath.Length - 1] != '/' && directoryPath[directoryPath.Length - 1] != '\\')
directoryPath += '/';
return directoryPath + fileOrDirectoryName;
}
public static string[] GetDirectories(string path, bool getFullPaths = true)
{
var paths = Directory.GetDirectories(path);
for (int i = 0; i < paths.Length; i++)
{
if (!getFullPaths)
paths[i] = Path.GetFileName(paths[i]);
// GetDirectories sometimes returns backslashes, so we need to convert them to
// forward slashes.
paths[i].Replace("\\", "/");
}
return paths;
}
public static void DeleteDirectory(string directoryPath)
{
if (DirectoryExists(directoryPath))
Directory.Delete(directoryPath, true);
}
// Note: Paths not ending in a slash are assumed to be a path to a file.
// In this case the Directory containing the file will be searched.
public static string[] GetFiles(string path, bool getFullPaths = true)
{
var paths = Directory.GetFiles(GetDirectoryPath(path));
if (!getFullPaths)
{
for (int i = 0; i < paths.Length; i++)
paths[i] = Path.GetFileName(paths[i]);
}
return paths;
}
public static byte[] ReadAllBytes(string path)
{
return File.ReadAllBytes(path);
}
public static void WriteAllBytes(string path, byte[] bytes)
{
File.WriteAllBytes(path, bytes);
}
public static void CommitBackup(EasySaveSettings settings)
{
var temporaryFilePath = settings.FullPath + temporaryFileSuffix;
if (settings.location == EasySave.Location.File)
{
var oldFileBackup = settings.FullPath + temporaryFileSuffix + ".bak";
// If there's existing save data to overwrite ...
if (FileExists(settings.FullPath))
{
// Delete any old backups.
DeleteFile(oldFileBackup);
// Rename the old file so we can restore it if it fails.
CopyFile(settings.FullPath, oldFileBackup);
try
{
// Delete the old file so that we can move it.
DeleteFile(settings.FullPath);
// Now rename the temporary file to the name of the save file.
MoveFile(temporaryFilePath, settings.FullPath);
}
catch (Exception)
{
// If any exceptions occur, restore the original save file.
try { DeleteFile(settings.FullPath); } catch { }
MoveFile(oldFileBackup, settings.FullPath);
throw;
}
DeleteFile(oldFileBackup);
}
// Else just rename the temporary file to the main file.
else
MoveFile(temporaryFilePath, settings.FullPath);
}
}
}
}

View File

@@ -0,0 +1,725 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Convention.EasySave.Internal
{
public static class EasySaveReflection
{
public const string memberFieldPrefix = "m_";
public const string componentTagFieldName = "tag";
public const string componentNameFieldName = "name";
public static readonly string[] excludedPropertyNames = new string[] { "runInEditMode", "useGUILayout", "hideFlags" };
public static readonly Type serializableAttributeType = typeof(System.SerializableAttribute);
public static readonly Type obsoleteAttributeType = typeof(System.ObsoleteAttribute);
public static readonly Type nonSerializedAttributeType = typeof(System.NonSerializedAttribute);
public static readonly Type es3SerializableAttributeType = typeof(EasySaved);
public static readonly Type es3NonSerializableAttributeType = typeof(EasySaveIgnored);
public static Type[] EmptyTypes = new Type[0];
private static Assembly[] _assemblies = null;
private static Assembly[] Assemblies
{
get
{
if (_assemblies == null)
{
var assemblyNames = new EasySaveSettings().assemblyNames;
var assemblyList = new List<Assembly>();
/* We only use a try/catch block for UWP because exceptions can be disabled on some other platforms (e.g. WebGL), but the non-try/catch method doesn't work on UWP */
#if NETFX_CORE
for (int i = 0; i < assemblyNames.Length; i++)
{
try
{
var assembly = Assembly.Load(new AssemblyName(assemblyNames[i]));
if (assembly != null)
assemblyList.Add(assembly);
}
catch { }
}
#else
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
// This try/catch block is here to catch errors such as assemblies containing double-byte characters in their path.
// This obviously won't work if exceptions are disabled.
try
{
if (assemblyNames.Contains(assembly.GetName().Name))
assemblyList.Add(assembly);
}
catch { }
}
#endif
_assemblies = assemblyList.ToArray();
}
return _assemblies;
}
}
/*
* Gets the element type of a collection or array.
* Returns null if type is not a collection type.
*/
public static Type[] GetElementTypes(Type type)
{
if (IsGenericType(type))
return EasySaveReflection.GetGenericArguments(type);
else if (type.IsArray)
return new Type[] { EasySaveReflection.GetElementType(type) };
else
return null;
}
public static List<FieldInfo> GetSerializableFields(Type type, List<FieldInfo> serializableFields = null, bool safe = true, string[] memberNames = null, BindingFlags bindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
{
if (type == null)
return new List<FieldInfo>();
var fields = type.GetFields(bindings);
if (serializableFields == null)
serializableFields = new List<FieldInfo>();
foreach (var field in fields)
{
var fieldName = field.Name;
// If a members array was provided as a parameter, only include the field if it's in the array.
if (memberNames != null)
if (!memberNames.Contains(fieldName))
continue;
var fieldType = field.FieldType;
if (AttributeIsDefined(field, es3SerializableAttributeType))
{
serializableFields.Add(field);
continue;
}
if (AttributeIsDefined(field, es3NonSerializableAttributeType))
continue;
if (safe)
{
// If the field is private, only serialize it if it's explicitly marked as serializable.
if (!field.IsPublic)
continue;
}
// Exclude const or readonly fields.
if (field.IsLiteral || field.IsInitOnly)
continue;
// Don't store fields whose type is the same as the class the field is housed in unless it's stored by reference (to prevent cyclic references)
if (fieldType == type)
continue;
// If property is marked as obsolete or non-serialized, don't serialize it.
if (AttributeIsDefined(field, nonSerializedAttributeType) || AttributeIsDefined(field, obsoleteAttributeType))
continue;
if (!TypeIsSerializable(field.FieldType))
continue;
// Don't serialize member fields.
if (safe && fieldName.StartsWith(memberFieldPrefix) && field.DeclaringType.Namespace != null && field.DeclaringType.Namespace.Contains("UnityEngine"))
continue;
serializableFields.Add(field);
}
var baseType = BaseType(type);
if (baseType != null && baseType != typeof(System.Object))
GetSerializableFields(BaseType(type), serializableFields, safe, memberNames);
return serializableFields;
}
public static List<PropertyInfo> GetSerializableProperties(Type type, List<PropertyInfo> serializableProperties = null, bool safe = true, string[] memberNames = null, BindingFlags bindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
{
// Only get private properties if we're not getting properties safely.
if (!safe)
bindings = bindings | BindingFlags.NonPublic;
var properties = type.GetProperties(bindings);
if (serializableProperties == null)
serializableProperties = new List<PropertyInfo>();
foreach (var p in properties)
{
if (AttributeIsDefined(p, es3SerializableAttributeType))
{
serializableProperties.Add(p);
continue;
}
if (AttributeIsDefined(p, es3NonSerializableAttributeType))
continue;
var propertyName = p.Name;
if (excludedPropertyNames.Contains(propertyName))
continue;
// If a members array was provided as a parameter, only include the property if it's in the array.
if (memberNames != null)
if (!memberNames.Contains(propertyName))
continue;
if (safe)
{
// If safe serialization is enabled, only get properties which are explicitly marked as serializable.
if (!AttributeIsDefined(p, es3SerializableAttributeType))
continue;
}
var propertyType = p.PropertyType;
// Don't store properties whose type is the same as the class the property is housed in unless it's stored by reference (to prevent cyclic references)
if (propertyType == type)
continue;
if (!p.CanRead || !p.CanWrite)
continue;
// Only support properties with indexing if they're an array.
if (p.GetIndexParameters().Length != 0 && !propertyType.IsArray)
continue;
// Check that the type of the property is one which we can serialize.
// Also check whether an EasySaveType exists for it.
if (!TypeIsSerializable(propertyType))
continue;
// If property is marked as obsolete or non-serialized, don't serialize it.
if (AttributeIsDefined(p, obsoleteAttributeType) || AttributeIsDefined(p, nonSerializedAttributeType))
continue;
serializableProperties.Add(p);
}
var baseType = BaseType(type);
if (baseType != null && baseType != typeof(System.Object))
GetSerializableProperties(baseType, serializableProperties, safe, memberNames);
return serializableProperties;
}
public static bool TypeIsSerializable(Type type)
{
if (type == null)
return false;
if (AttributeIsDefined(type, es3NonSerializableAttributeType))
return false;
if (IsPrimitive(type) || IsValueType(type))
return true;
var es3Type = EasySaveTypeMgr.GetOrCreateEasySaveType(type, false);
if (es3Type != null && !es3Type.isUnsupported)
return true;
if (TypeIsArray(type))
{
if (TypeIsSerializable(type.GetElementType()))
return true;
return false;
}
var genericArgs = type.GetGenericArguments();
for (int i = 0; i < genericArgs.Length; i++)
if (!TypeIsSerializable(genericArgs[i]))
return false;
/*if (HasParameterlessConstructor(type))
return true;*/
return false;
}
public static System.Object CreateInstance(Type type)
{
if (EasySaveReflection.HasParameterlessConstructor(type))
return Activator.CreateInstance(type);
else
{
#if NETFX_CORE
throw new NotSupportedException($"Cannot create an instance of {type} because it does not have a parameterless constructor, which is required on Universal Windows platform.");
#else
return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
#endif
}
}
public static System.Object CreateInstance(Type type, params object[] args)
{
return Activator.CreateInstance(type, args);
}
public static Array ArrayCreateInstance(Type type, int length)
{
return Array.CreateInstance(type, new int[] { length });
}
public static Array ArrayCreateInstance(Type type, int[] dimensions)
{
return Array.CreateInstance(type, dimensions);
}
public static Type MakeGenericType(Type type, Type genericParam)
{
return type.MakeGenericType(genericParam);
}
public static EasySaveReflectedMember[] GetSerializableMembers(Type type, bool safe = true, string[] memberNames = null)
{
if (type == null)
return new EasySaveReflectedMember[0];
var fieldInfos = GetSerializableFields(type, new List<FieldInfo>(), safe, memberNames);
var propertyInfos = GetSerializableProperties(type, new List<PropertyInfo>(), safe, memberNames);
var reflectedFields = new EasySaveReflectedMember[fieldInfos.Count + propertyInfos.Count];
for (int i = 0; i < fieldInfos.Count; i++)
reflectedFields[i] = new EasySaveReflectedMember(fieldInfos[i]);
for (int i = 0; i < propertyInfos.Count; i++)
reflectedFields[i + fieldInfos.Count] = new EasySaveReflectedMember(propertyInfos[i]);
return reflectedFields;
}
public static EasySaveReflectedMember GetEasySaveReflectedProperty(Type type, string propertyName)
{
var propertyInfo = EasySaveReflection.GetProperty(type, propertyName);
return new EasySaveReflectedMember(propertyInfo);
}
public static EasySaveReflectedMember GetEasySaveReflectedMember(Type type, string fieldName)
{
var fieldInfo = EasySaveReflection.GetField(type, fieldName);
return new EasySaveReflectedMember(fieldInfo);
}
/*
* Finds all classes of a specific type, and then returns an instance of each.
* Ignores classes which can't be instantiated (i.e. abstract classes, those without parameterless constructors).
*/
public static IList<T> GetInstances<T>()
{
var instances = new List<T>();
foreach (var assembly in Assemblies)
foreach (var type in assembly.GetTypes())
if (IsAssignableFrom(typeof(T), type) && EasySaveReflection.HasParameterlessConstructor(type) && !EasySaveReflection.IsAbstract(type))
instances.Add((T)Activator.CreateInstance(type));
return instances;
}
public static IList<Type> GetDerivedTypes(Type derivedType)
{
return
(
from assembly in Assemblies
from type in assembly.GetTypes()
where IsAssignableFrom(derivedType, type)
select type
).ToList();
}
public static bool IsAssignableFrom(Type a, Type b)
{
return a.IsAssignableFrom(b);
}
public static Type GetGenericTypeDefinition(Type type)
{
return type.GetGenericTypeDefinition();
}
public static Type[] GetGenericArguments(Type type)
{
return type.GetGenericArguments();
}
public static int GetArrayRank(Type type)
{
return type.GetArrayRank();
}
public static string GetAssemblyQualifiedName(Type type)
{
return type.AssemblyQualifiedName;
}
public static EasySaveReflectedMethod GetMethod(Type type, string methodName, Type[] genericParameters, Type[] parameterTypes)
{
return new EasySaveReflectedMethod(type, methodName, genericParameters, parameterTypes);
}
public static bool TypeIsArray(Type type)
{
return type.IsArray;
}
public static Type GetElementType(Type type)
{
return type.GetElementType();
}
#if NETFX_CORE
public static bool IsAbstract(Type type)
{
return type.GetTypeInfo().IsAbstract;
}
public static bool IsInterface(Type type)
{
return type.GetTypeInfo().IsInterface;
}
public static bool IsGenericType(Type type)
{
return type.GetTypeInfo().IsGenericType;
}
public static bool IsValueType(Type type)
{
return type.GetTypeInfo().IsValueType;
}
public static bool IsEnum(Type type)
{
return type.GetTypeInfo().IsEnum;
}
public static bool HasParameterlessConstructor(Type type)
{
return GetParameterlessConstructor(type) != null;
}
public static ConstructorInfo GetParameterlessConstructor(Type type)
{
foreach (var cInfo in type.GetTypeInfo().DeclaredConstructors)
if (!cInfo.IsStatic && cInfo.GetParameters().Length == 0)
return cInfo;
return null;
}
public static string GetShortAssemblyQualifiedName(Type type)
{
if (IsPrimitive (type))
return type.ToString ();
return type.FullName + "," + type.GetTypeInfo().Assembly.GetName().Name;
}
public static PropertyInfo GetProperty(Type type, string propertyName)
{
var property = type.GetTypeInfo().GetDeclaredProperty(propertyName);
if (property == null && type.BaseType != typeof(object))
return GetProperty(type.BaseType, propertyName);
return property;
}
public static FieldInfo GetField(Type type, string fieldName)
{
return type.GetTypeInfo().GetDeclaredField(fieldName);
}
public static MethodInfo[] GetMethods(Type type, string methodName)
{
return type.GetTypeInfo().GetDeclaredMethods(methodName);
}
public static bool IsPrimitive(Type type)
{
return (type.GetTypeInfo().IsPrimitive || type == typeof(string) || type == typeof(decimal));
}
public static bool AttributeIsDefined(MemberInfo info, Type attributeType)
{
var attributes = info.GetCustomAttributes(attributeType, true);
foreach(var attribute in attributes)
return true;
return false;
}
public static bool AttributeIsDefined(Type type, Type attributeType)
{
var attributes = type.GetTypeInfo().GetCustomAttributes(attributeType, true);
foreach(var attribute in attributes)
return true;
return false;
}
public static bool ImplementsInterface(Type type, Type interfaceType)
{
return type.GetTypeInfo().ImplementedInterfaces.Contains(interfaceType);
}
public static Type BaseType(Type type)
{
return type.GetTypeInfo().BaseType;
}
#else
public static bool IsAbstract(Type type)
{
return type.IsAbstract;
}
public static bool IsInterface(Type type)
{
return type.IsInterface;
}
public static bool IsGenericType(Type type)
{
return type.IsGenericType;
}
public static bool IsValueType(Type type)
{
return type.IsValueType;
}
public static bool IsEnum(Type type)
{
return type.IsEnum;
}
public static bool HasParameterlessConstructor(Type type)
{
if (IsValueType(type) || GetParameterlessConstructor(type) != null)
return true;
return false;
}
public static ConstructorInfo GetParameterlessConstructor(Type type)
{
var constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var constructor in constructors)
if (constructor.GetParameters().Length == 0)
return constructor;
return null;
}
public static string GetShortAssemblyQualifiedName(Type type)
{
if (IsPrimitive(type))
return type.ToString();
return type.FullName + "," + type.Assembly.GetName().Name;
}
public static PropertyInfo GetProperty(Type type, string propertyName)
{
var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (property == null && BaseType(type) != typeof(object))
return GetProperty(BaseType(type), propertyName);
return property;
}
public static FieldInfo GetField(Type type, string fieldName)
{
var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null && BaseType(type) != typeof(object))
return GetField(BaseType(type), fieldName);
return field;
}
public static MethodInfo[] GetMethods(Type type, string methodName)
{
return type.GetMethods().Where(t => t.Name == methodName).ToArray();
}
public static bool IsPrimitive(Type type)
{
return (type.IsPrimitive || type == typeof(string) || type == typeof(decimal));
}
public static bool AttributeIsDefined(MemberInfo info, Type attributeType)
{
return Attribute.IsDefined(info, attributeType, true);
}
public static bool AttributeIsDefined(Type type, Type attributeType)
{
return type.IsDefined(attributeType, true);
}
public static bool ImplementsInterface(Type type, Type interfaceType)
{
return (type.GetInterface(interfaceType.Name) != null);
}
public static Type BaseType(Type type)
{
return type.BaseType;
}
public static Type GetType(string typeString)
{
switch (typeString)
{
case "bool":
return typeof(bool);
case "byte":
return typeof(byte);
case "sbyte":
return typeof(sbyte);
case "char":
return typeof(char);
case "decimal":
return typeof(decimal);
case "double":
return typeof(double);
case "float":
return typeof(float);
case "int":
return typeof(int);
case "uint":
return typeof(uint);
case "long":
return typeof(long);
case "ulong":
return typeof(ulong);
case "short":
return typeof(short);
case "ushort":
return typeof(ushort);
case "string":
return typeof(string);
case "System.Object":
return typeof(object);
default:
return Type.GetType(typeString);
}
}
public static string GetTypeString(Type type)
{
if (type == typeof(bool))
return "bool";
else if (type == typeof(byte))
return "byte";
else if (type == typeof(sbyte))
return "sbyte";
else if (type == typeof(char))
return "char";
else if (type == typeof(decimal))
return "decimal";
else if (type == typeof(double))
return "double";
else if (type == typeof(float))
return "float";
else if (type == typeof(int))
return "int";
else if (type == typeof(uint))
return "uint";
else if (type == typeof(long))
return "long";
else if (type == typeof(ulong))
return "ulong";
else if (type == typeof(short))
return "short";
else if (type == typeof(ushort))
return "ushort";
else if (type == typeof(string))
return "string";
else if (type == typeof(object))
return "System.Object";
else
return GetShortAssemblyQualifiedName(type);
}
#endif
/*
* Allows us to use FieldInfo and PropertyInfo interchangably.
*/
public struct EasySaveReflectedMember
{
// The FieldInfo or PropertyInfo for this field.
private FieldInfo fieldInfo;
private PropertyInfo propertyInfo;
public bool isProperty;
public bool IsNull { get { return fieldInfo == null && propertyInfo == null; } }
public string Name { get { return (isProperty ? propertyInfo.Name : fieldInfo.Name); } }
public Type MemberType { get { return (isProperty ? propertyInfo.PropertyType : fieldInfo.FieldType); } }
public bool IsPublic { get { return (isProperty ? (propertyInfo.GetGetMethod(true).IsPublic && propertyInfo.GetSetMethod(true).IsPublic) : fieldInfo.IsPublic); } }
public bool IsProtected { get { return (isProperty ? (propertyInfo.GetGetMethod(true).IsFamily) : fieldInfo.IsFamily); } }
public bool IsStatic { get { return (isProperty ? (propertyInfo.GetGetMethod(true).IsStatic) : fieldInfo.IsStatic); } }
public EasySaveReflectedMember(System.Object fieldPropertyInfo)
{
if (fieldPropertyInfo == null)
{
this.propertyInfo = null;
this.fieldInfo = null;
isProperty = false;
return;
}
isProperty = EasySaveReflection.IsAssignableFrom(typeof(PropertyInfo), fieldPropertyInfo.GetType());
if (isProperty)
{
this.propertyInfo = (PropertyInfo)fieldPropertyInfo;
this.fieldInfo = null;
}
else
{
this.fieldInfo = (FieldInfo)fieldPropertyInfo;
this.propertyInfo = null;
}
}
public void SetValue(System.Object obj, System.Object value)
{
if (isProperty)
propertyInfo.SetValue(obj, value, null);
else
fieldInfo.SetValue(obj, value);
}
public System.Object GetValue(System.Object obj)
{
if (isProperty)
return propertyInfo.GetValue(obj, null);
else
return fieldInfo.GetValue(obj);
}
}
public class EasySaveReflectedMethod
{
private MethodInfo method;
public EasySaveReflectedMethod(Type type, string methodName, Type[] genericParameters, Type[] parameterTypes)
{
MethodInfo nonGenericMethod = type.GetMethod(methodName, parameterTypes);
this.method = nonGenericMethod.MakeGenericMethod(genericParameters);
}
public EasySaveReflectedMethod(Type type, string methodName, Type[] genericParameters, Type[] parameterTypes, BindingFlags bindingAttr)
{
MethodInfo nonGenericMethod = type.GetMethod(methodName, bindingAttr, null, parameterTypes, null);
this.method = nonGenericMethod.MakeGenericMethod(genericParameters);
}
public object Invoke(object obj, object[] parameters = null)
{
return method.Invoke(obj, parameters);
}
}
}
}

View File

@@ -0,0 +1,301 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Convention.EasySave.Internal;
public class EasySaveSpreadsheet
{
private int cols = 0;
private int rows = 0;
private Dictionary<Index, string> cells = new Dictionary<Index, string>();
private const string QUOTE = "\"";
private const char QUOTE_CHAR = '"';
private const char COMMA_CHAR = ',';
private const char NEWLINE_CHAR = '\n';
private const string ESCAPED_QUOTE = "\"\"";
private static char[] CHARS_TO_ESCAPE = { ',', '"', '\n', ' ' };
public int ColumnCount
{
get{ return cols; }
}
public int RowCount
{
get{ return rows; }
}
public int GetColumnLength(int col)
{
if (col >= cols)
return 0;
int maxRow = -1;
foreach(var index in cells.Keys)
if (index.col == col && index.row > maxRow)
maxRow = index.row;
return maxRow+1;
}
public int GetRowLength(int row)
{
if (row >= rows)
return 0;
int maxCol = -1;
foreach (var index in cells.Keys)
if (index.row == row && index.col > maxCol)
maxCol = index.col;
return maxCol + 1;
}
public void SetCell(int col, int row, object value)
{
var type = value.GetType();
// If we're writing a string, add it without formatting.
if (type == typeof(string))
{
SetCellString(col, row, (string)value);
return;
}
var settings = new EasySaveSettings();
if (EasySaveReflection.IsPrimitive(type))
SetCellString(col, row, value.ToString());
else
SetCellString(col, row, settings.encoding.GetString(EasySave.Serialize(value, EasySaveTypeMgr.GetOrCreateEasySaveType(type))));
// Expand the spreadsheet if necessary.
if (col >= cols)
cols = (col + 1);
if (row >= rows)
rows = (row + 1);
}
private void SetCellString(int col, int row, string value)
{
cells [new Index (col, row)] = value;
// Expand the spreadsheet if necessary.
if(col >= cols)
cols = (col+1);
if (row >= rows)
rows = (row + 1);
}
// Don't create non-generic version of this. Generic parameter is necessary as no type data is stored in the CSV file.
public T GetCell<T>(int col, int row)
{
var val = GetCell(typeof(T), col, row);
if (val == null)
return default(T);
return (T)val;
}
public object GetCell(System.Type type, int col, int row)
{
string value;
if (col >= cols || row >= rows)
throw new System.IndexOutOfRangeException("Cell (" + col + ", " + row + ") is out of bounds of spreadsheet (" + cols + ", " + rows + ").");
if (!cells.TryGetValue(new Index(col, row), out value) || value == null)
return null;
// If we're loading a string, simply return the string value.
if (type == typeof(string))
{
var str = (object)value;
return str;
}
var settings = new EasySaveSettings();
return EasySave.Deserialize(EasySaveTypeMgr.GetOrCreateEasySaveType(type, true), settings.encoding.GetBytes(value), settings);
}
public void Load(string filePath)
{
Load(new EasySaveSettings (filePath));
}
public void Load(string filePath, EasySaveSettings settings)
{
Load(new EasySaveSettings (filePath, settings));
}
public void Load(EasySaveSettings settings)
{
Load(EasySaveStream.CreateStream(settings, EasySaveFileMode.Read), settings);
}
public void LoadRaw(string str)
{
Load(new MemoryStream (((new EasySaveSettings ()).encoding).GetBytes(str)), new EasySaveSettings());
}
public void LoadRaw(string str, EasySaveSettings settings)
{
Load(new MemoryStream ((settings.encoding).GetBytes(str)), settings);
}
private void Load(Stream stream, EasySaveSettings settings)
{
using (var reader = new StreamReader(stream))
{
int c_int;
char c;
string value = "";
int col = 0;
int row = 0;
// Read until the end of the stream.
while(true)
{
c_int = reader.Read();
c = (char)c_int;
if(c == QUOTE_CHAR)
{
while (true)
{
c = (char)reader.Read();
if(c == QUOTE_CHAR)
{
// If this quote isn't escaped by another, it is the last quote, so we should stop parsing this value.
if(((char)reader.Peek()) != QUOTE_CHAR)
break;
else
c = (char)reader.Read();
}
value += c;
}
}
// If this is the end of a column, row, or the stream, add the value to the spreadsheet.
else if(c == COMMA_CHAR || c == NEWLINE_CHAR || c_int == -1)
{
SetCell(col, row, value);
value = "";
if(c == COMMA_CHAR)
col++;
else if(c == NEWLINE_CHAR)
{
col = 0;
row++;
}
else
break;
}
else
value += c;
}
}
}
public void Save(string filePath)
{
Save(new EasySaveSettings (filePath), false);
}
public void Save(string filePath, EasySaveSettings settings)
{
Save(new EasySaveSettings (filePath, settings), false);
}
public void Save(EasySaveSettings settings)
{
Save(settings, false);
}
public void Save(string filePath, bool append)
{
Save(new EasySaveSettings (filePath), append);
}
public void Save(string filePath, EasySaveSettings settings, bool append)
{
Save(new EasySaveSettings (filePath, settings), append);
}
public void Save(EasySaveSettings settings, bool append)
{
using (var writer = new StreamWriter(EasySaveStream.CreateStream(settings, append ? EasySaveFileMode.Append : EasySaveFileMode.Write)))
{
// If data already exists and we're appending, we need to prepend a newline.
if(append && EasySave.FileExists(settings))
writer.Write(NEWLINE_CHAR);
var array = ToArray();
for(int row = 0; row < rows; row++)
{
if(row != 0)
writer.Write(NEWLINE_CHAR);
for(int col = 0; col < cols; col++)
{
if(col != 0)
writer.Write(COMMA_CHAR);
writer.Write( Escape(array [col, row]) );
}
}
}
if(!append)
EasySaveIO.CommitBackup(settings);
}
private static string Escape(string str, bool isAlreadyWrappedInQuotes=false)
{
if (str == "")
return "\"\"";
else if(str == null)
return null;
// Now escape any other quotes.
if(str.Contains(QUOTE))
str = str.Replace(QUOTE, ESCAPED_QUOTE);
// If there's chars to escape, wrap the value in quotes.
if(str.IndexOfAny(CHARS_TO_ESCAPE) > -1)
str = QUOTE + str + QUOTE;
return str;
}
private static string Unescape(string str)
{
if(str.StartsWith(QUOTE) && str.EndsWith(QUOTE))
{
str = str.Substring(1, str.Length-2);
if(str.Contains(ESCAPED_QUOTE))
str = str.Replace(ESCAPED_QUOTE, QUOTE);
}
return str;
}
private string[,] ToArray()
{
var array = new string[cols, rows];
foreach (var cell in cells)
array [cell.Key.col, cell.Key.row] = cell.Value;
return array;
}
protected struct Index
{
public int col;
public int row;
public Index(int col, int row)
{
this.col = col;
this.row = row;
}
}
}

View File

@@ -0,0 +1,566 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;
using Convention.EasySave.Types;
using System.Globalization;
namespace Convention.EasySave.Internal
{
/*
* Specific EasySaveReader for reading JSON data.
*
* Note: Leading & trailing whitespace is ignored whenever
* reading characters which are part of the JSON syntax,
* i.e. { } [ ] , " " :
*/
public class EasySaveJSONReader : EasySaveReader
{
private const char endOfStreamChar = (char)65535;
public StreamReader baseReader;
internal EasySaveJSONReader(Stream stream, EasySaveSettings settings, bool readHeaderAndFooter = true) : base(settings, readHeaderAndFooter)
{
this.baseReader = new StreamReader(stream);
// Read opening brace from file if we're loading straight from file.
if(readHeaderAndFooter)
{
try
{
SkipOpeningBraceOfFile();
}
catch
{
this.Dispose();
throw new FormatException("Cannot load from file because the data in it is not JSON data, or the data is encrypted.\nIf the save data is encrypted, please ensure that encryption is enabled when you load, and that you are using the same password used to encrypt the data.");
}
}
}
#region Property/Key Methods
/*
* Reads the name of a property, and must be positioned (with or without whitespace) either:
* - Before the '"' of a property name.
* - Before the ',' separating properties.
* - Before the '}' or ']' terminating this list of properties.
* Can be used in conjunction with Read(EasySaveType) to read a property.
*/
public override string ReadPropertyName()
{
char c = PeekCharIgnoreWhitespace();
// Check whether there are any properties left to read.
if(IsTerminator(c))
return null;
else if(c == ',')
ReadCharIgnoreWhitespace();
else if(!IsQuotationMark(c))
throw new FormatException("Expected ',' separating properties or '\"' before property name, found '"+c+"'.");
var propertyName = Read_string();
if(propertyName == null)
throw new FormatException("Stream isn't positioned before a property.");
// Skip the ':' seperating property and value.
ReadCharIgnoreWhitespace(':');
return propertyName;
}
/*
* Reads the type data prefixed to this key.
* If ignore is true, it will return null to save the computation of converting
* the string to a Type.
*/
protected override Type ReadKeyPrefix(bool ignoreType=false)
{
StartReadObject();
Type dataType = null;
string propertyName = ReadPropertyName();
if(propertyName == EasySaveType.typeFieldName)
{
string typeString = Read_string();
dataType = ignoreType ? null : EasySaveReflection.GetType(typeString);
propertyName = ReadPropertyName();
}
if(propertyName != "value")
throw new FormatException("This data is not Easy Save Key Value data. Expected property name \"value\", found \""+propertyName+"\".");
return dataType;
}
protected override void ReadKeySuffix()
{
EndReadObject();
}
internal override bool StartReadObject()
{
base.StartReadObject();
return ReadNullOrCharIgnoreWhitespace('{');
}
internal override void EndReadObject()
{
ReadCharIgnoreWhitespace('}');
base.EndReadObject();
}
internal override bool StartReadDictionary()
{
return StartReadObject();
}
internal override void EndReadDictionary(){}
internal override bool StartReadDictionaryKey()
{
// If this is an empty Dictionary, return false.
if(PeekCharIgnoreWhitespace() == '}')
{
ReadCharIgnoreWhitespace();
return false;
}
return true;
}
internal override void EndReadDictionaryKey()
{
ReadCharIgnoreWhitespace(':');
}
internal override void StartReadDictionaryValue(){}
internal override bool EndReadDictionaryValue()
{
char c = ReadCharIgnoreWhitespace();
// If we find a ']', we reached the end of the array.
if(c == '}')
return true;
// Else, we should expect a comma.
else if(c != ',')
throw new FormatException("Expected ',' seperating Dictionary items or '}' terminating Dictionary, found '"+c+"'.");
return false;
}
internal override bool StartReadCollection()
{
return ReadNullOrCharIgnoreWhitespace('[');
}
internal override void EndReadCollection(){}
internal override bool StartReadCollectionItem()
{
// If this is an empty collection, return false.
if(PeekCharIgnoreWhitespace() == ']')
{
ReadCharIgnoreWhitespace();
return false;
}
return true;
}
internal override bool EndReadCollectionItem()
{
char c = ReadCharIgnoreWhitespace();
// If we find a ']', we reached the end of the array.
if(c == ']')
return true;
// Else, we should expect a comma.
else if(c != ',')
throw new FormatException("Expected ',' seperating collection items or ']' terminating collection, found '"+c+"'.");
return false;
}
#endregion
#region Seeking Methods
/*
* Reads a string value into a StreamWriter.
* Reader should be positioned after the opening quotation mark.
* Will also read the closing quotation mark.
* If the 'skip' parameter is true, data will not be written into a StreamWriter and will return null.
*/
private void ReadString(StreamWriter writer, bool skip=false)
{
bool endOfString = false;
// Read to end of string, or throw error if we reach end of stream.
while(!endOfString)
{
char c = ReadOrSkipChar(writer, skip);
switch(c)
{
case endOfStreamChar:
throw new FormatException("String without closing quotation mark detected.");
case '\\':
ReadOrSkipChar(writer, skip);
break;
default:
if(IsQuotationMark(c))
endOfString = true;
break;
}
}
}
/*
* Reads the current object in the stream.
* Stream position should be somewhere before the opening brace for the object.
* When this method successfully exits, it will be on the closing brace for the object.
* If the 'skip' parameter is true, data will not be written into a StreamWriter and will return null.
*/
internal override byte[] ReadElement(bool skip=false)
{
// If 'skip' is enabled, don't create a stream or writer as we'll discard all bytes we read.
StreamWriter writer = skip ? null : new StreamWriter(new MemoryStream(settings.bufferSize));
using(writer)
{
int nesting = 0;
char c = (char)baseReader.Peek();
// Determine if we're skipping a primitive type.
// First check if it's an opening object or array brace.
if(!IsOpeningBrace(c))
{
// If we're skipping a string, use SkipString().
if(c == '\"')
{
// Skip initial quotation mark as SkipString() requires this.
ReadOrSkipChar(writer, skip);
ReadString(writer, skip);
}
// Else we just need to read until we reach a closing brace.
else
// While we've not peeked a closing brace.
while(!IsEndOfValue((char)baseReader.Peek()))
ReadOrSkipChar(writer, skip);
if(skip)
return null;
writer.Flush();
return ((MemoryStream)writer.BaseStream).ToArray();
}
// Else, we're skipping a type surrounded by braces.
// Iterate through every character, logging nesting.
while(true)
{
c = ReadOrSkipChar(writer, skip);
if(c == endOfStreamChar) // Detect premature end of stream, which denotes missing closing brace.
throw new FormatException("Missing closing brace detected, as end of stream was reached before finding it.");
// Handle quoted strings.
// According to the RFC, only '\' and '"' must be escaped in strings.
if(IsQuotationMark(c))
{
ReadString(writer, skip);
continue;
}
// Handle braces and other characters.
switch(c)
{
case '{': // Entered another level of nesting.
case '[':
nesting++;
break;
case '}': // Exited a level of nesting.
case ']':
nesting--;
// If nesting < 1, we've come to the end of the object.
if(nesting<1)
{
if(skip)
return null;
writer.Flush();
return ((MemoryStream)writer.BaseStream).ToArray();
}
break;
default:
break;
}
}
}
}
/*
* Reads the next char into a stream, or ignores it if 'skip' is true.
*/
private char ReadOrSkipChar(StreamWriter writer, bool skip)
{
char c = (char)baseReader.Read();
if(!skip) writer.Write(c);
return c;
}
#endregion
#region JSON-specific methods.
/*
* Reads a char from the stream and ignores leading and trailing whitespace.
*/
private char ReadCharIgnoreWhitespace(bool ignoreTrailingWhitespace=true)
{
char c;
// Skip leading whitespace and read char.
while(IsWhiteSpace(c = (char)baseReader.Read()))
{}
// Skip trailing whitespace.
if(ignoreTrailingWhitespace)
while(IsWhiteSpace((char)baseReader.Peek()))
baseReader.Read();
return c;
}
/*
* Reads a char, or the NULL value, from the stream and ignores leading and trailing whitespace.
* Returns true if NULL was read.
*/
private bool ReadNullOrCharIgnoreWhitespace(char expectedChar)
{
char c = ReadCharIgnoreWhitespace();
// Check for null
if(c == 'n')
{
var chars = new char[3];
baseReader.ReadBlock(chars, 0, 3);
if((char)chars[0] == 'u' && (char)chars[1] == 'l' && (char)chars[2] == 'l')
return true;
}
if(c != expectedChar)
{
if(c == endOfStreamChar)
throw new FormatException("End of stream reached when expecting '"+expectedChar+"'.");
else
throw new FormatException("Expected \'"+expectedChar+"\' or \"null\", found \'"+c+"\'.");
}
return false;
}
/*
* Reads a char from the stream and ignores leading and trailing whitespace.
* Throws an error if the char isn't equal to the one specificed as a parameter, or if it's the end of stream.
*/
private char ReadCharIgnoreWhitespace(char expectedChar)
{
char c = ReadCharIgnoreWhitespace();
if(c != expectedChar)
{
if(c == endOfStreamChar)
throw new FormatException("End of stream reached when expecting '"+expectedChar+"'.");
else
throw new FormatException("Expected \'"+expectedChar+"\', found \'"+c+"\'.");
}
return c;
}
private bool ReadQuotationMarkOrNullIgnoreWhitespace()
{
char c = ReadCharIgnoreWhitespace(false); // Don't read trailing whitespace as this is the value.
if(c == 'n')
{
var chars = new char[3];
baseReader.ReadBlock(chars, 0, 3);
if((char)chars[0] == 'u' && (char)chars[1] == 'l' && (char)chars[2] == 'l')
return true;
}
else if(!IsQuotationMark(c))
{
if(c == endOfStreamChar)
throw new FormatException("End of stream reached when expecting quotation mark.");
else
throw new FormatException("Expected quotation mark, found \'"+c+"\'.");
}
return false;
}
/*
* Peeks the next char in the stream, ignoring leading whitespace, but not trailing whitespace.
*/
private char PeekCharIgnoreWhitespace(char expectedChar)
{
char c = PeekCharIgnoreWhitespace();
if(c != expectedChar)
{
if(c == endOfStreamChar)
throw new FormatException("End of stream reached while peeking, when expecting '"+expectedChar+"'.");
else
throw new FormatException("Expected \'"+expectedChar+"\', found \'"+c+"\'.");
}
return c;
}
/*
* Peeks the next char in the stream, ignoring leading whitespace, but not trailing whitespace.
* Throws an error if the char isn't equal to the one specificed as a parameter.
*/
private char PeekCharIgnoreWhitespace()
{
char c;
// Skip leading whitespace and read char.
while(IsWhiteSpace(c = (char)baseReader.Peek()))
baseReader.Read();
return c;
}
// Skips all whitespace immediately after the current position.
private void SkipWhiteSpace()
{
while(IsWhiteSpace((char)baseReader.Peek()))
baseReader.Read();
}
private void SkipOpeningBraceOfFile()
{
// Skip the whitespace and '{' at the beginning of the JSON file.
char firstChar = ReadCharIgnoreWhitespace();
if(firstChar != '{') // If first char isn't '{', it's not valid JSON.
throw new FormatException("File is not valid JSON. Expected '{' at beginning of file, but found '"+firstChar+"'.");
}
private static bool IsWhiteSpace(char c)
{
return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
}
private static bool IsOpeningBrace(char c)
{
return (c == '{' || c == '[');
}
private static bool IsEndOfValue(char c)
{
return (c == '}' || c == ' ' || c == '\t' || c == ']' || c == ',' || c== ':' || c == endOfStreamChar || c == '\n' || c == '\r');
}
private static bool IsTerminator(char c)
{
return (c == '}' || c == ']');
}
private static bool IsQuotationMark(char c)
{
return c == '\"' || c == '“' || c == '”';
}
private static bool IsEndOfStream(char c)
{
return c == endOfStreamChar;
}
/*
* Reads a value (i.e. non-string, non-object) from the stream as a string.
* Used mostly in Read_[type]() methods.
*/
private string GetValueString()
{
StringBuilder builder = new StringBuilder();
while(!IsEndOfValue(PeekCharIgnoreWhitespace()))
builder.Append((char)baseReader.Read());
// If it's an empty value, return null.
if(builder.Length == 0)
return null;
return builder.ToString();
}
#endregion
#region Primitive Read() Methods.
internal override string Read_string()
{
if(ReadQuotationMarkOrNullIgnoreWhitespace())
return null;
char c;
StringBuilder sb = new StringBuilder();
while(!IsQuotationMark((c = (char)baseReader.Read())))
{
// If escape mark is found, generate correct escaped character.
if(c == '\\')
{
c = (char)baseReader.Read();
if(IsEndOfStream(c))
throw new FormatException("Reached end of stream while trying to read string literal.");
switch(c)
{
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
default:
break;
}
}
sb.Append(c);
}
return sb.ToString();
}
internal override long Read_ref()
{
if (IsQuotationMark(PeekCharIgnoreWhitespace()))
return long.Parse(Read_string());
return Read_long();
}
internal override char Read_char() { return char.Parse( Read_string()); }
internal override float Read_float() { return float.Parse( GetValueString(), CultureInfo.InvariantCulture); }
internal override int Read_int() { return int.Parse( GetValueString()); }
internal override bool Read_bool() { return bool.Parse( GetValueString()); }
internal override decimal Read_decimal() { return decimal.Parse( GetValueString(), CultureInfo.InvariantCulture); }
internal override double Read_double() { return double.Parse( GetValueString(), CultureInfo.InvariantCulture); }
internal override long Read_long() { return long.Parse( GetValueString()); }
internal override ulong Read_ulong() { return ulong.Parse( GetValueString()); }
internal override uint Read_uint() { return uint.Parse( GetValueString()); }
internal override byte Read_byte() { return (byte)int.Parse( GetValueString()); }
internal override sbyte Read_sbyte() { return (sbyte)int.Parse( GetValueString()); }
internal override short Read_short() { return (short)int.Parse( GetValueString()); }
internal override ushort Read_ushort() { return (ushort)int.Parse( GetValueString()); }
internal override byte[] Read_byteArray(){ return System.Convert.FromBase64String(Read_string()); }
#endregion
public override void Dispose()
{
baseReader.Dispose();
}
}
}

View File

@@ -0,0 +1,454 @@
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System;
using System.ComponentModel;
using Convention.EasySave.Types;
using Convention.EasySave.Internal;
public abstract class EasySaveReader : System.IDisposable
{
/// <summary>The settings used to create this reader.</summary>
public EasySaveSettings settings;
protected int serializationDepth = 0;
#region EasySaveReader Abstract Methods
internal abstract int Read_int();
internal abstract float Read_float();
internal abstract bool Read_bool();
internal abstract char Read_char();
internal abstract decimal Read_decimal();
internal abstract double Read_double();
internal abstract long Read_long();
internal abstract ulong Read_ulong();
internal abstract byte Read_byte();
internal abstract sbyte Read_sbyte();
internal abstract short Read_short();
internal abstract ushort Read_ushort();
internal abstract uint Read_uint();
internal abstract string Read_string();
internal abstract byte[] Read_byteArray();
internal abstract long Read_ref();
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public abstract string ReadPropertyName();
protected abstract Type ReadKeyPrefix(bool ignore = false);
protected abstract void ReadKeySuffix();
internal abstract byte[] ReadElement(bool skip=false);
/// <summary>Disposes of the reader and it's underlying stream.</summary>
public abstract void Dispose();
// Seeks to the given key. Note that the stream position will not be reset.
internal virtual bool Goto(string key)
{
if (key == null)
throw new ArgumentNullException("Key cannot be NULL when loading data.");
string currentKey;
while ((currentKey = ReadPropertyName()) != key)
{
if (currentKey == null)
return false;
Skip();
}
return true;
}
internal virtual bool StartReadObject()
{
serializationDepth++;
return false;
}
internal virtual void EndReadObject()
{
serializationDepth--;
}
internal abstract bool StartReadDictionary();
internal abstract void EndReadDictionary();
internal abstract bool StartReadDictionaryKey();
internal abstract void EndReadDictionaryKey();
internal abstract void StartReadDictionaryValue();
internal abstract bool EndReadDictionaryValue();
internal abstract bool StartReadCollection();
internal abstract void EndReadCollection();
internal abstract bool StartReadCollectionItem();
internal abstract bool EndReadCollectionItem();
#endregion
internal EasySaveReader(EasySaveSettings settings, bool readHeaderAndFooter = true)
{
this.settings = settings;
}
// If this is not null, the next call to the Properties will return this name.
internal string overridePropertiesName = null;
/// <summary>Allows you to enumerate over each field name. This should only be used within an EasySaveType file.</summary>
public virtual EasySaveReaderPropertyEnumerator Properties
{
get
{
return new EasySaveReaderPropertyEnumerator (this);
}
}
internal virtual EasySaveReaderRawEnumerator RawEnumerator
{
get
{
return new EasySaveReaderRawEnumerator (this);
}
}
/*
* Skips the current object in the stream.
* Stream position should be somewhere before the opening brace for the object.
* When this method successfully exits, it will be on the closing brace for the object.
*/
/// <summary>Skips the current object in the stream.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void Skip()
{
ReadElement(true);
}
/// <summary>Reads a value of type T from the reader.</summary>
public virtual T Read<T>()
{
return Read<T>(EasySaveTypeMgr.GetOrCreateEasySaveType(typeof(T)));
}
/// <summary>Reads a value of type T from the reader into an existing object.</summary>
/// <param name="obj">The object we want to read our value into.</param>
public virtual void ReadInto<T>(object obj)
{
ReadInto<T>(obj, EasySaveTypeMgr.GetOrCreateEasySaveType(typeof(T)));
}
/// <summary>Reads a property (i.e. a property name and value) from the reader, ignoring the property name and only returning the value.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public T ReadProperty<T>()
{
return ReadProperty<T>(EasySaveTypeMgr.GetOrCreateEasySaveType(typeof(T)));
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public T ReadProperty<T>(EasySaveType type)
{
ReadPropertyName();
return Read<T>(type);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public long ReadRefProperty()
{
ReadPropertyName();
return Read_ref();
}
internal Type ReadType()
{
return EasySaveReflection.GetType(Read<string>(EasySaveType_string.Instance));
}
/// <summary>Sets the value of a private property on an object.</summary>
/// <param name="name">The name of the property we want to set.</param>
/// <param name="value">The value we want to set the property to.</param>
/// <param name="objectContainingProperty">The object containing the property we want to set.</param>
/// <returns>The objectContainingProperty object. This is helpful if you're setting a private property on a struct or other immutable type and need to return the boxed value.</returns>
public object SetPrivateProperty(string name, object value, object objectContainingProperty)
{
var property = EasySaveReflection.GetEasySaveReflectedProperty(objectContainingProperty.GetType(), name);
if (property.IsNull)
throw new MissingMemberException("A private property named " + name + " does not exist in the type " + objectContainingProperty.GetType());
property.SetValue(objectContainingProperty, value);
return objectContainingProperty;
}
/// <summary>Sets the value of a private field on an object.</summary>
/// <param name="name">The name of the field we want to set.</param>
/// <param name="value">The value we want to set the field to.</param>
/// <param name="objectContainingField">The object containing the field we want to set.</param>
/// <returns>The objectContainingField object. This is helpful if you're setting a private property on a struct or other immutable type and need to return the boxed value.</returns>
public object SetPrivateField(string name, object value, object objectContainingField)
{
var field = EasySaveReflection.GetEasySaveReflectedMember(objectContainingField.GetType(), name);
if(field.IsNull)
throw new MissingMemberException("A private field named "+ name + " does not exist in the type "+objectContainingField.GetType());
field.SetValue(objectContainingField, value);
return objectContainingField;
}
#region Read(key) & Read(key, obj) methods
/// <summary>Reads a value from the reader with the given key.</summary>
/// <param name="key">The key which uniquely identifies our value.</param>
public virtual T Read<T>(string key)
{
if(!Goto(key))
throw new KeyNotFoundException("Key \"" + key + "\" was not found in file \""+settings.FullPath+"\". Use Load<T>(key, defaultValue) if you want to return a default value if the key does not exist.");
Type type = ReadTypeFromHeader<T>();
T obj = Read<T>(EasySaveTypeMgr.GetOrCreateEasySaveType(type));
//ReadKeySuffix(); //No need to read key suffix as we're returning. Doing so would throw an error at this point for BinaryReaders.
return obj;
}
/// <summary>Reads a value from the reader with the given key, returning the default value if the key does not exist.</summary>
/// <param name="key">The key which uniquely identifies our value.</param>
/// <param name="defaultValue">The value we want to return if this key does not exist in the reader.</param>
public virtual T Read<T>(string key, T defaultValue)
{
if(!Goto(key))
return defaultValue;
Type type = ReadTypeFromHeader<T>();
T obj = Read<T>(EasySaveTypeMgr.GetOrCreateEasySaveType(type));
//ReadKeySuffix(); //No need to read key suffix as we're returning. Doing so would throw an error at this point for BinaryReaders.
return obj;
}
/// <summary>Reads a value from the reader with the given key into the provided object.</summary>
/// <param name="key">The key which uniquely identifies our value.</param>
/// <param name="obj">The object we want to load the value into.</param>
public virtual void ReadInto<T>(string key, T obj) where T : class
{
if(!Goto(key))
throw new KeyNotFoundException("Key \"" + key + "\" was not found in file \""+settings.FullPath+"\"");
Type type = ReadTypeFromHeader<T>();
ReadInto<T>(obj, EasySaveTypeMgr.GetOrCreateEasySaveType(type));
//ReadKeySuffix(); //No need to read key suffix as we're returning. Doing so would throw an error at this point for BinaryReaders.
}
protected virtual void ReadObject<T>(object obj, EasySaveType type)
{
// Check for null.
if(StartReadObject())
return;
type.ReadInto<T>(this, obj);
EndReadObject();
}
protected virtual T ReadObject<T>(EasySaveType type)
{
if(StartReadObject())
return default(T);
object obj = type.Read<T>(this);
EndReadObject();
return (T)obj;
}
#endregion
#region Read(EasySaveType) & Read(obj,EasySaveType) methods
/*
* Parses the next JSON Object in the stream (i.e. must be between '{' and '}' chars).
* If the first character in the Stream is not a '{', it will throw an error.
* Will also read the terminating '}'.
* If we have reached the end of stream, it will return null.
*/
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual T Read<T>(EasySaveType type)
{
if (type == null || type.isUnsupported)
throw new NotSupportedException("Type of " + type + " is not currently supported, and could not be loaded using reflection.");
else if (type.isPrimitive)
return (T)type.Read<T>(this);
else if (type.isCollection)
return (T)((ECollectionType)type).Read(this);
else if (type.isDictionary)
return (T)((EasySaveDictionaryType)type).Read(this);
else
return ReadObject<T>(type);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void ReadInto<T>(object obj, EasySaveType type)
{
if(type == null || type.isUnsupported)
throw new NotSupportedException("Type of "+obj.GetType()+" is not currently supported, and could not be loaded using reflection.");
else if(type.isCollection)
((ECollectionType)type).ReadInto(this, obj);
else if(type.isDictionary)
((EasySaveDictionaryType)type).ReadInto(this, obj);
else
ReadObject<T>(obj, type);
}
#endregion
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
internal Type ReadTypeFromHeader<T>()
{
// Check whether we need to determine the type by reading the header.
if(typeof(T) == typeof(object))
return ReadKeyPrefix();
else if(settings.typeChecking)
{
Type type = ReadKeyPrefix();
if(type != typeof(T))
throw new InvalidOperationException("Trying to load data of type "+typeof(T)+", but data contained in file is type of "+type+".");
return type;
}
else
{
ReadKeyPrefix(true);
return typeof(T);
}
}
/// <summary>Creates a new EasySaveReader and loads the default file into it.</summary>
public static EasySaveReader Create()
{
return Create(new EasySaveSettings());
}
/// <summary>Creates a new EasySaveReader and loads a file in storage into it.</summary>
/// <param name="filePath">The relative or absolute path of the file we want to load into the reader.</param>
public static EasySaveReader Create(string filePath)
{
return Create(new EasySaveSettings(filePath));
}
/// <summary>Creates a new EasySaveReader and loads a file in storage into it.</summary>
/// <param name="filePath">The relative or absolute path of the file we want to load into the reader.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public static EasySaveReader Create(string filePath, EasySaveSettings settings)
{
return Create(new EasySaveSettings(filePath, settings));
}
/// <summary>Creates a new EasySaveReader and loads a file in storage into it.</summary>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public static EasySaveReader Create(EasySaveSettings settings)
{
Stream stream = EasySaveStream.CreateStream(settings, EasySaveFileMode.Read);
if(stream == null)
return null;
// Get the baseWriter using the given Stream.
if (settings.format == EasySave.Format.JSON)
return new EasySaveJSONReader(stream, settings);
return null;
}
/// <summary>Creates a new EasySaveReader and loads the bytes provided into it.</summary>
public static EasySaveReader Create(byte[] bytes)
{
return Create(bytes, new EasySaveSettings());
}
/// <summary>Creates a new EasySaveReader and loads the bytes provided into it.</summary>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public static EasySaveReader Create(byte[] bytes, EasySaveSettings settings)
{
Stream stream = EasySaveStream.CreateStream(new MemoryStream(bytes), settings, EasySaveFileMode.Read);
if(stream == null)
return null;
// Get the baseWriter using the given Stream.
if(settings.format == EasySave.Format.JSON)
return new EasySaveJSONReader(stream, settings);
return null;
}
internal static EasySaveReader Create(Stream stream, EasySaveSettings settings)
{
stream = EasySaveStream.CreateStream(stream, settings, EasySaveFileMode.Read);
// Get the baseWriter using the given Stream.
if(settings.format == EasySave.Format.JSON)
return new EasySaveJSONReader(stream, settings);
return null;
}
internal static EasySaveReader Create(Stream stream, EasySaveSettings settings, bool readHeaderAndFooter)
{
// Get the baseWriter using the given Stream.
if(settings.format == EasySave.Format.JSON)
return new EasySaveJSONReader(stream, settings, readHeaderAndFooter);
return null;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class EasySaveReaderPropertyEnumerator
{
public EasySaveReader reader;
public EasySaveReaderPropertyEnumerator(EasySaveReader reader)
{
this.reader = reader;
}
public IEnumerator GetEnumerator()
{
string propertyName;
while(true)
{
// Allows us to repeat a property name or insert one of our own.
if(reader.overridePropertiesName != null)
{
string tempName = reader.overridePropertiesName;
reader.overridePropertiesName = null;
yield return tempName;
}
else
{
if((propertyName = reader.ReadPropertyName()) == null)
yield break;
yield return propertyName;
}
}
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class EasySaveReaderRawEnumerator
{
public EasySaveReader reader;
public EasySaveReaderRawEnumerator(EasySaveReader reader)
{
this.reader = reader;
}
public IEnumerator GetEnumerator()
{
while(true)
{
string key = reader.ReadPropertyName();
if(key == null)
yield break;
Type type = reader.ReadTypeFromHeader<object>();
byte[] bytes = reader.ReadElement();
reader.ReadKeySuffix();
if(type != null)
yield return new KeyValuePair<string,EasySaveData>(key, new EasySaveData(type, bytes));
}
}
}
}

View File

@@ -0,0 +1,18 @@
public class EasySaveDefaults
{
public EasySaveSerializableSettings settings = new EasySaveSerializableSettings();
public bool addMgrToSceneAutomatically = false;
public bool autoUpdateReferences = true;
public bool addAllPrefabsToManager = true;
public int collectDependenciesDepth = 4;
public int collectDependenciesTimeout = 10;
public bool updateReferencesWhenSceneChanges = true;
public bool updateReferencesWhenSceneIsSaved = true;
public bool updateReferencesWhenSceneIsOpened = true;
public string[] referenceFolders = new string[0];
public bool logDebugInfo = false;
public bool logWarnings = true;
public bool logErrors = true;
}

View File

@@ -0,0 +1,373 @@
using Convention.EasySave.Internal;
public class EasySaveSettings : System.ICloneable
{
#region Default settings
private static EasySaveSettings _defaults = null;
private static EasySaveDefaults _defaultSettingsScriptableObject;
private const string defaultSettingsPath = "EasySave/EasySaveDefaults";
public static EasySaveDefaults defaultSettingsScriptableObject
{
get
{
if (_defaultSettingsScriptableObject == null)
{
_defaultSettingsScriptableObject = Resources.Load<EasySaveDefaults>(defaultSettingsPath);
#if UNITY_EDITOR
if (_defaultSettingsScriptableObject == null)
{
_defaultSettingsScriptableObject = ScriptableObject.CreateInstance<ES3Defaults>();
// If this is the version being submitted to the Asset Store, don't include ES3Defaults.
if (Application.productName.Contains("EasySave Release"))
{
Debug.Log("This has been identified as a release build as the title contains 'EasySave Release', so ES3Defaults will not be created.");
return _defaultSettingsScriptableObject;
}
// Convert the old settings to the new settings if necessary.
var oldSettings = GetOldSettings();
if (oldSettings != null)
{
oldSettings.CopyInto(_defaultSettingsScriptableObject.settings);
// Only enable warning logs by default for new installs as this may look like unexpected behaviour to some.
_defaultSettingsScriptableObject.logWarnings = false;
RemoveOldSettings();
}
CreateDefaultSettingsFolder();
AssetDatabase.CreateAsset(_defaultSettingsScriptableObject, PathToDefaultSettings());
AssetDatabase.SaveAssets();
}
#endif
}
return _defaultSettingsScriptableObject;
}
}
public static EasySaveSettings defaultSettings
{
get
{
if(_defaults == null)
{
if(defaultSettingsScriptableObject != null)
_defaults = defaultSettingsScriptableObject.settings;
}
return _defaults;
}
}
private static EasySaveSettings _unencryptedUncompressedSettings = null;
internal static EasySaveSettings unencryptedUncompressedSettings
{
get
{
if (_unencryptedUncompressedSettings == null)
_unencryptedUncompressedSettings = new EasySaveSettings(EasySave.EncryptionType.None, EasySave.CompressionType.None);
return _unencryptedUncompressedSettings;
}
}
#endregion
#region Fields
private static readonly string[] resourcesExtensions = new string[]{".txt", ".htm", ".html", ".xml", ".bytes", ".json", ".csv", ".yaml", ".fnt" };
[SerializeField]
private EasySave.Location _location;
/// <summary>The location where we wish to store data. As it's not possible to save/load from File in WebGL, if the default location is File it will use PlayerPrefs instead.</summary>
public EasySave.Location location
{
get
{
if(_location == EasySave.Location.File && (Application.platform == RuntimePlatform.WebGLPlayer || Application.platform == RuntimePlatform.tvOS))
return EasySave.Location.PlayerPrefs;
return _location;
}
set{ _location = value; }
}
/// <summary>The path associated with this EasySaveSettings object, if any.</summary>
public string path = "SaveFile.es3";
/// <summary>The type of encryption to use when encrypting data, if any.</summary>
public EasySave.EncryptionType encryptionType = EasySave.EncryptionType.None;
/// <summary>The type of encryption to use when encrypting data, if any.</summary>
public EasySave.CompressionType compressionType = EasySave.CompressionType.None;
/// <summary>The password to use when encrypting data.</summary>
public string encryptionPassword = "password";
/// <summary>The default directory in which to store files, and the location which relative paths should be relative to.</summary>
public EasySave.Directory directory = EasySave.Directory.PersistentDataPath;
/// <summary>What format to use when serialising and deserialising data.</summary>
public EasySave.Format format = EasySave.Format.JSON;
/// <summary>Whether we want to pretty print JSON.</summary>
public bool prettyPrint = true;
/// <summary>Any stream buffers will be set to this length in bytes.</summary>
public int bufferSize = 2048;
/// <summary>The text encoding to use for text-based format. Note that changing this may invalidate previous save data.</summary>
public System.Text.Encoding encoding = System.Text.Encoding.UTF8;
// <summary>Whether we should serialise children when serialising a GameObject.</summary>
public bool saveChildren = true;
// <summary>Whether we should apply encryption and/or compression to raw cached data if they're specified in the cached data's settings.</summary>
public bool postprocessRawCachedData = false;
/// <summary>Whether we should check that the data we are loading from a file matches the method we are using to load it.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public bool typeChecking = true;
/// <summary>Enabling this ensures that only serialisable fields are serialised. Otherwise, possibly unsafe fields and properties will be serialised.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public bool safeReflection = true;
/// <summary>Whether UnityEngine.Object members should be stored by value, reference or both.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public EasySave.ReferenceMode memberReferenceMode = EasySave.ReferenceMode.ByRef;
/// <summary>Whether the main save methods should save UnityEngine.Objects by value, reference, or both.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public EasySave.ReferenceMode referenceMode = EasySave.ReferenceMode.ByRefAndValue;
/// <summary>How many levels of hierarchy Easy Save will serialise. This is used to protect against cyclic references.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public int serializationDepthLimit = 64;
/// <summary>The names of the Assemblies we should try to load our Convention.EasySave.Types from.</summary>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public string[] assemblyNames = new string[] { "Assembly-CSharp-firstpass", "Assembly-CSharp"};
/// <summary>Gets the full, absolute path which this EasySaveSettings object identifies.</summary>
public string FullPath
{
get
{
if (path == null)
throw new System.NullReferenceException("The 'path' field of this EasySaveSettings is null, indicating that it was not possible to load the default settings from Resources. Please check that the EasySave Default Settings.prefab exists in Assets/Plugins/Resources/EasySave/");
if(IsAbsolute(path))
return path;
if(location == EasySave.Location.File)
{
if(directory == EasySave.Directory.PersistentDataPath)
return EasySaveIO.persistentDataPath + "/" + path;
if(directory == EasySave.Directory.DataPath)
return Application.dataPath + "/" + path;
throw new System.NotImplementedException("File directory \""+directory+"\" has not been implemented.");
}
if(location == EasySave.Location.Resources)
{
// Check that it has valid extension
var extension = System.IO.Path.GetExtension(path);
bool hasValidExtension = false;
foreach (var ext in resourcesExtensions)
{
if (extension == ext)
{
hasValidExtension = true;
break;
}
}
if(!hasValidExtension)
throw new System.ArgumentException("Extension of file in Resources must be .json, .bytes, .txt, .csv, .htm, .html, .xml, .yaml or .fnt, but path given was \"" + path + "\"");
// Remove extension
string resourcesPath = path.Replace(extension, "");
return resourcesPath;
}
return path;
}
}
#endregion
#region Constructors
/// <summary>Creates a new EasySaveSettings object with the given path.</summary>
/// <param name="path">The path associated with this EasySaveSettings object.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public EasySaveSettings(string path = null, EasySaveSettings settings = null) : this(true)
{
// if there are settings to merge, merge them.
if (settings != null)
settings.CopyInto(this);
if (path != null)
this.path = path;
}
/// <summary>Creates a new EasySaveSettings object with the given path.</summary>
/// <param name="path">The path associated with this EasySaveSettings object.</param>
/// <param name="enums">Accepts an EasySave.EncryptionType, EasySave.CompressionType, EasySave.Location, EasySave.Directory or EasySave.ReferenceMode.</param>
public EasySaveSettings(string path, params System.Enum[] enums) : this(enums)
{
if (path != null)
this.path = path;
}
/// <summary>Creates a new EasySaveSettings object with the given path.</summary>
/// <param name="path">The path associated with this EasySaveSettings object.</param>
/// <param name="enums">Accepts an EasySave.EncryptionType, EasySave.CompressionType, EasySave.Location, EasySave.Directory or EasySave.ReferenceMode.</param>
public EasySaveSettings(params System.Enum[] enums) : this(true)
{
foreach (var setting in enums)
{
if (setting is EasySave.EncryptionType)
this.encryptionType = (EasySave.EncryptionType)setting;
else if (setting is EasySave.Location)
this.location = (EasySave.Location)setting;
else if (setting is EasySave.CompressionType)
this.compressionType = (EasySave.CompressionType)setting;
else if (setting is EasySave.ReferenceMode)
this.referenceMode = (EasySave.ReferenceMode)setting;
else if (setting is EasySave.Format)
this.format = (EasySave.Format)setting;
else if (setting is EasySave.Directory)
this.directory = (EasySave.Directory)setting;
}
}
/// <summary>Creates a new EasySaveSettings object with the given encryption settings.</summary>
/// <param name="encryptionType">The type of encryption to use, if any.</param>
/// <param name="encryptionPassword">The password to use when encrypting data.</param>
public EasySaveSettings(EasySave.EncryptionType encryptionType, string encryptionPassword) : this(true)
{
this.encryptionType = encryptionType;
this.encryptionPassword = encryptionPassword;
}
/// <summary>Creates a new EasySaveSettings object with the given path and encryption settings.</summary>
/// <param name="path">The path associated with this EasySaveSettings object.</param>
/// <param name="encryptionType">The type of encryption to use, if any.</param>
/// <param name="encryptionPassword">The password to use when encrypting data.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public EasySaveSettings(string path, EasySave.EncryptionType encryptionType, string encryptionPassword, EasySaveSettings settings = null) : this(path, settings)
{
this.encryptionType = encryptionType;
this.encryptionPassword = encryptionPassword;
}
/* Base constructor which allows us to bypass defaults so it can be called by Editor serialization */
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public EasySaveSettings(bool applyDefaults)
{
if (applyDefaults)
if (defaultSettings != null)
_defaults.CopyInto(this);
}
#endregion
#region Editor methods
#if UNITY_EDITOR
public static string pathToEasySaveFolder = null;
public static string PathToEasySaveFolder()
{
// If the path has not yet been cached, get the path and cache it.
if (string.IsNullOrEmpty(pathToEasySaveFolder))
{
string[] guids = AssetDatabase.FindAssets("ES3Window");
if (guids.Length == 0)
ES3Debug.LogError("Could not locate the Easy Save 3 folder because the ES3Window script has been moved or removed.");
if (guids.Length > 1)
ES3Debug.LogError("Could not locate the Easy Save 3 folder because more than one ES3Window script exists in the project, but this needs to be unique to locate the folder.");
pathToEasySaveFolder = AssetDatabase.GUIDToAssetPath(guids[0]).Split(new string[] { "Editor" }, System.StringSplitOptions.RemoveEmptyEntries)[0];
}
return pathToEasySaveFolder;
}
internal static string PathToDefaultSettings()
{
return PathToEasySaveFolder() + "Resources/"+defaultSettingsPath+".asset";
}
internal static void CreateDefaultSettingsFolder()
{
if (AssetDatabase.IsValidFolder(PathToEasySaveFolder() + "Resources/EasySave"))
return;
// Remove leading slash from PathToEasySaveFolder.
AssetDatabase.CreateFolder(PathToEasySaveFolder().Remove(PathToEasySaveFolder().Length - 1, 1), "Resources");
AssetDatabase.CreateFolder(PathToEasySaveFolder() + "Resources", "EasySave");
}
private static ES3SerializableSettings GetOldSettings()
{
var go = Resources.Load<GameObject>(defaultSettingsPath.Replace("ES3Defaults", "EasySave Default Settings"));
if(go != null)
{
var c = go.GetComponent<ES3DefaultSettings>();
if (c != null && c.settings != null)
return c.settings;
}
return null;
}
private static void RemoveOldSettings()
{
AssetDatabase.DeleteAsset(PathToDefaultSettings().Replace("ES3Defaults.asset", "EasySave Default Settings.prefab"));
}
#endif
#endregion
#region Utility methods
private static bool IsAbsolute(string path)
{
if (path.Length > 0 && (path[0] == '/' || path[0] == '\\'))
return true;
if (path.Length > 1 && path[1] == ':')
return true;
return false;
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public object Clone()
{
var settings = new EasySaveSettings();
CopyInto(settings);
return settings;
}
private void CopyInto(EasySaveSettings newSettings)
{
newSettings._location = _location;
newSettings.directory = directory;
newSettings.format = format;
newSettings.prettyPrint = prettyPrint;
newSettings.path = path;
newSettings.encryptionType = encryptionType;
newSettings.encryptionPassword = encryptionPassword;
newSettings.compressionType = compressionType;
newSettings.bufferSize = bufferSize;
newSettings.encoding = encoding;
newSettings.typeChecking = typeChecking;
newSettings.safeReflection = safeReflection;
newSettings.referenceMode = referenceMode;
newSettings.memberReferenceMode = memberReferenceMode;
newSettings.assemblyNames = assemblyNames;
newSettings.saveChildren = saveChildren;
newSettings.serializationDepthLimit = serializationDepthLimit;
newSettings.postprocessRawCachedData = postprocessRawCachedData;
}
#endregion
}
/*
* A serializable version of the settings we can use as a field in the Editor, which doesn't automatically
* assign defaults to itself, so we get no serialization errors.
*/
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[System.Serializable]
public class EasySaveSerializableSettings : EasySaveSettings
{
public EasySaveSerializableSettings() : base(false){}
public EasySaveSerializableSettings(bool applyDefaults) : base(applyDefaults){}
public EasySaveSerializableSettings(string path) : base(false) { this.path = path; }
public EasySaveSerializableSettings(string path, EasySave.Location location) : base(false) { this.location = location; }
}

View File

@@ -0,0 +1,68 @@
using System.IO;
namespace Convention.EasySave.Internal
{
public enum EasySaveFileMode {Read, Write, Append}
public class EasySaveFileStream : FileStream
{
private bool isDisposed = false;
public EasySaveFileStream( string path, EasySaveFileMode fileMode, int bufferSize, bool useAsync)
: base( GetPath(path, fileMode), GetFileMode(fileMode), GetFileAccess(fileMode), FileShare.None, bufferSize, useAsync)
{
}
// Gets a temporary path if necessary.
protected static string GetPath(string path, EasySaveFileMode fileMode)
{
string directoryPath = EasySaveIO.GetDirectoryPath(path);
// Attempt to create the directory incase it does not exist if we are storing data.
if (fileMode != EasySaveFileMode.Read && directoryPath != EasySaveIO.persistentDataPath)
EasySaveIO.CreateDirectory(directoryPath);
if(fileMode != EasySaveFileMode.Write || fileMode == EasySaveFileMode.Append)
return path;
return (fileMode == EasySaveFileMode.Write) ? path + EasySaveIO.temporaryFileSuffix : path;
}
protected static FileMode GetFileMode(EasySaveFileMode fileMode)
{
if (fileMode == EasySaveFileMode.Read)
return FileMode.Open;
else if (fileMode == EasySaveFileMode.Write)
return FileMode.Create;
else
return FileMode.Append;
}
protected static FileAccess GetFileAccess(EasySaveFileMode fileMode)
{
if (fileMode == EasySaveFileMode.Read)
return FileAccess.Read;
else if (fileMode == EasySaveFileMode.Write)
return FileAccess.Write;
else
return FileAccess.Write;
}
protected override void Dispose (bool disposing)
{
// Ensure we only perform disposable once.
if(isDisposed)
return;
isDisposed = true;
base.Dispose(disposing);
// If this is a file writer, we need to replace the temp file.
/*if(fileMode == EasySaveFileMode.Write && fileMode != EasySaveFileMode.Append)
{
// Delete the old file before overwriting it.
EasySaveIO.DeleteFile(path);
// Rename temporary file to new file.
EasySaveIO.MoveFile(path + EasySave.temporaryFileSuffix, path);
}*/
}
}
}

View File

@@ -0,0 +1,92 @@
using System.IO;
using System.IO.Compression;
using System;
namespace Convention.EasySave.Internal
{
public static class EasySaveStream
{
public static Stream CreateStream(EasySaveSettings settings, EasySaveFileMode fileMode)
{
bool isWriteStream = (fileMode != EasySaveFileMode.Read);
Stream stream = null;
// Check that the path is in a valid format. This will throw an exception if not.
new FileInfo(settings.FullPath);
try
{
if (settings.location == EasySave.Location.InternalMS)
{
// There's no point in creating an empty MemoryStream if we're only reading from it.
if (!isWriteStream)
return null;
stream = new MemoryStream(settings.bufferSize);
}
else if (settings.location == EasySave.Location.File)
{
if (!isWriteStream && !EasySaveIO.FileExists(settings.FullPath))
return null;
stream = new EasySaveFileStream(settings.FullPath, fileMode, settings.bufferSize, false);
}
return CreateStream(stream, settings, fileMode);
}
catch(System.Exception e)
{
if (stream != null)
stream.Dispose();
throw;
}
}
public static Stream CreateStream(Stream stream, EasySaveSettings settings, EasySaveFileMode fileMode)
{
try
{
bool isWriteStream = (fileMode != EasySaveFileMode.Read);
#if !DISABLE_ENCRYPTION
// Encryption
if(settings.encryptionType != EasySave.EncryptionType.None && stream.GetType() != typeof(UnbufferedCryptoStream))
{
EncryptionAlgorithm alg = null;
if(settings.encryptionType == EasySave.EncryptionType.AES)
alg = new AESEncryptionAlgorithm();
stream = new UnbufferedCryptoStream(stream, !isWriteStream, settings.encryptionPassword, settings.bufferSize, alg);
}
#endif
// Compression
if (settings.compressionType != EasySave.CompressionType.None && stream.GetType() != typeof(GZipStream))
{
if (settings.compressionType == EasySave.CompressionType.Gzip)
stream = isWriteStream ? new GZipStream(stream, CompressionMode.Compress) : new GZipStream(stream, CompressionMode.Decompress);
}
return stream;
}
catch (System.Exception e)
{
if (stream != null)
stream.Dispose();
if (e.GetType() == typeof(System.Security.Cryptography.CryptographicException))
throw new System.Security.Cryptography.CryptographicException("Could not decrypt file. Please ensure that you are using the same password used to encrypt the file.");
else
throw;
}
}
public static void CopyTo(Stream source, Stream destination)
{
#if UNITY_2019_1_OR_NEWER
source.CopyTo(destination);
#else
byte[] buffer = new byte[2048];
int bytesRead;
while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
destination.Write(buffer, 0, bytesRead);
#endif
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public abstract class ECollectionType : EasySaveType
{
public EasySaveType elementType;
/*protected EasySaveReflection.EasySaveReflectedMethod readMethod = null;
protected EasySaveReflection.EasySaveReflectedMethod readIntoMethod = null;*/
public abstract object Read(EasySaveReader reader);
public abstract void ReadInto(EasySaveReader reader, object obj);
public abstract void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode);
public ECollectionType(Type type) : base(type)
{
elementType = EasySaveTypeMgr.GetOrCreateEasySaveType(EasySaveReflection.GetElementTypes(type)[0], false);
isCollection = true;
// If the element type is null (i.e. unsupported), make this EasySaveType null.
if(elementType == null)
isUnsupported = true;
}
public ECollectionType(Type type, EasySaveType elementType) : base(type)
{
this.elementType = elementType;
isCollection = true;
}
public override void Write(object obj, EasySaveWriter writer)
{
Write(obj, writer, EasySave.ReferenceMode.ByRefAndValue);
}
protected virtual bool ReadICollection<T>(EasySaveReader reader, ICollection<T> collection, EasySaveType elementType)
{
if(reader.StartReadCollection())
return false;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
collection.Add(reader.Read<T>(elementType));
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
return true;
}
protected virtual void ReadICollectionInto<T>(EasySaveReader reader, ICollection<T> collection, EasySaveType elementType)
{
ReadICollectionInto(reader, collection, elementType);
}
protected virtual void ReadICollectionInto(EasySaveReader reader, ICollection collection, EasySaveType elementType)
{
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
// Iterate through each item in the collection and try to load it.
foreach(var item in collection)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<object>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
/*
* Calls the Read method using reflection so we don't need to provide a generic parameter.
*/
/*public virtual object Read(EasySaveReader reader)
{
if(readMethod == null)
readMethod = EasySaveReflection.GetMethod(this.GetType(), "Read", new Type[]{elementType.type}, new Type[]{typeof(EasySaveReader)});
return readMethod.Invoke(this, new object[]{reader});
}
public virtual void ReadInto(EasySaveReader reader, object obj)
{
if(readIntoMethod == null)
readIntoMethod = EasySaveReflection.GetMethod(this.GetType(), "ReadInto", new Type[]{elementType.type}, new Type[]{typeof(EasySaveReader), typeof(object)});
readIntoMethod.Invoke(this, new object[]{reader, obj});
}*/
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
using System.Linq;
namespace Convention.EasySave.Types
{
public class EasySave2DArrayType : ECollectionType
{
public EasySave2DArrayType(Type type) : base(type){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode unityObjectType)
{
var array = (System.Array)obj;
if(elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
//writer.StartWriteCollection();
for(int i=0; i < array.GetLength(0); i++)
{
writer.StartWriteCollectionItem(i);
writer.StartWriteCollection();
for(int j=0; j < array.GetLength(1); j++)
{
writer.StartWriteCollectionItem(j);
writer.Write(array.GetValue(i,j), elementType, unityObjectType);
writer.EndWriteCollectionItem(j);
}
writer.EndWriteCollection();
writer.EndWriteCollectionItem(i);
}
//writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
/*if(reader.StartReadCollection())
return null;
// Create a List to store the items as a 1D array, which we can work out the positions of by calculating the lengths of the two dimensions.
var items = new List<T>();
int length1 = 0;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
ReadICollection<T>(reader, items, elementType);
length1++;
if(reader.EndReadCollectionItem())
break;
}
int length2 = items.Count / length1;
var array = new T[length1,length2];
for(int i=0; i<length1; i++)
for(int j=0; j<length2; j++)
array[i,j] = items[ (i * length2) + j ];
return array;*/
}
public override object Read(EasySaveReader reader)
{
if(reader.StartReadCollection())
return null;
// Create a List to store the items as a 1D array, which we can work out the positions of by calculating the lengths of the two dimensions.
var items = new List<object>();
int length1 = 0;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
ReadICollection<object>(reader, items, elementType);
length1++;
if(reader.EndReadCollectionItem())
break;
}
int length2 = items.Count / length1;
var array = EasySaveReflection.ArrayCreateInstance(elementType.type, new int[]{length1, length2});
for(int i=0; i<length1; i++)
for(int j=0; j<length2; j++)
array.SetValue(items[ (i * length2) + j ], i, j);
return array;
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadInto(reader, obj);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
var array = (Array)obj;
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
bool iHasBeenRead = false;
for(int i=0; i < array.GetLength(0); i++)
{
bool jHasBeenRead = false;
if(!reader.StartReadCollectionItem())
throw new IndexOutOfRangeException("The collection we are loading is smaller than the collection provided as a parameter.");
reader.StartReadCollection();
for(int j=0; j < array.GetLength(1); j++)
{
if(!reader.StartReadCollectionItem())
throw new IndexOutOfRangeException("The collection we are loading is smaller than the collection provided as a parameter.");
reader.ReadInto<object>(array.GetValue(i,j), elementType);
jHasBeenRead = reader.EndReadCollectionItem();
}
if(!jHasBeenRead)
throw new IndexOutOfRangeException("The collection we are loading is larger than the collection provided as a parameter.");
reader.EndReadCollection();
iHasBeenRead = reader.EndReadCollectionItem();
}
if(!iHasBeenRead)
throw new IndexOutOfRangeException("The collection we are loading is larger than the collection provided as a parameter.");
reader.EndReadCollection();
}
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySave3DArrayType : ECollectionType
{
public EasySave3DArrayType(Type type) : base(type){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
var array = (System.Array)obj;
if(elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
//writer.StartWriteCollection();
for(int i=0; i < array.GetLength(0); i++)
{
writer.StartWriteCollectionItem(i);
writer.StartWriteCollection();
for(int j=0; j < array.GetLength(1); j++)
{
writer.StartWriteCollectionItem(j);
writer.StartWriteCollection();
for(int k=0; k < array.GetLength(2); k++)
{
writer.StartWriteCollectionItem(k);
writer.Write(array.GetValue(i,j,k), elementType, memberReferenceMode);
writer.EndWriteCollectionItem(k);
}
writer.EndWriteCollection();
writer.EndWriteCollectionItem(j);
}
writer.EndWriteCollection();
writer.EndWriteCollectionItem(i);
}
//writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
}
public override object Read(EasySaveReader reader)
{
if(reader.StartReadCollection())
return null;
// Create a List to store the items as a 1D array, which we can work out the positions of by calculating the lengths of the two dimensions.
var items = new List<object>();
int length1 = 0;
int length2 = 0;
// Iterate through each sub-array
while(true)
{
if(!reader.StartReadCollectionItem())
break;
reader.StartReadCollection();
length1++;
while(true)
{
if(!reader.StartReadCollectionItem())
break;
ReadICollection<object>(reader, items, elementType);
length2++;
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
length2 = length2/length1;
int length3 = items.Count / length2 / length1;
var array = EasySaveReflection.ArrayCreateInstance(elementType.type, new int[]{length1,length2,length3});
for(int i=0; i<length1; i++)
for(int j=0; j<length2; j++)
for(int k=0; k<length3; k++)
array.SetValue(items[i * (length2*length3) + (j * length3) + k], i, j, k);
return array;
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadInto(reader, obj);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
var array = (Array)obj;
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
bool iHasBeenRead = false;
for(int i=0; i < array.GetLength(0); i++)
{
bool jHasBeenRead = false;
if(!reader.StartReadCollectionItem())
throw new IndexOutOfRangeException("The collection we are loading is smaller than the collection provided as a parameter.");
reader.StartReadCollection();
for(int j=0; j < array.GetLength(1); j++)
{
bool kHasBeenRead = false;
if(!reader.StartReadCollectionItem())
throw new IndexOutOfRangeException("The collection we are loading is smaller than the collection provided as a parameter.");
reader.StartReadCollection();
for(int k=0; k < array.GetLength(2); k++)
{
if(!reader.StartReadCollectionItem())
throw new IndexOutOfRangeException("The collection we are loading is smaller than the collection provided as a parameter.");
reader.ReadInto<object>(array.GetValue(i,j,k), elementType);
kHasBeenRead = reader.EndReadCollectionItem();
}
if(!kHasBeenRead)
throw new IndexOutOfRangeException("The collection we are loading is larger than the collection provided as a parameter.");
reader.EndReadCollection();
jHasBeenRead = reader.EndReadCollectionItem();
}
if(!jHasBeenRead)
throw new IndexOutOfRangeException("The collection we are loading is larger than the collection provided as a parameter.");
reader.EndReadCollection();
iHasBeenRead = reader.EndReadCollectionItem();
}
if(!iHasBeenRead)
throw new IndexOutOfRangeException("The collection we are loading is larger than the collection provided as a parameter.");
reader.EndReadCollection();
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySaveArrayType : ECollectionType
{
public EasySaveArrayType(Type type) : base(type){}
public EasySaveArrayType(Type type, EasySaveType elementType) : base(type, elementType){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
var array = (System.Array)obj;
if(elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
//writer.StartWriteCollection();
for(int i=0; i<array.Length; i++)
{
writer.StartWriteCollectionItem(i);
writer.Write(array.GetValue(i), elementType, memberReferenceMode);
writer.EndWriteCollectionItem(i);
}
//writer.EndWriteCollection();
}
public override object Read(EasySaveReader reader)
{
var list = new List<object>();
if (!ReadICollection(reader, list, elementType))
return null;
var array = EasySaveReflection.ArrayCreateInstance(elementType.type, list.Count);
int i = 0;
foreach (var item in list)
{
array.SetValue(item, i);
i++;
}
return array;
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadICollectionInto(reader, (ICollection)obj, elementType);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
var collection = (IList)obj;
if (collection.Count == 0)
ES3Debug.LogWarning("LoadInto/ReadInto expects a collection containing instances to load data in to, but the collection is empty.");
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
// Iterate through each item in the collection and try to load it.
foreach(var item in collection)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<object>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
}
}

View File

@@ -0,0 +1,140 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySaveConcurrentDictionaryType : EasySaveType
{
public EasySaveType keyType;
public EasySaveType valueType;
protected EasySaveReflection.EasySaveReflectedMethod readMethod = null;
protected EasySaveReflection.EasySaveReflectedMethod readIntoMethod = null;
public EasySaveConcurrentDictionaryType(Type type) : base(type)
{
var types = EasySaveReflection.GetElementTypes(type);
keyType = EasySaveTypeMgr.GetOrCreateEasySaveType(types[0], false);
valueType = EasySaveTypeMgr.GetOrCreateEasySaveType(types[1], false);
// If either the key or value type is unsupported, make this type NULL.
if(keyType == null || valueType == null)
isUnsupported = true;;
isDictionary = true;
}
public EasySaveConcurrentDictionaryType(Type type, EasySaveType keyType, EasySaveType valueType) : base(type)
{
this.keyType = keyType;
this.valueType = valueType;
// If either the key or value type is unsupported, make this type NULL.
if (keyType == null || valueType == null)
isUnsupported = true; ;
isDictionary = true;
}
public override void Write(object obj, EasySaveWriter writer)
{
Write(obj, writer, writer.settings.memberReferenceMode);
}
public void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
var dict = (IDictionary)obj;
//writer.StartWriteDictionary(dict.Count);
int i=0;
foreach(System.Collections.DictionaryEntry kvp in dict)
{
writer.StartWriteDictionaryKey(i);
writer.Write(kvp.Key, keyType, memberReferenceMode);
writer.EndWriteDictionaryKey(i);
writer.StartWriteDictionaryValue(i);
writer.Write(kvp.Value, valueType, memberReferenceMode);
writer.EndWriteDictionaryValue(i);
i++;
}
//writer.EndWriteDictionary();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadInto(reader, obj);
}
/*
* Allows us to call the generic Read method using Reflection so we can define the generic parameter at runtime.
* It also caches the method to improve performance in later calls.
*/
public object Read(EasySaveReader reader)
{
if(reader.StartReadDictionary())
return null;
var dict = (IDictionary)EasySaveReflection.CreateInstance(type);
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadDictionaryKey())
return dict;
var key = reader.Read<object>(keyType);
reader.EndReadDictionaryKey();
reader.StartReadDictionaryValue();
var value = reader.Read<object>(valueType);
dict.Add(key,value);
if(reader.EndReadDictionaryValue())
break;
}
reader.EndReadDictionary();
return dict;
}
public void ReadInto(EasySaveReader reader, object obj)
{
if(reader.StartReadDictionary())
throw new NullReferenceException("The Dictionary we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
var dict = (IDictionary)obj;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadDictionaryKey())
return;
var key = reader.Read<object>(keyType);
if(!dict.Contains(key))
throw new KeyNotFoundException("The key \"" + key + "\" in the Dictionary we are loading does not exist in the Dictionary we are loading into");
var value = dict[key];
reader.EndReadDictionaryKey();
reader.StartReadDictionaryValue();
reader.ReadInto<object>(value, valueType);
if(reader.EndReadDictionaryValue())
break;
}
reader.EndReadDictionary();
}
}
}

View File

@@ -0,0 +1,140 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySaveDictionaryType : EasySaveType
{
public EasySaveType keyType;
public EasySaveType valueType;
protected EasySaveReflection.EasySaveReflectedMethod readMethod = null;
protected EasySaveReflection.EasySaveReflectedMethod readIntoMethod = null;
public EasySaveDictionaryType(Type type) : base(type)
{
var types = EasySaveReflection.GetElementTypes(type);
keyType = EasySaveTypeMgr.GetOrCreateEasySaveType(types[0], false);
valueType = EasySaveTypeMgr.GetOrCreateEasySaveType(types[1], false);
// If either the key or value type is unsupported, make this type NULL.
if(keyType == null || valueType == null)
isUnsupported = true;;
isDictionary = true;
}
public EasySaveDictionaryType(Type type, EasySaveType keyType, EasySaveType valueType) : base(type)
{
this.keyType = keyType;
this.valueType = valueType;
// If either the key or value type is unsupported, make this type NULL.
if (keyType == null || valueType == null)
isUnsupported = true; ;
isDictionary = true;
}
public override void Write(object obj, EasySaveWriter writer)
{
Write(obj, writer, writer.settings.memberReferenceMode);
}
public void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
var dict = (IDictionary)obj;
//writer.StartWriteDictionary(dict.Count);
int i=0;
foreach(System.Collections.DictionaryEntry kvp in dict)
{
writer.StartWriteDictionaryKey(i);
writer.Write(kvp.Key, keyType, memberReferenceMode);
writer.EndWriteDictionaryKey(i);
writer.StartWriteDictionaryValue(i);
writer.Write(kvp.Value, valueType, memberReferenceMode);
writer.EndWriteDictionaryValue(i);
i++;
}
//writer.EndWriteDictionary();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadInto(reader, obj);
}
/*
* Allows us to call the generic Read method using Reflection so we can define the generic parameter at runtime.
* It also caches the method to improve performance in later calls.
*/
public object Read(EasySaveReader reader)
{
if(reader.StartReadDictionary())
return null;
var dict = (IDictionary)EasySaveReflection.CreateInstance(type);
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadDictionaryKey())
return dict;
var key = reader.Read<object>(keyType);
reader.EndReadDictionaryKey();
reader.StartReadDictionaryValue();
var value = reader.Read<object>(valueType);
dict.Add(key,value);
if(reader.EndReadDictionaryValue())
break;
}
reader.EndReadDictionary();
return dict;
}
public void ReadInto(EasySaveReader reader, object obj)
{
if(reader.StartReadDictionary())
throw new NullReferenceException("The Dictionary we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
var dict = (IDictionary)obj;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadDictionaryKey())
return;
var key = reader.Read<object>(keyType);
if(!dict.Contains(key))
throw new KeyNotFoundException("The key \"" + key + "\" in the Dictionary we are loading does not exist in the Dictionary we are loading into");
var value = dict[key];
reader.EndReadDictionaryKey();
reader.StartReadDictionaryValue();
reader.ReadInto<object>(value, valueType);
if(reader.EndReadDictionaryValue())
break;
}
reader.EndReadDictionary();
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
using System.Linq;
using System.Reflection;
namespace Convention.EasySave.Types
{
public class EasySaveHashSetType : ECollectionType
{
public EasySaveHashSetType(Type type) : base(type){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
if (obj == null) { writer.WriteNull(); return; };
var list = (IEnumerable)obj;
if (elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
int count = 0;
foreach (var item in list)
count++;
//writer.StartWriteCollection(count);
int i = 0;
foreach (object item in list)
{
writer.StartWriteCollectionItem(i);
writer.Write(item, elementType, memberReferenceMode);
writer.EndWriteCollectionItem(i);
i++;
}
//writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
var val = Read(reader);
if (val == null)
return default(T);
return (T)val;
}
public override object Read(EasySaveReader reader)
{
/*var method = typeof(ECollectionType).GetMethod("ReadICollection", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(elementType.type);
if(!(bool)method.Invoke(this, new object[] { reader, list, elementType }))
return null;*/
var genericParam = EasySaveReflection.GetGenericArguments(type)[0];
var listType = EasySaveReflection.MakeGenericType(typeof(List<>), genericParam);
var list = (IList)EasySaveReflection.CreateInstance(listType);
if (!reader.StartReadCollection())
{
// Iterate through each character until we reach the end of the array.
while (true)
{
if (!reader.StartReadCollectionItem())
break;
list.Add(reader.Read<object>(elementType));
if (reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
}
return EasySaveReflection.CreateInstance(type, list);
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadInto(reader, obj);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
throw new NotImplementedException("Cannot use LoadInto/ReadInto with HashSet because HashSets do not maintain the order of elements");
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySaveListType : ECollectionType
{
public EasySaveListType(Type type) : base(type){}
public EasySaveListType(Type type, EasySaveType elementType) : base(type, elementType){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
if(obj == null){ writer.WriteNull(); return; };
var list = (IList)obj;
if(elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
//writer.StartWriteCollection();
int i = 0;
foreach(object item in list)
{
writer.StartWriteCollectionItem(i);
writer.Write(item, elementType, memberReferenceMode);
writer.EndWriteCollectionItem(i);
i++;
}
//writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
/*var list = new List<T>();
if(!ReadICollection<T>(reader, list, elementType))
return null;
return list;*/
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadICollectionInto(reader, (ICollection)obj, elementType);
}
public override object Read(EasySaveReader reader)
{
var instance = (IList)EasySaveReflection.CreateInstance(type);
if(reader.StartReadCollection())
return null;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
instance.Add(reader.Read<object>(elementType));
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
return instance;
}
public override void ReadInto(EasySaveReader reader, object obj)
{
var collection = (IList)obj;
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
// Iterate through each item in the collection and try to load it.
foreach(var item in collection)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<object>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
using System.Linq;
namespace Convention.EasySave.Types
{
public class EasySaveNativeArrayType : ECollectionType
{
public EasySaveNativeArrayType(Type type) : base(type){}
public EasySaveNativeArrayType(Type type, EasySaveType elementType) : base(type, elementType){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
if (elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
var enumerable = (IEnumerable)obj;
int i = 0;
foreach(var item in enumerable)
{
writer.StartWriteCollectionItem(i);
writer.Write(item, elementType, memberReferenceMode);
writer.EndWriteCollectionItem(i);
i++;
}
}
public override object Read(EasySaveReader reader)
{
var array = ReadAsArray(reader);
return EasySaveReflection.CreateInstance(type, new object[] { array, Allocator.Persistent });
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
ReadInto(reader, obj);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
var array = ReadAsArray(reader);
var copyFromMethod = EasySaveReflection.GetMethods(type, "CopyFrom").First(m => EasySaveReflection.TypeIsArray(m.GetParameters()[0].GetType()));
copyFromMethod.Invoke(obj, new object[] { array });
}
private System.Array ReadAsArray(EasySaveReader reader)
{
var list = new List<object>();
if (!ReadICollection(reader, list, elementType))
return null;
var array = EasySaveReflection.ArrayCreateInstance(elementType.type, list.Count);
int i = 0;
foreach (var item in list)
{
array.SetValue(item, i);
i++;
}
return array;
}
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySaveQueueType : ECollectionType
{
public EasySaveQueueType(Type type) : base(type){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
var list = (ICollection)obj;
if(elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
//writer.StartWriteCollection();
int i = 0;
foreach(object item in list)
{
writer.StartWriteCollectionItem(i);
writer.Write(item, elementType, memberReferenceMode);
writer.EndWriteCollectionItem(i);
i++;
}
//writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
/*if(reader.StartReadCollection())
return null;
var queue = new Queue<T>();
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
queue.Enqueue(reader.Read<T>(elementType));
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
return queue;*/
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
var queue = (Queue<T>)obj;
// Iterate through each item in the collection and try to load it.
foreach(var item in queue)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<T>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == queue.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != queue.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
public override object Read(EasySaveReader reader)
{
var instance = (IList)EasySaveReflection.CreateInstance(EasySaveReflection.MakeGenericType(typeof(List<>), elementType.type));
if(reader.StartReadCollection())
return null;
// Iterate through each character until we reach the end of the array.
while(true)
{
if (!reader.StartReadCollectionItem())
break;
instance.Add(reader.Read<object>(elementType));
if (reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
return EasySaveReflection.CreateInstance(type, instance);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
var collection = (ICollection)obj;
// Iterate through each item in the collection and try to load it.
foreach(var item in collection)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<object>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
using System.Reflection;
using System.Linq;
namespace Convention.EasySave.Types
{
public class EasySaveStackType : ECollectionType
{
public EasySaveStackType(Type type) : base(type){}
public override void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
var list = (ICollection)obj;
if(elementType == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
//writer.StartWriteCollection();
int i = 0;
foreach(object item in list)
{
writer.StartWriteCollectionItem(i);
writer.Write(item, elementType, memberReferenceMode);
writer.EndWriteCollectionItem(i);
i++;
}
//writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
return Read(reader);
/*if(reader.StartReadCollection())
return null;
var stack = new Stack<T>();
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
stack.Push(reader.Read<T>(elementType));
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
return stack;*/
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
var stack = (Stack<T>)obj;
// Iterate through each item in the collection and try to load it.
foreach(var item in stack)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<T>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == stack.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != stack.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
public override object Read(EasySaveReader reader)
{
var instance = (IList)EasySaveReflection.CreateInstance(EasySaveReflection.MakeGenericType(typeof(List<>), elementType.type));
if(reader.StartReadCollection())
return null;
// Iterate through each character until we reach the end of the array.
while(true)
{
if(!reader.StartReadCollectionItem())
break;
instance.Add(reader.Read<object>(elementType));
if(reader.EndReadCollectionItem())
break;
}
reader.EndReadCollection();
EasySaveReflection.GetMethods(instance.GetType(), "Reverse").FirstOrDefault(t => !t.IsStatic).Invoke(instance, new object[]{});
return EasySaveReflection.CreateInstance(type, instance);
}
public override void ReadInto(EasySaveReader reader, object obj)
{
if(reader.StartReadCollection())
throw new NullReferenceException("The Collection we are trying to load is stored as null, which is not allowed when using ReadInto methods.");
int itemsLoaded = 0;
var collection = (ICollection)obj;
// Iterate through each item in the collection and try to load it.
foreach(var item in collection)
{
itemsLoaded++;
if(!reader.StartReadCollectionItem())
break;
reader.ReadInto<object>(item, elementType);
// If we find a ']', we reached the end of the array.
if(reader.EndReadCollectionItem())
break;
// If there's still items to load, but we've reached the end of the collection we're loading into, throw an error.
if(itemsLoaded == collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is longer than the collection provided as a parameter.");
}
// If we loaded fewer items than the parameter collection, throw index out of range exception.
if(itemsLoaded != collection.Count)
throw new IndexOutOfRangeException("The collection we are loading is shorter than the collection provided as a parameter.");
reader.EndReadCollection();
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public class EasySaveTupleType : EasySaveType
{
public EasySaveType[] es3Types;
public Type[] types;
protected EasySaveReflection.EasySaveReflectedMethod readMethod = null;
protected EasySaveReflection.EasySaveReflectedMethod readIntoMethod = null;
public EasySaveTupleType(Type type) : base(type)
{
types = EasySaveReflection.GetElementTypes(type);
es3Types = new EasySaveType[types.Length];
for(int i=0; i<types.Length; i++)
{
es3Types[i] = EasySaveTypeMgr.GetOrCreateEasySaveType(types[i], false);
if (es3Types[i] == null)
isUnsupported = true;
}
isTuple = true;
}
public override void Write(object obj, EasySaveWriter writer)
{
Write(obj, writer, writer.settings.memberReferenceMode);
}
public void Write(object obj, EasySaveWriter writer, EasySave.ReferenceMode memberReferenceMode)
{
if (obj == null) { writer.WriteNull(); return; };
writer.StartWriteCollection();
for (int i=0; i<es3Types.Length; i++)
{
var itemProperty = EasySaveReflection.GetProperty(type, "Item"+(i+1));
var item = itemProperty.GetValue(obj);
writer.StartWriteCollectionItem(i);
writer.Write(item, es3Types[i], memberReferenceMode);
writer.EndWriteCollectionItem(i);
}
writer.EndWriteCollection();
}
public override object Read<T>(EasySaveReader reader)
{
var objects = new object[types.Length];
if (reader.StartReadCollection())
return null;
for(int i=0; i<types.Length; i++)
{
reader.StartReadCollectionItem();
objects[i] = reader.Read<object>(es3Types[i]);
reader.EndReadCollectionItem();
}
reader.EndReadCollection();
var constructor = type.GetConstructor(types);
var instance = constructor.Invoke(objects);
return instance;
}
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
public abstract class EasySaveObjectType : EasySaveType
{
public EasySaveObjectType(Type type) : base(type) {}
protected abstract void WriteObject(object obj, EasySaveWriter writer);
protected abstract object ReadObject<T>(EasySaveReader reader);
protected virtual void ReadObject<T>(EasySaveReader reader, object obj)
{
throw new NotSupportedException("ReadInto is not supported for type "+type);
}
public override void Write(object obj, EasySaveWriter writer)
{
if (!WriteUsingDerivedType(obj, writer))
{
var baseType = EasySaveReflection.BaseType(obj.GetType());
if (baseType != typeof(object))
{
var es3Type = EasySaveTypeMgr.GetOrCreateEasySaveType(baseType, false);
// If it's a Dictionary or Collection, we need to write it as a field with a property name.
if (es3Type != null && (es3Type.isDictionary || es3Type.isCollection))
writer.WriteProperty("_Values", obj, es3Type);
}
WriteObject(obj, writer);
}
}
public override object Read<T>(EasySaveReader reader)
{
string propertyName;
while(true)
{
propertyName = ReadPropertyName(reader);
if(propertyName == EasySaveType.typeFieldName)
return EasySaveTypeMgr.GetOrCreateEasySaveType(reader.ReadType()).Read<T>(reader);
else
{
reader.overridePropertiesName = propertyName;
return ReadObject<T>(reader);
}
}
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
string propertyName;
while(true)
{
propertyName = ReadPropertyName(reader);
if(propertyName == EasySaveType.typeFieldName)
{
EasySaveTypeMgr.GetOrCreateEasySaveType(reader.ReadType()).ReadInto<T>(reader, obj);
return;
}
// This is important we return if the enumerator returns null, otherwise we will encounter an endless cycle.
else if (propertyName == null)
return;
else
{
reader.overridePropertiesName = propertyName;
ReadObject<T>(reader, obj);
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.ComponentModel;
namespace Convention.EasySave.Internal
{
public class EasySaveMember
{
public string name;
public Type type;
public bool isProperty;
public EasySaveReflection.EasySaveReflectedMember reflectedMember;
public bool useReflection = false;
public EasySaveMember(string name, Type type, bool isProperty)
{
this.name = name;
this.type = type;
this.isProperty = isProperty;
}
public EasySaveMember(EasySaveReflection.EasySaveReflectedMember reflectedMember)
{
this.reflectedMember = reflectedMember;
this.name = reflectedMember.Name;
this.type = reflectedMember.MemberType;
this.isProperty = reflectedMember.isProperty;
this.useReflection = true;
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
using System.Linq;
namespace Convention.EasySave.Types
{
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public abstract class EasySaveType
{
public const string typeFieldName = "__type";
public EasySaveMember[] members;
public Type type;
public bool isPrimitive = false;
public bool isValueType = false;
public bool isCollection = false;
public bool isDictionary = false;
public bool isTuple = false;
public bool isEnum = false;
public bool isES3TypeUnityObject = false;
public bool isReflectedType = false;
public bool isUnsupported = false;
public int priority = 0;
protected EasySaveType(Type type)
{
EasySaveTypeMgr.Add(type, this);
this.type = type;
this.isValueType = EasySaveReflection.IsValueType(type);
}
public abstract void Write(object obj, EasySaveWriter writer);
public abstract object Read<T>(EasySaveReader reader);
public virtual void ReadInto<T>(EasySaveReader reader, object obj)
{
throw new NotImplementedException("Self-assigning Read is not implemented or supported on this type.");
}
protected bool WriteUsingDerivedType(object obj, EasySaveWriter writer)
{
var objType = obj.GetType();
if(objType != this.type)
{
writer.WriteType(objType);
EasySaveTypeMgr.GetOrCreateEasySaveType(objType).Write(obj, writer);
return true;
}
return false;
}
protected void ReadUsingDerivedType<T>(EasySaveReader reader, object obj)
{
EasySaveTypeMgr.GetOrCreateEasySaveType(reader.ReadType()).ReadInto<T>(reader, obj);
}
internal string ReadPropertyName(EasySaveReader reader)
{
if(reader.overridePropertiesName != null)
{
string propertyName = reader.overridePropertiesName;
reader.overridePropertiesName = null;
return propertyName;
}
return reader.ReadPropertyName();
}
#region Reflection Methods
protected void WriteProperties(object obj, EasySaveWriter writer)
{
if(members == null)
GetMembers(writer.settings.safeReflection);
for(int i=0; i<members.Length; i++)
{
var property = members[i];
writer.WriteProperty(property.name, property.reflectedMember.GetValue(obj), EasySaveTypeMgr.GetOrCreateEasySaveType(property.type), writer.settings.memberReferenceMode);
}
}
protected object ReadProperties(EasySaveReader reader, object obj)
{
// Iterate through each property in the file and try to load it using the appropriate
// EasySaveMember in the members array.
foreach (string propertyName in reader.Properties)
{
// Find the property.
EasySaveMember property = null;
for(int i=0; i<members.Length; i++)
{
if(members[i].name == propertyName)
{
property = members[i];
break;
}
}
// If this is a class which derives directly from a Collection, we need to load it's dictionary first.
if(propertyName == "_Values")
{
var baseType = EasySaveTypeMgr.GetOrCreateEasySaveType(EasySaveReflection.BaseType(obj.GetType()));
if(baseType.isDictionary)
{
var dict = (IDictionary)obj;
var loaded = (IDictionary)baseType.Read<IDictionary>(reader);
foreach (DictionaryEntry kvp in loaded)
dict[kvp.Key] = kvp.Value;
}
else if(baseType.isCollection)
{
var loaded = (IEnumerable)baseType.Read<IEnumerable>(reader);
var type = baseType.GetType();
if (type == typeof(EasySaveListType))
foreach (var item in loaded)
((IList)obj).Add(item);
else if (type == typeof(EasySaveQueueType))
{
var method = baseType.type.GetMethod("Enqueue");
foreach (var item in loaded)
method.Invoke(obj, new object[] { item });
}
else if (type == typeof(EasySaveStackType))
{
var method = baseType.type.GetMethod("Push");
foreach (var item in loaded)
method.Invoke(obj, new object[] { item });
}
else if (type == typeof(EasySaveHashSetType))
{
var method = baseType.type.GetMethod("Add");
foreach (var item in loaded)
method.Invoke(obj, new object[] { item });
}
}
}
if (property == null)
reader.Skip();
else
{
var type = EasySaveTypeMgr.GetOrCreateEasySaveType(property.type);
if(EasySaveReflection.IsAssignableFrom(typeof(EasySaveDictionaryType), type.GetType()))
property.reflectedMember.SetValue(obj, ((EasySaveDictionaryType)type).Read(reader));
else if(EasySaveReflection.IsAssignableFrom(typeof(ECollectionType), type.GetType()))
property.reflectedMember.SetValue(obj, ((ECollectionType)type).Read(reader));
else
{
object readObj = reader.Read<object>(type);
property.reflectedMember.SetValue(obj, readObj);
}
}
}
return obj;
}
protected void GetMembers(bool safe)
{
GetMembers(safe, null);
}
protected void GetMembers(bool safe, string[] memberNames)
{
var serializedMembers = EasySaveReflection.GetSerializableMembers(type, safe, memberNames);
members = new EasySaveMember[serializedMembers.Length];
for(int i=0; i<serializedMembers.Length; i++)
members[i] = new EasySaveMember(serializedMembers[i]);
}
#endregion
}
[AttributeUsage(AttributeTargets.Class)]
public class EasySavePropertiesAttribute : System.Attribute
{
public readonly string[] members;
public EasySavePropertiesAttribute(params string[] members)
{
this.members = members;
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Types;
namespace Convention.EasySave.Internal
{
public static class EasySaveTypeMgr
{
private static object _lock = new object();
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static Dictionary<Type, EasySaveType> types = null;
// We cache the last accessed type as we quite often use the same type multiple times,
// so this improves performance as another lookup is not required.
private static EasySaveType lastAccessedType = null;
public static EasySaveType GetOrCreateEasySaveType(Type type, bool throwException = true)
{
if(types == null)
Init();
if (type != typeof(object) && lastAccessedType != null && lastAccessedType.type == type)
return lastAccessedType;
// If type doesn't exist, create one.
if(types.TryGetValue(type, out lastAccessedType))
return lastAccessedType;
return (lastAccessedType = CreateES3Type(type, throwException));
}
public static EasySaveType GetES3Type(Type type)
{
if(types == null)
Init();
if(types.TryGetValue(type, out lastAccessedType))
return lastAccessedType;
return null;
}
internal static void Add(Type type, EasySaveType es3Type)
{
if(types == null)
Init();
var existingType = GetES3Type(type);
if (existingType != null && existingType.priority > es3Type.priority)
return;
lock (_lock)
{
types[type] = es3Type;
}
}
internal static EasySaveType CreateES3Type(Type type, bool throwException = true)
{
EasySaveType es3Type;
if(EasySaveReflection.IsEnum(type))
return new EasySaveType_enum(type);
else if(EasySaveReflection.TypeIsArray(type))
{
int rank = EasySaveReflection.GetArrayRank(type);
if(rank == 1)
es3Type = new EasySaveArrayType(type);
else if(rank == 2)
es3Type = new EasySave2DArrayType(type);
else if(rank == 3)
es3Type = new EasySave3DArrayType(type);
else if(throwException)
throw new NotSupportedException("Only arrays with up to three dimensions are supported by Easy Save.");
else
return null;
}
else if(EasySaveReflection.IsGenericType(type) && EasySaveReflection.ImplementsInterface(type, typeof(IEnumerable)))
{
Type genericType = EasySaveReflection.GetGenericTypeDefinition(type);
if (typeof(List<>).IsAssignableFrom(genericType))
es3Type = new EasySaveListType(type);
else if (typeof(Dictionary<,>).IsAssignableFrom(genericType))
es3Type = new EasySaveDictionaryType(type);
else if (genericType == typeof(Queue<>))
es3Type = new EasySaveQueueType(type);
else if (genericType == typeof(Stack<>))
es3Type = new EasySaveStackType(type);
else if (genericType == typeof(HashSet<>))
es3Type = new EasySaveHashSetType(type);
else if (genericType == typeof(Unity.Collections.NativeArray<>))
es3Type = new EasySaveNativeArrayType(type);
else if (throwException)
throw new NotSupportedException("Generic type \"" + type.ToString() + "\" is not supported by Easy Save.");
else
return null;
}
else if(EasySaveReflection.IsPrimitive(type)) // ERROR: We should not have to create an EasySaveType for a primitive.
{
if(types == null || types.Count == 0) // If the type list is not initialised, it is most likely an initialisation error.
throw new TypeLoadException("EasySaveType for primitive could not be found, and the type list is empty. Please contact Easy Save developers at http://www.moodkie.com/contact");
else // Else it's a different error, possibly an error in the specific EasySaveType for that type.
throw new TypeLoadException("EasySaveType for primitive could not be found, but the type list has been initialised and is not empty. Please contact Easy Save developers on mail@moodkie.com");
}
else
{
if (EasySaveReflection.IsAssignableFrom(typeof(Component), type))
es3Type = new ES3ReflectedComponentType(type);
else if (EasySaveReflection.IsValueType(type))
es3Type = new EasySaveReflectedValueType(type);
else if (EasySaveReflection.IsAssignableFrom(typeof(ScriptableObject), type))
es3Type = new ES3ReflectedScriptableObjectType(type);
else if (EasySaveReflection.IsAssignableFrom(typeof(UnityEngine.Object), type))
es3Type = new ES3ReflectedUnityObjectType(type);
/*else if (EasySaveReflection.HasParameterlessConstructor(type) || EasySaveReflection.IsAbstract(type) || EasySaveReflection.IsInterface(type))
es3Type = new EasySaveReflectedObjectType(type);*/
else if (type.Name.StartsWith("Tuple`"))
es3Type = new EasySaveTupleType(type);
/*else if (throwException)
throw new NotSupportedException("Type of " + type + " is not supported as it does not have a parameterless constructor. Only value types, Components or ScriptableObjects are supportable without a parameterless constructor. However, you may be able to create an EasySaveType script to add support for it.");*/
else
es3Type = new EasySaveReflectedObjectType(type);
}
if(es3Type.type == null || es3Type.isUnsupported)
{
if(throwException)
throw new NotSupportedException(string.Format("EasySaveType.type is null when trying to create an EasySaveType for {0}, possibly because the element type is not supported.", type));
return null;
}
Add(type, es3Type);
return es3Type;
}
internal static void Init()
{
lock (_lock)
{
types = new Dictionary<Type, EasySaveType>();
// Convention.EasySave.Types add themselves to the types Dictionary.
EasySaveReflection.GetInstances<EasySaveType>();
// Check that the type list was initialised correctly.
if (types == null || types.Count == 0)
throw new TypeLoadException("Type list could not be initialised. Please contact Easy Save developers on mail@moodkie.com.");
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Numerics;
namespace Convention.EasySave.Types
{
[EasySaveProperties("bytes")]
public class EasySaveType_BigInteger : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_BigInteger() : base(typeof(BigInteger))
{
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
BigInteger casted = (BigInteger)obj;
writer.WriteProperty("bytes", casted.ToByteArray(), EasySaveType_byteArray.Instance);
}
public override object Read<T>(EasySaveReader reader)
{
return new BigInteger(reader.ReadProperty<byte[]>(EasySaveType_byteArray.Instance));
}
}
public class EasySaveType_BigIntegerArray : EasySaveArrayType
{
public static EasySaveType Instance;
public EasySaveType_BigIntegerArray() : base(typeof(BigInteger[]), EasySaveType_BigInteger.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* System.Random is no longer serializable at runtime due to Unity changing the implementation away from .NET.
*/
using System;
namespace Convention.EasySave.Types
{
[EasySaveProperties("inext", "inextp", "SeedArray")]
public class EasySaveType_Random : EasySaveObjectType
{
public static EasySaveType Instance = null;
public EasySaveType_Random() : base(typeof(System.Random)){ Instance = this; }
protected override void WriteObject(object obj, EasySaveWriter writer)
{
var instance = (System.Random)obj;
writer.WritePrivateField("inext", instance);
writer.WritePrivateField("inextp", instance);
writer.WritePrivateField("SeedArray", instance);
}
protected override void ReadObject<T>(EasySaveReader reader, object obj)
{
var instance = (System.Random)obj;
foreach(string propertyName in reader.Properties)
{
switch(propertyName)
{
case "inext":
reader.SetPrivateField("inext", reader.Read<System.Int32>(), instance);
break;
case "inextp":
reader.SetPrivateField("inextp", reader.Read<System.Int32>(), instance);
break;
case "SeedArray":
reader.SetPrivateField("SeedArray", reader.Read<System.Int32[]>(), instance);
break;
default:
reader.Skip();
break;
}
}
}
protected override object ReadObject<T>(EasySaveReader reader)
{
var instance = new System.Random();
ReadObject<T>(reader, instance);
return instance;
}
}
public class EasySaveType_RandomArray : EasySaveArrayType
{
public static EasySaveType Instance;
public EasySaveType_RandomArray() : base(typeof(System.Random[]), EasySaveType_Random.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Convention.EasySave.Types
{
[EasySaveProperties()]
public class EasySaveType_Type : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_Type() : base(typeof(System.Type))
{
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
Type type = (Type)obj;
writer.WriteProperty("assemblyQualifiedName", type.AssemblyQualifiedName);
}
public override object Read<T>(EasySaveReader reader)
{
return Type.GetType(reader.ReadProperty<string>());
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_DateTime : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_DateTime() : base(typeof(DateTime))
{
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WriteProperty("ticks", ((DateTime)obj).Ticks, EasySaveType_long.Instance);
}
public override object Read<T>(EasySaveReader reader)
{
reader.ReadPropertyName();
return new DateTime(reader.Read<long>(EasySaveType_long.Instance));
}
}
public class ES3Type_DateTimeArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_DateTimeArray() : base(typeof(DateTime[]), EasySaveType_DateTime.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
namespace Convention.EasySave.Types
{
public class EasySaveType_EasySaveRef : EasySaveType
{
public static EasySaveType Instance = new EasySaveType_EasySaveRef();
public EasySaveType_EasySaveRef() : base(typeof(long))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive(((long)obj).ToString());
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)new ES3Ref(reader.Read_ref());
}
}
public class ES3Type_ES3RefArray : EasySaveArrayType
{
public static EasySaveType Instance = new ES3Type_ES3RefArray();
public ES3Type_ES3RefArray() : base(typeof(ES3Ref[]), EasySaveType_EasySaveRef.Instance)
{
Instance = this;
}
}
public class ES3Type_ES3RefDictionary : EasySaveDictionaryType
{
public static EasySaveType Instance = new ES3Type_ES3RefDictionary();
public ES3Type_ES3RefDictionary() : base(typeof(Dictionary<ES3Ref, ES3Ref>), EasySaveType_EasySaveRef.Instance, EasySaveType_EasySaveRef.Instance)
{
Instance = this;
}
}
}
public class ES3Ref
{
public long id;
public ES3Ref(long id)
{
this.id = id;
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_IntPtr : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_IntPtr() : base(typeof(IntPtr))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((long)(IntPtr)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)(IntPtr)reader.Read_long();
}
}
public class ES3Type_IntPtrArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_IntPtrArray() : base(typeof(IntPtr[]), EasySaveType_IntPtr.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_UIntPtr : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_UIntPtr() : base(typeof(UIntPtr))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((ulong)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (object)reader.Read_ulong();
}
}
public class ES3Type_UIntPtrArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_UIntPtrArray() : base(typeof(UIntPtr[]), EasySaveType_UIntPtr.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_bool : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_bool() : base(typeof(bool))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((bool)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_bool();
}
}
public class ES3Type_boolArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_boolArray() : base(typeof(bool[]), EasySaveType_bool.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_byte : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_byte() : base(typeof(byte))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((byte)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_byte();
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_byteArray : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_byteArray() : base(typeof(byte[]))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((byte[])obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_byteArray();
}
}
}

View File

@@ -0,0 +1,32 @@
namespace Convention.EasySave.Types
{
public class EasySaveType_char : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_char() : base(typeof(char))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((char)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_char();
}
}
public class ES3Type_charArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_charArray() : base(typeof(char[]), EasySaveType_char.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_decimal : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_decimal() : base(typeof(decimal))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((decimal)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_decimal();
}
}
public class ES3Type_decimalArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_decimalArray() : base(typeof(decimal[]), EasySaveType_decimal.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_double : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_double() : base(typeof(double))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((double)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_double();
}
}
public class ES3Type_doubleArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_doubleArray() : base(typeof(double[]), EasySaveType_double.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_enum : EasySaveType
{
public static EasySaveType Instance = null;
private Type underlyingType = null;
public EasySaveType_enum(Type type) : base(type)
{
isPrimitive = true;
isEnum = true;
Instance = this;
underlyingType = Enum.GetUnderlyingType(type);
}
public override void Write(object obj, EasySaveWriter writer)
{
if(underlyingType == typeof(int)) writer.WritePrimitive((int)obj);
else if(underlyingType == typeof(bool)) writer.WritePrimitive((bool)obj);
else if(underlyingType == typeof(byte)) writer.WritePrimitive((byte)obj);
else if(underlyingType == typeof(char)) writer.WritePrimitive((char)obj);
else if(underlyingType == typeof(decimal)) writer.WritePrimitive((decimal)obj);
else if(underlyingType == typeof(double)) writer.WritePrimitive((double)obj);
else if(underlyingType == typeof(float)) writer.WritePrimitive((float)obj);
else if(underlyingType == typeof(long)) writer.WritePrimitive((long)obj);
else if(underlyingType == typeof(sbyte)) writer.WritePrimitive((sbyte)obj);
else if(underlyingType == typeof(short)) writer.WritePrimitive((short)obj);
else if(underlyingType == typeof(uint)) writer.WritePrimitive((uint)obj);
else if(underlyingType == typeof(ulong)) writer.WritePrimitive((ulong)obj);
else if(underlyingType == typeof(ushort)) writer.WritePrimitive((ushort)obj);
else
throw new System.InvalidCastException("The underlying type " + underlyingType + " of Enum "+type+" is not supported");
}
public override object Read<T>(EasySaveReader reader)
{
if(underlyingType == typeof(int)) return Enum.ToObject (type, reader.Read_int());
else if(underlyingType == typeof(bool)) return Enum.ToObject (type, reader.Read_bool());
else if(underlyingType == typeof(byte)) return Enum.ToObject (type, reader.Read_byte());
else if(underlyingType == typeof(char)) return Enum.ToObject (type, reader.Read_char());
else if(underlyingType == typeof(decimal)) return Enum.ToObject (type, reader.Read_decimal());
else if(underlyingType == typeof(double)) return Enum.ToObject (type, reader.Read_double());
else if(underlyingType == typeof(float)) return Enum.ToObject (type, reader.Read_float());
else if(underlyingType == typeof(long)) return Enum.ToObject (type, reader.Read_long());
else if(underlyingType == typeof(sbyte)) return Enum.ToObject (type, reader.Read_sbyte());
else if(underlyingType == typeof(short)) return Enum.ToObject (type, reader.Read_short());
else if(underlyingType == typeof(uint)) return Enum.ToObject (type, reader.Read_uint());
else if(underlyingType == typeof(ulong)) return Enum.ToObject (type, reader.Read_ulong());
else if(underlyingType == typeof(ushort)) return Enum.ToObject (type, reader.Read_ushort());
else
throw new System.InvalidCastException("The underlying type " + underlyingType + " of Enum "+type+" is not supported");
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_float : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_float() : base(typeof(float))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((float)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_float();
}
}
public class ES3Type_floatArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_floatArray() : base(typeof(float[]), EasySaveType_float.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_int : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_int() : base(typeof(int))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((int)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_int();
}
}
public class ES3Type_intArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_intArray() : base(typeof(int[]), EasySaveType_int.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_long : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_long() : base(typeof(long))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((long)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_long();
}
}
public class ES3Type_longArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_longArray() : base(typeof(long[]), EasySaveType_long.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_sbyte : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_sbyte() : base(typeof(sbyte))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((sbyte)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_sbyte();
}
}
public class ES3Type_sbyteArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_sbyteArray() : base(typeof(sbyte[]), EasySaveType_sbyte.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_short : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_short() : base(typeof(short))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((short)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_short();
}
}
public class ES3Type_shortArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_shortArray() : base(typeof(short[]), EasySaveType_short.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_string : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_string() : base(typeof(string))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((string)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return reader.Read_string();
}
}
public class ES3Type_StringArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_StringArray() : base(typeof(string[]), EasySaveType_string.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_uint : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_uint() : base(typeof(uint))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((uint)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_uint();
}
}
public class ES3Type_uintArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_uintArray() : base(typeof(uint[]), EasySaveType_uint.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_ulong : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_ulong() : base(typeof(ulong))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((ulong)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_ulong();
}
}
public class ES3Type_ulongArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_ulongArray() : base(typeof(ulong[]), EasySaveType_ulong.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace Convention.EasySave.Types
{
public class EasySaveType_ushort : EasySaveType
{
public static EasySaveType Instance = null;
public EasySaveType_ushort() : base(typeof(ushort))
{
isPrimitive = true;
Instance = this;
}
public override void Write(object obj, EasySaveWriter writer)
{
writer.WritePrimitive((ushort)obj);
}
public override object Read<T>(EasySaveReader reader)
{
return (T)(object)reader.Read_ushort();
}
}
public class ES3Type_ushortArray : EasySaveArrayType
{
public static EasySaveType Instance;
public ES3Type_ushortArray() : base(typeof(ushort[]), EasySaveType_ushort.Instance)
{
Instance = this;
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
internal class EasySaveReflectedObjectType : EasySaveObjectType
{
public EasySaveReflectedObjectType(Type type) : base(type)
{
isReflectedType = true;
GetMembers(true);
}
protected override void WriteObject(object obj, EasySaveWriter writer)
{
WriteProperties(obj, writer);
}
protected override object ReadObject<T>(EasySaveReader reader)
{
var obj = EasySaveReflection.CreateInstance(this.type);
ReadProperties(reader, obj);
return obj;
}
protected override void ReadObject<T>(EasySaveReader reader, object obj)
{
ReadProperties(reader, obj);
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.EasySave.Internal;
namespace Convention.EasySave.Types
{
internal class EasySaveReflectedValueType : EasySaveType
{
public EasySaveReflectedValueType(Type type) : base(type)
{
isReflectedType = true;
GetMembers(true);
}
public override void Write(object obj, EasySaveWriter writer)
{
WriteProperties(obj, writer);
}
public override object Read<T>(EasySaveReader reader)
{
var obj = EasySaveReflection.CreateInstance(this.type);
if(obj == null)
throw new NotSupportedException("Cannot create an instance of "+this.type+". However, you may be able to add support for it using a custom EasySaveType file. For more information see: http://docs.moodkie.com/easy-save-3/es3-guides/controlling-serialization-using-es3types/");
// Make sure we return the result of ReadProperties as properties aren't assigned by reference.
return ReadProperties(reader, obj);
}
public override void ReadInto<T>(EasySaveReader reader, object obj)
{
throw new NotSupportedException("Cannot perform self-assigning load on a value type.");
}
}
}

View File

@@ -0,0 +1,121 @@
using System.Collections;
using System.Collections.Generic;
using System;
namespace Convention.EasySave.Internal
{
internal enum EasySaveSpecialByte : byte
{
Null = 0,
Bool = 1,
Byte = 2,
Sbyte = 3,
Char = 4,
Decimal = 5,
Double = 6,
Float = 7,
Int = 8,
Uint = 9,
Long = 10,
Ulong = 11,
Short = 12,
Ushort = 13,
String = 14,
ByteArray = 15,
Collection = 128,
Dictionary = 129,
CollectionItem = 130,
Object = 254,
Terminator = 255
}
internal static class EasySaveBinary
{
internal const string ObjectTerminator = ".";
internal static readonly Dictionary<EasySaveSpecialByte, Type> IdToType = new Dictionary<EasySaveSpecialByte, Type>()
{
{ EasySaveSpecialByte.Null, null },
{ EasySaveSpecialByte.Bool, typeof(bool)},
{ EasySaveSpecialByte.Byte, typeof(byte)},
{ EasySaveSpecialByte.Sbyte, typeof(sbyte)},
{ EasySaveSpecialByte.Char, typeof(char)},
{ EasySaveSpecialByte.Decimal, typeof(decimal)},
{ EasySaveSpecialByte.Double, typeof(double)},
{ EasySaveSpecialByte.Float, typeof(float)},
{ EasySaveSpecialByte.Int, typeof(int)},
{ EasySaveSpecialByte.Uint, typeof(uint)},
{ EasySaveSpecialByte.Long, typeof(long)},
{ EasySaveSpecialByte.Ulong, typeof(ulong)},
{ EasySaveSpecialByte.Short, typeof(short)},
{ EasySaveSpecialByte.Ushort, typeof(ushort)},
{ EasySaveSpecialByte.String, typeof(string)},
{ EasySaveSpecialByte.ByteArray, typeof(byte[])}
};
internal static readonly Dictionary<Type, EasySaveSpecialByte> TypeToId = new Dictionary<Type, EasySaveSpecialByte>()
{
{ typeof(bool), EasySaveSpecialByte.Bool},
{ typeof(byte), EasySaveSpecialByte.Byte},
{ typeof(sbyte), EasySaveSpecialByte.Sbyte},
{ typeof(char), EasySaveSpecialByte.Char},
{ typeof(decimal), EasySaveSpecialByte.Decimal},
{ typeof(double), EasySaveSpecialByte.Double},
{ typeof(float), EasySaveSpecialByte.Float},
{ typeof(int), EasySaveSpecialByte.Int},
{ typeof(uint), EasySaveSpecialByte.Uint},
{ typeof(long), EasySaveSpecialByte.Long},
{ typeof(ulong), EasySaveSpecialByte.Ulong},
{ typeof(short), EasySaveSpecialByte.Short},
{ typeof(ushort), EasySaveSpecialByte.Ushort},
{ typeof(string), EasySaveSpecialByte.String},
{ typeof(byte[]), EasySaveSpecialByte.ByteArray}
};
internal static EasySaveSpecialByte TypeToByte(Type type)
{
EasySaveSpecialByte b;
if (TypeToId.TryGetValue(type, out b))
return b;
return EasySaveSpecialByte.Object;
}
internal static Type ByteToType(EasySaveSpecialByte b)
{
return ByteToType((byte)b);
}
internal static Type ByteToType(byte b)
{
Type type;
if (IdToType.TryGetValue((EasySaveSpecialByte)b, out type))
return type;
return typeof(object);
}
internal static bool IsPrimitive(EasySaveSpecialByte b)
{
switch(b)
{
case EasySaveSpecialByte.Bool:
case EasySaveSpecialByte.Byte:
case EasySaveSpecialByte.Sbyte:
case EasySaveSpecialByte.Char:
case EasySaveSpecialByte.Decimal:
case EasySaveSpecialByte.Double:
case EasySaveSpecialByte.Float:
case EasySaveSpecialByte.Int:
case EasySaveSpecialByte.Uint:
case EasySaveSpecialByte.Long:
case EasySaveSpecialByte.Ulong:
case EasySaveSpecialByte.Short:
case EasySaveSpecialByte.Ushort:
case EasySaveSpecialByte.String:
case EasySaveSpecialByte.ByteArray:
return true;
default:
return false;
}
}
}
}

View File

@@ -0,0 +1,156 @@
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System;
using System.Text;
using System.Globalization;
namespace Convention.EasySave.Internal
{
internal class EasySaveCacheWriter : EasySaveWriter
{
EasySaveFile es3File;
internal EasySaveCacheWriter(EasySaveSettings settings, bool writeHeaderAndFooter, bool mergeKeys) : base(settings, writeHeaderAndFooter, mergeKeys)
{
es3File = new EasySaveFile(settings);
}
/* User-facing methods used when writing randomly-accessible Key-Value pairs. */
#region Write(key, value) Methods
/// <summary>Writes a value to the writer with the given key.</summary>
/// <param name="key">The key which uniquely identifies this value.</param>
/// <param name="value">The value we want to write.</param>
public override void Write<T>(string key, object value)
{
es3File.Save<T>(key, (T)value);
}
internal override void Write(string key, Type type, byte[] value)
{
throw new NotImplementedException();
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public override void Write(Type type, string key, object value)
{
es3File.Save(key, value);
}
#endregion
#region WritePrimitive(value) methods.
internal override void WritePrimitive(int value) { }
internal override void WritePrimitive(float value) { }
internal override void WritePrimitive(bool value) { }
internal override void WritePrimitive(decimal value) { }
internal override void WritePrimitive(double value) { }
internal override void WritePrimitive(long value) { }
internal override void WritePrimitive(ulong value) { }
internal override void WritePrimitive(uint value) { }
internal override void WritePrimitive(byte value) { }
internal override void WritePrimitive(sbyte value) { }
internal override void WritePrimitive(short value) { }
internal override void WritePrimitive(ushort value) { }
internal override void WritePrimitive(char value) { }
internal override void WritePrimitive(byte[] value) { }
internal override void WritePrimitive(string value)
{
}
internal override void WriteNull()
{
}
#endregion
#region Format-specific methods
private static bool CharacterRequiresEscaping(char c)
{
return false;
}
private void WriteCommaIfRequired()
{
}
internal override void WriteRawProperty(string name, byte[] value)
{
}
internal override void StartWriteFile()
{
}
internal override void EndWriteFile()
{
}
internal override void StartWriteProperty(string name)
{
base.StartWriteProperty(name);
}
internal override void EndWriteProperty(string name)
{
}
internal override void StartWriteObject(string name)
{
}
internal override void EndWriteObject(string name)
{
}
internal override void StartWriteCollection()
{
}
internal override void EndWriteCollection()
{
}
internal override void StartWriteCollectionItem(int index)
{
}
internal override void EndWriteCollectionItem(int index)
{
}
internal override void StartWriteDictionary()
{
}
internal override void EndWriteDictionary()
{
}
internal override void StartWriteDictionaryKey(int index)
{
}
internal override void EndWriteDictionaryKey(int index)
{
}
internal override void StartWriteDictionaryValue(int index)
{
}
internal override void EndWriteDictionaryValue(int index)
{
}
#endregion
public override void Dispose(){}
}
}

View File

@@ -0,0 +1,232 @@
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System;
using System.Text;
using System.Globalization;
namespace Convention.EasySave.Internal
{
internal class EasySaveJSONWriter : EasySaveWriter
{
internal StreamWriter baseWriter;
private bool isFirstProperty = true;
public EasySaveJSONWriter(Stream stream, EasySaveSettings settings) : this(stream, settings, true, true){}
internal EasySaveJSONWriter(Stream stream, EasySaveSettings settings, bool writeHeaderAndFooter, bool mergeKeys) : base(settings, writeHeaderAndFooter, mergeKeys)
{
baseWriter = new StreamWriter(stream);
StartWriteFile();
}
#region WritePrimitive(value) methods.
internal override void WritePrimitive(int value) { baseWriter.Write(value); }
internal override void WritePrimitive(float value) { baseWriter.Write(value.ToString("R", CultureInfo.InvariantCulture)); }
internal override void WritePrimitive(bool value) { baseWriter.Write(value ? "true" : "false"); }
internal override void WritePrimitive(decimal value) { baseWriter.Write(value.ToString(CultureInfo.InvariantCulture)); }
internal override void WritePrimitive(double value) { baseWriter.Write(value.ToString("R", CultureInfo.InvariantCulture)); }
internal override void WritePrimitive(long value) { baseWriter.Write(value); }
internal override void WritePrimitive(ulong value) { baseWriter.Write(value); }
internal override void WritePrimitive(uint value) { baseWriter.Write(value); }
internal override void WritePrimitive(byte value) { baseWriter.Write(System.Convert.ToInt32(value)); }
internal override void WritePrimitive(sbyte value) { baseWriter.Write(System.Convert.ToInt32(value)); }
internal override void WritePrimitive(short value) { baseWriter.Write(System.Convert.ToInt32(value)); }
internal override void WritePrimitive(ushort value) { baseWriter.Write(System.Convert.ToInt32(value)); }
internal override void WritePrimitive(char value) { WritePrimitive( value.ToString() ); }
internal override void WritePrimitive(byte[] value) { WritePrimitive( System.Convert.ToBase64String(value) ); }
internal override void WritePrimitive(string value)
{
baseWriter.Write("\"");
// Escape any quotation marks within the string.
for(int i = 0; i<value.Length; i++)
{
char c = value[i];
switch(c)
{
case '\"':
case '“':
case '”':
case '\\':
case '/':
baseWriter.Write('\\');
baseWriter.Write(c);
break;
case '\b':
baseWriter.Write("\\b");
break;
case '\f':
baseWriter.Write("\\f");
break;
case '\n':
baseWriter.Write("\\n");
break;
case '\r':
baseWriter.Write("\\r");
break;
case '\t':
baseWriter.Write("\\t");
break;
default:
baseWriter.Write(c);
break;
}
}
baseWriter.Write("\"");
}
internal override void WriteNull()
{
baseWriter.Write("null");
}
#endregion
#region Format-specific methods
private static bool CharacterRequiresEscaping(char c)
{
return c == '\"' || c == '\\' || c == '“' || c == '”';
}
private void WriteCommaIfRequired()
{
if (!isFirstProperty)
baseWriter.Write(',');
else
isFirstProperty = false;
WriteNewlineAndTabs();
}
internal override void WriteRawProperty(string name, byte[] value)
{
StartWriteProperty(name); baseWriter.Write(settings.encoding.GetString(value, 0, value.Length)); EndWriteProperty(name);
}
internal override void StartWriteFile()
{
if (writeHeaderAndFooter)
baseWriter.Write('{');
base.StartWriteFile();
}
internal override void EndWriteFile()
{
base.EndWriteFile();
WriteNewlineAndTabs();
if(writeHeaderAndFooter)
baseWriter.Write('}');
}
internal override void StartWriteProperty(string name)
{
base.StartWriteProperty(name);
WriteCommaIfRequired();
Write(name);
if(settings.prettyPrint)
baseWriter.Write(' ');
baseWriter.Write(':');
if (settings.prettyPrint)
baseWriter.Write(' ');
}
internal override void EndWriteProperty(string name)
{
// It's not necessary to perform any operations after writing the property in JSON.
base.EndWriteProperty(name);
}
internal override void StartWriteObject(string name)
{
base.StartWriteObject(name);
isFirstProperty = true;
baseWriter.Write('{');
}
internal override void EndWriteObject(string name)
{
base.EndWriteObject(name);
// Set isFirstProperty to false incase we didn't write any properties, in which case
// WriteCommaIfRequired() is never called.
isFirstProperty = false;
WriteNewlineAndTabs();
baseWriter.Write('}');
}
internal override void StartWriteCollection()
{
base.StartWriteCollection();
baseWriter.Write('[');
WriteNewlineAndTabs();
}
internal override void EndWriteCollection()
{
base.EndWriteCollection();
WriteNewlineAndTabs();
baseWriter.Write(']');
}
internal override void StartWriteCollectionItem(int index)
{
if(index != 0)
baseWriter.Write(',');
}
internal override void EndWriteCollectionItem(int index)
{
}
internal override void StartWriteDictionary()
{
StartWriteObject(null);
}
internal override void EndWriteDictionary()
{
EndWriteObject(null);
}
internal override void StartWriteDictionaryKey(int index)
{
if(index != 0)
baseWriter.Write(',');
}
internal override void EndWriteDictionaryKey(int index)
{
baseWriter.Write(':');
}
internal override void StartWriteDictionaryValue(int index)
{
}
internal override void EndWriteDictionaryValue(int index)
{
}
#endregion
public override void Dispose()
{
baseWriter.Dispose();
}
public void WriteNewlineAndTabs()
{
if (settings.prettyPrint)
{
baseWriter.Write(Environment.NewLine);
for (int i = 0; i < serializationDepth; i++)
baseWriter.Write('\t');
}
}
}
}

View File

@@ -0,0 +1,446 @@
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System;
using Convention.EasySave.Types;
using Convention.EasySave.Internal;
public abstract class EasySaveWriter : IDisposable
{
public EasySaveSettings settings;
protected HashSet<string> keysToDelete = new HashSet<string>();
internal bool writeHeaderAndFooter = true;
internal bool overwriteKeys = true;
protected int serializationDepth = 0;
#region ES3Writer Abstract Methods
internal abstract void WriteNull();
internal virtual void StartWriteFile()
{
serializationDepth++;
}
internal virtual void EndWriteFile()
{
serializationDepth--;
}
internal virtual void StartWriteObject(string name)
{
serializationDepth++;
}
internal virtual void EndWriteObject(string name)
{
serializationDepth--;
}
internal virtual void StartWriteProperty(string name)
{
if (name == null)
throw new ArgumentNullException("Key or field name cannot be NULL when saving data.");
}
internal virtual void EndWriteProperty(string name)
{
}
internal virtual void StartWriteCollection()
{
serializationDepth++;
}
internal virtual void EndWriteCollection()
{
serializationDepth--;
}
internal abstract void StartWriteCollectionItem(int index);
internal abstract void EndWriteCollectionItem(int index);
internal abstract void StartWriteDictionary();
internal abstract void EndWriteDictionary();
internal abstract void StartWriteDictionaryKey(int index);
internal abstract void EndWriteDictionaryKey(int index);
internal abstract void StartWriteDictionaryValue(int index);
internal abstract void EndWriteDictionaryValue(int index);
public abstract void Dispose();
#endregion
#region ES3Writer Interface abstract methods
internal abstract void WriteRawProperty(string name, byte[] bytes);
internal abstract void WritePrimitive(int value);
internal abstract void WritePrimitive(float value);
internal abstract void WritePrimitive(bool value);
internal abstract void WritePrimitive(decimal value);
internal abstract void WritePrimitive(double value);
internal abstract void WritePrimitive(long value);
internal abstract void WritePrimitive(ulong value);
internal abstract void WritePrimitive(uint value);
internal abstract void WritePrimitive(byte value);
internal abstract void WritePrimitive(sbyte value);
internal abstract void WritePrimitive(short value);
internal abstract void WritePrimitive(ushort value);
internal abstract void WritePrimitive(char value);
internal abstract void WritePrimitive(string value);
internal abstract void WritePrimitive(byte[] value);
#endregion
protected EasySaveWriter(EasySaveSettings settings, bool writeHeaderAndFooter, bool overwriteKeys)
{
this.settings = settings;
this.writeHeaderAndFooter = writeHeaderAndFooter;
this.overwriteKeys = overwriteKeys;
}
/* User-facing methods used when writing randomly-accessible Key-Value pairs. */
#region Write(key, value) Methods
internal virtual void Write(string key, Type type, byte[] value)
{
StartWriteProperty(key);
StartWriteObject(key);
WriteType(type);
WriteRawProperty("value", value);
EndWriteObject(key);
EndWriteProperty(key);
MarkKeyForDeletion(key);
}
/// <summary>Writes a value to the writer with the given key.</summary>
/// <param name="key">The key which uniquely identifies this value.</param>
/// <param name="value">The value we want to write.</param>
public virtual void Write<T>(string key, object value)
{
if(typeof(T) == typeof(object))
Write(value.GetType(), key, value);
else
Write(typeof(T), key, value);
}
/// <summary>Writes a value to the writer with the given key, using the given type rather than the generic parameter.</summary>
/// <param name="key">The key which uniquely identifies this value.</param>
/// <param name="value">The value we want to write.</param>
/// <param name="type">The type we want to use for the header, and to retrieve an EasySaveType.</param>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void Write(Type type, string key, object value)
{
StartWriteProperty(key);
StartWriteObject(key);
WriteType(type);
WriteProperty("value", value, EasySaveTypeMgr.GetOrCreateEasySaveType(type), settings.referenceMode);
EndWriteObject(key);
EndWriteProperty(key);
MarkKeyForDeletion(key);
}
#endregion
#region Write(value) & Write(value, EasySaveType) Methods
/// <summary>Writes a value to the writer. Note that this should only be called within an EasySaveType.</summary>
/// <param name="value">The value we want to write.</param>
/// <param name="memberReferenceMode">Whether we want to write UnityEngine.Object fields and properties by reference, by value, or both.</param>
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void Write(object value, EasySave.ReferenceMode memberReferenceMode = EasySave.ReferenceMode.ByRef)
{
if(value == null){ WriteNull(); return; }
var type = EasySaveTypeMgr.GetOrCreateEasySaveType(value.GetType());
Write(value, type, memberReferenceMode);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void Write(object value, EasySaveType type, EasySave.ReferenceMode memberReferenceMode = EasySave.ReferenceMode.ByRef)
{
// Note that we have to check UnityEngine.Object types for null by casting it first, otherwise
// it will always return false.
if (value == null)
{
WriteNull();
return;
}
// Deal with System.Objects
if (type == null || type.type == typeof(object))
{
var valueType = value.GetType();
type = EasySaveTypeMgr.GetOrCreateEasySaveType(valueType);
if(type == null)
throw new NotSupportedException("Types of " + valueType + " are not supported. Please see the Supported Types guide for more information: https://docs.moodkie.com/easy-save-3/es3-supported-types/");
if (!type.isCollection && !type.isDictionary)
{
StartWriteObject(null);
WriteType(valueType);
type.Write(value, this);
EndWriteObject(null);
return;
}
}
if(type == null)
throw new ArgumentNullException("EasySaveType argument cannot be null.");
if (type.isUnsupported)
{
if(type.isCollection || type.isDictionary)
throw new NotSupportedException(type.type + " is not supported because it's element type is not supported. Please see the Supported Types guide for more information: https://docs.moodkie.com/easy-save-3/es3-supported-types/");
else
throw new NotSupportedException("Types of " + type.type + " are not supported. Please see the Supported Types guide for more information: https://docs.moodkie.com/easy-save-3/es3-supported-types/");
}
if (type.isPrimitive || type.isEnum)
type.Write(value, this);
else if (type.isCollection)
{
StartWriteCollection();
((ECollectionType)type).Write(value, this, memberReferenceMode);
EndWriteCollection();
}
else if (type.isDictionary)
{
StartWriteDictionary();
((EasySaveDictionaryType)type).Write(value, this, memberReferenceMode);
EndWriteDictionary();
}
else
{
throw new NotImplementedException();
//if (type.type == typeof(GameObject))
// ((ES3Type_GameObject)type).saveChildren = settings.saveChildren;
//StartWriteObject(null);
//if (type.isES3TypeUnityObject)
// ((ES3UnityObjectType)type).WriteObject(value, this, memberReferenceMode);
//else
// type.Write(value, this);
//EndWriteObject(null);
}
}
#endregion
/* Writes a property as a name value pair. */
#region WriteProperty(name, value) methods
/// <summary>Writes a field or property to the writer. Note that this should only be called within an EasySaveType.</summary>
/// <param name="name">The name of the field or property.</param>
/// <param name="value">The value we want to write.</param>
public virtual void WriteProperty(string name, object value)
{
WriteProperty(name, value, settings.memberReferenceMode);
}
/// <summary>Writes a field or property to the writer. Note that this should only be called within an EasySaveType.</summary>
/// <param name="name">The name of the field or property.</param>
/// <param name="value">The value we want to write.</param>
/// <param name="memberReferenceMode">Whether we want to write the property by reference, by value, or both.</param>
public virtual void WriteProperty(string name, object value, EasySave.ReferenceMode memberReferenceMode)
{
if (SerializationDepthLimitExceeded())
return;
StartWriteProperty(name); Write(value, memberReferenceMode); EndWriteProperty(name);
}
/// <summary>Writes a field or property to the writer. Note that this should only be called within an EasySaveType.</summary>
/// <param name="name">The name of the field or property.</param>
/// <param name="value">The value we want to write.</param>
public virtual void WriteProperty<T>(string name, object value)
{
WriteProperty(name, value, EasySaveTypeMgr.GetOrCreateEasySaveType(typeof(T)), settings.memberReferenceMode);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void WriteProperty(string name, object value, EasySaveType type)
{
WriteProperty(name, value, type, settings.memberReferenceMode);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void WriteProperty(string name, object value, EasySaveType type, EasySave.ReferenceMode memberReferenceMode)
{
if (SerializationDepthLimitExceeded())
return;
StartWriteProperty(name);
Write(value, type, memberReferenceMode);
EndWriteProperty(name);
}
/// <summary>Writes a private property to the writer. Note that this should only be called within an EasySaveType.</summary>
/// <param name="name">The name of the property.</param>
/// <param name="objectContainingProperty">The object containing the property we want to write.</param>
public void WritePrivateProperty(string name, object objectContainingProperty)
{
var property = EasySaveReflection.GetEasySaveReflectedProperty(objectContainingProperty.GetType(), name);
if(property.IsNull)
throw new MissingMemberException("A private property named "+ name + " does not exist in the type "+objectContainingProperty.GetType());
WriteProperty(name, property.GetValue(objectContainingProperty), EasySaveTypeMgr.GetOrCreateEasySaveType(property.MemberType));
}
/// <summary>Writes a private field to the writer. Note that this should only be called within an EasySaveType.</summary>
/// <param name="name">The name of the field.</param>
/// <param name="objectContainingField">The object containing the property we want to write.</param>
public void WritePrivateField(string name, object objectContainingField)
{
var field = EasySaveReflection.GetEasySaveReflectedMember(objectContainingField.GetType(), name);
if(field.IsNull)
throw new MissingMemberException("A private field named "+ name + " does not exist in the type "+objectContainingField.GetType());
WriteProperty(name,field.GetValue(objectContainingField), EasySaveTypeMgr.GetOrCreateEasySaveType(field.MemberType));
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public void WritePrivateProperty(string name, object objectContainingProperty, EasySaveType type)
{
var property = EasySaveReflection.GetEasySaveReflectedProperty(objectContainingProperty.GetType(), name);
if(property.IsNull)
throw new MissingMemberException("A private property named "+ name + " does not exist in the type "+objectContainingProperty.GetType());
WriteProperty(name, property.GetValue(objectContainingProperty), type);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public void WritePrivateField(string name, object objectContainingField, EasySaveType type)
{
var field = EasySaveReflection.GetEasySaveReflectedMember(objectContainingField.GetType(), name);
if(field.IsNull)
throw new MissingMemberException("A private field named "+ name + " does not exist in the type "+objectContainingField.GetType());
WriteProperty(name,field.GetValue(objectContainingField), type);
}
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public void WriteType(Type type)
{
WriteProperty(EasySaveType.typeFieldName, EasySaveReflection.GetTypeString(type));
}
#endregion
#region Create methods
/// <summary>Creates a new EasySaveWriter.</summary>
/// <param name="filePath">The relative or absolute path of the file we want to write to.</param>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public static EasySaveWriter Create(string filePath, EasySaveSettings settings)
{
return Create(new EasySaveSettings(filePath, settings));
}
/// <summary>Creates a new EasySaveWriter.</summary>
/// <param name="settings">The settings we want to use to override the default settings.</param>
public static EasySaveWriter Create(EasySaveSettings settings)
{
return Create(settings, true, true, false);
}
// Implicit Stream Methods.
internal static EasySaveWriter Create(EasySaveSettings settings, bool writeHeaderAndFooter, bool overwriteKeys, bool append)
{
var stream = EasySaveStream.CreateStream(settings, (append ? EasySaveFileMode.Append : EasySaveFileMode.Write));
if(stream == null)
return null;
return Create(stream, settings, writeHeaderAndFooter, overwriteKeys);
}
// Explicit Stream Methods.
internal static EasySaveWriter Create(Stream stream, EasySaveSettings settings, bool writeHeaderAndFooter, bool overwriteKeys)
{
if(stream.GetType() == typeof(MemoryStream))
{
settings = (EasySaveSettings)settings.Clone();
settings.location = EasySave.Location.InternalMS;
}
// Get the baseWriter using the given Stream.
if(settings.format == EasySave.Format.JSON)
return new EasySaveJSONWriter(stream, settings, writeHeaderAndFooter, overwriteKeys);
else
return null;
}
#endregion
/*
* Checks whether serialization depth limit has been exceeded
*/
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
protected bool SerializationDepthLimitExceeded()
{
if (serializationDepth > settings.serializationDepthLimit)
{
//ES3Debug.LogWarning("Serialization depth limit of " + settings.serializationDepthLimit + " has been exceeded, indicating that there may be a circular reference.\nIf this is not a circular reference, you can increase the depth by going to Window > Easy Save 3 > Settings > Advanced Settings > Serialization Depth Limit");
return true;
}
return false;
}
/*
* Marks a key for deletion.
* When merging files, keys marked for deletion will not be included.
*/
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public virtual void MarkKeyForDeletion(string key)
{
keysToDelete.Add(key);
}
/*
* Merges the contents of the non-temporary file with this EasySaveWriter,
* ignoring any keys which are marked for deletion.
*/
protected void Merge()
{
using(var reader = EasySaveReader.Create(settings))
{
if(reader == null)
return;
Merge(reader);
}
}
/*
* Merges the contents of the EasySaveReader with this EasySaveWriter,
* ignoring any keys which are marked for deletion.
*/
protected void Merge(EasySaveReader reader)
{
foreach(KeyValuePair<string,EasySaveData> kvp in reader.RawEnumerator)
if(!keysToDelete.Contains(kvp.Key) || kvp.Value.type == null) // Don't add keys whose data is of a type which no longer exists in the project.
Write(kvp.Key, kvp.Value.type.type, kvp.Value.bytes);
}
/// <summary>Stores the contents of the writer and overwrites any existing keys if overwriting is enabled.</summary>
public virtual void Save()
{
Save(overwriteKeys);
}
/// <summary>Stores the contents of the writer and overwrites any existing keys if overwriting is enabled.</summary>
/// <param name="overwriteKeys">Whether we should overwrite existing keys.</param>
public virtual void Save(bool overwriteKeys)
{
if(overwriteKeys)
Merge();
EndWriteFile();
Dispose();
// If we're writing to a location which can become corrupted, rename the backup file to the file we want.
// This prevents corrupt data.
if(settings.location == EasySave.Location.File)
EasySaveIO.CommitBackup(settings);
}
}

View File

@@ -5,44 +5,27 @@ using Convention;
public class Program
{
class Slot1
class Vector2
{
public double x, y;
}
class Slot2
class Vector2X2
{
}
class Slot3
{
}
class Slot4
{
public double x, y;
public Vector2 next;
}
static void Main(string[] args)
{
var seed = new Random(2049);
for (int i = 0; i < 100000; i++)
EasySave.Save("key", new Vector2X2()
{
Convention.Architecture.InternalReset();
var slot1 = new Slot1();
var slot2 = new Slot2();
var slot3 = new Slot3();
var slot4 = new Slot4();
List<Action> list = new() {
()=> Convention.Architecture.Register(slot1,()=>{ },typeof(Slot2),typeof(Slot3),typeof(Slot4)),
()=> Convention.Architecture.Register(slot2,()=>{ },typeof(Slot3),typeof(Slot4)),
()=> Convention.Architecture.Register(slot3,()=>{ },typeof(Slot4)),
()=> Convention.Architecture.Register(slot4,()=>{ })};
list.Sort((x, y) => seed.Next() > seed.Next() ? 1 : -1);
foreach (var item in list)
x = 10,
y = 20,
next = new()
{
item();
x = 30,
y = 40
}
Console.Write($"{i}\t\r");
}
}, "test.json");
}
}