using System; using System.IO; using game; using metagen; public static class Persister { private static readonly object SaveLock = new object(); public static PersistError SaveToFile(DataSave data, string file_path, string secret) { var file_path_tmp = file_path + ".~" + UnityEngine.Random.value; var err = DoSaveToFile(data, file_path, file_path_tmp, secret); Log.Info("Saved to file: " + file_path + ", " + err); return err; } //TODO: not needed at the moment //async static public UniTask SaveToFileAsync(DataSave data, string file_path, string secret) //{ // var err = new PersistError(); // string file_path_tmp = file_path + ".~" + UnityEngine.Random.value; // await UniTask.Run(() => // err = DoSaveToFile(data, file_path, file_path_tmp, secret) // ); // Log.Info("Saved to file async: " + file_path + ", " + err); // return err; //} static PersistError DoSaveToFile(DataSave data, string file_path, string file_path_tmp, string secret) { var err = new PersistError(); if (string.IsNullOrEmpty(secret)) { err.Description = $"{nameof(secret)} is empty"; err.Code = PersistCode.NO_SECRET; return err; } //NOTE: to be on the safe side lock (SaveLock) { try { Directory.CreateDirectory(Path.GetDirectoryName(file_path)); Directory.CreateDirectory(Path.GetDirectoryName(file_path_tmp)); using (var ms = new MemoryStream()) { var writer = new MsgPackDataWriter(ms); var data_err = Meta.SyncSafe(MetaSyncContext.NewForWrite(writer), ref data); if (data_err == 0) { using (var fs = File.Open(file_path_tmp, FileMode.Create)) { if (fs == null) throw new Exception("Can't create file: " + file_path_tmp); ms.Position = 0; if (AES.Encrypt(ms, fs, secret) == 0) { err.Description = "Data error: empty data"; err.Code = PersistCode.DATA_ERR; } else { err.Bytes = ms.Position; } } } else { err.Description = "Data write error: " + data_err; err.Code = PersistCode.DATA_ERR; } } if (err == PersistCode.OK) { var full_file_path_tmp_bak = file_path_tmp + ".bak"; //NOTE: trying as hard as possible to postpone destruction of // the original save file if (File.Exists(full_file_path_tmp_bak)) File.Delete(full_file_path_tmp_bak); if (File.Exists(file_path)) File.Move(file_path, full_file_path_tmp_bak); File.Move(file_path_tmp, file_path); if (File.Exists(full_file_path_tmp_bak)) File.Delete(full_file_path_tmp_bak); } } catch (Exception e) { err.Description = "IO write error: " + e.Message; err.Code = e.IsStorageFullException() ? PersistCode.IO_ERR_DISK_FULL : PersistCode.IO_ERR; } } return err; } public static PersistError LoadFromFile(ref DataSave data, string file_path, string secret) { var err = new PersistError(); try { if (File.Exists(file_path)) { using (var fs = File.Open(file_path, FileMode.Open)) { if (fs == null) throw new Exception("Can't open file for read: " + file_path); if (string.IsNullOrEmpty(secret)) { err.Description = "Sekret is empty"; err.Code = PersistCode.NO_SECRET; return err; } using (var ms = new MemoryStream()) { AES.Decrypt(fs, ms, secret); ms.Position = 0; var reader = new MsgPackDataReader(ms); //var data_err = data.read(reader); var data_err = Meta.SyncSafe(MetaSyncContext.NewForRead(AutogenBundle.createById, reader), ref data); if (data_err != 0) { Log.Warn("IO read error: " + data_err); err.Description = "MsgPackDataReader read error: " + data_err; err.Code = PersistCode.DATA_ERR; } else { err.Bytes = ms.Position; } } } } else { err.Code = PersistCode.NO_FILE; } } catch (Exception e) { Log.Warn("IO read error: " + e.Message); err.Code = PersistCode.IO_ERR; err.Description = e.Message; } finally { Log.Info("Loading from file: " + file_path + "(" + err + ")"); } return err; } }