AxibugEmuOnline/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/SaveSlotManager/SaveFile.cs

223 lines
6.9 KiB
C#

using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.Tools;
using AxibugProtobuf;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace AxibugEmuOnline.Client
{
/// <summary> 存档文件抽象类 </summary>
public class SaveFile
{
public SavCloudApi CloudAPI => App.SavMgr.CloudApi;
/// <summary> 指示该存档是否是自动存档 </summary>
public bool AutoSave => SlotIndex == 0;
/// <summary> 指示该存档所在槽位 </summary>
public int SlotIndex { get; private set; }
/// <summary> 指示该存档所属Rom的ID </summary>
public int RomID { get; private set; }
/// <summary> 指示该存档所属模拟器平台 </summary>
public RomPlatformType EmuPlatform { get; private set; }
/// <summary> 指示该存档是否为空 </summary>
public bool IsEmpty { get; private set; }
/// <summary> 存档文件路径 </summary>
public string FilePath
{
get
{
var path = App.UserPersistenDataPath(EmuPlatform);
path = $"{path}/Slot/{RomID}";
Directory.CreateDirectory(path);
var filePath = $"{path}/slot{SlotIndex}.SlotSav";
return filePath;
}
}
public event Action OnSavSuccessed;
/// <summary> 存档顺序号,用于判断云存档和本地存档的同步状态,是否存在冲突 </summary>
public uint Sequecen { get; private set; }
SimpleFSM<SaveFile> FSM;
byte[] m_savDataCaches;
byte[] m_screenShotCaches;
Header m_headerCache;
bool m_cacheOutdate = true;
public SaveFile(int romID, RomPlatformType platform, int slotIndex)
{
RomID = romID;
EmuPlatform = platform;
SlotIndex = slotIndex;
FSM = new SimpleFSM<SaveFile>(this);
FSM.AddState<UnkownState>();
FSM.AddState<CheckingState>();
FSM.AddState<DownloadingState>();
FSM.AddState<UploadingState>();
FSM.AddState<SyncedState>();
IsEmpty = !File.Exists(FilePath);
if (IsEmpty) Sequecen = 0;
else //从文件头读取存储序号
{
byte[] saveOrderData = new byte[4];
var streaming = File.OpenRead(FilePath);
int res = streaming.Read(saveOrderData, 0, 4);
if (res != 4) //无效的存档文件
{
IsEmpty = true;
File.Delete(FilePath);
}
else
{
Sequecen = BitConverter.ToUInt32(saveOrderData, 0);
}
streaming.Dispose();
}
FSM.ChangeState<UnkownState>();
}
public void Update()
{
FSM.Update();
}
/// <summary>
/// 获得存档的保存时间(UTC)
/// </summary>
/// <returns></returns>
public DateTime GetSavTimeUTC()
{
GetSavData(out _, out _);
return new DateTime((long)m_headerCache.SavTicks, DateTimeKind.Utc);
}
public unsafe void GetSavData(out byte[] savData, out byte[] screenShotData)
{
if (!m_cacheOutdate)
{
savData = m_savDataCaches;
screenShotData = m_screenShotCaches;
return;
}
m_cacheOutdate = false;
savData = null;
screenShotData = null;
if (!File.Exists(FilePath)) return;
var raw = File.ReadAllBytes(FilePath);
int headerSize = Marshal.SizeOf(typeof(Header));
if (raw.Length < headerSize)
{
App.log.Warning("无效存档");
return;
}
m_headerCache = new Header();
fixed (Header* headPtr = &m_headerCache)
{
var headP = (byte*)headPtr;
Marshal.Copy(raw, 0, (IntPtr)headP, sizeof(Header));
}
savData = new byte[m_headerCache.DataLength];
Array.Copy(raw, headerSize, savData, 0, savData.Length);
screenShotData = new byte[m_headerCache.ScreenShotLength];
Array.Copy(raw, headerSize + savData.Length, screenShotData, 0, screenShotData.Length);
m_savDataCaches = savData;
m_screenShotCaches = screenShotData;
return;
}
public unsafe void Save(uint sequence, byte[] savData, byte[] screenShotData)
{
var filePath = FilePath;
var header = new Header
{
Sequence = sequence,
RomID = RomID,
SavTicks = (ulong)DateTime.UtcNow.Ticks,
DataLength = (uint)savData.Length,
ScreenShotLength = (uint)screenShotData.Length,
};
int headerSize = Marshal.SizeOf(typeof(Header));
IntPtr ptr = Marshal.AllocHGlobal(headerSize);
var totalSize = headerSize + savData.Length + screenShotData.Length;
byte[] raw = new byte[totalSize];
try
{
Marshal.StructureToPtr(header, ptr, false);
Marshal.Copy(ptr, raw, 0, headerSize);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
Array.Copy(savData, 0, raw, headerSize, savData.Length);
Array.Copy(screenShotData, 0, raw, headerSize + savData.Length, screenShotData.Length);
File.WriteAllBytes(filePath, raw);
Sequecen = sequence;
m_headerCache = header;
m_savDataCaches = savData;
m_screenShotCaches = screenShotData;
IsEmpty = false;
OnSavSuccessed?.Invoke();
}
/// <summary>
/// 尝试同步存档
/// </summary>
public void TrySync()
{
if (FSM.CurrentState is not UnkownState && FSM.CurrentState is not SyncedState) return;
FSM.ChangeState<CheckingState>();
}
[StructLayout(LayoutKind.Explicit, Size = 24)]
struct Header
{
[FieldOffset(0)]
public uint Sequence;
[FieldOffset(4)]
public int RomID;
[FieldOffset(8)]
public ulong SavTicks;
[FieldOffset(16)]
public uint DataLength;
[FieldOffset(20)]
public uint ScreenShotLength;
}
public enum EnumState
{
Unkown,
Checking,
Downloading,
Uploading,
Synced
}
}
}