diff --git a/AxiReplay.csproj b/AxiReplay.csproj
new file mode 100644
index 0000000..dbdcea4
--- /dev/null
+++ b/AxiReplay.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/ReplayData.cs b/ReplayData.cs
new file mode 100644
index 0000000..ab3dd58
--- /dev/null
+++ b/ReplayData.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace AxiReplay
+{
+ [StructLayout(LayoutKind.Explicit,Size = 44)]
+ public struct ReplayHandler
+ {
+ [FieldOffset(0)]
+ public int Format;
+ [FieldOffset(sizeof(int) * 1)]
+ public int RomID;
+ [FieldOffset(sizeof(int) * 2)]
+ public int RomType;
+ [FieldOffset(sizeof(int) * 3)]
+ public int DataOffset;
+ [FieldOffset(sizeof(int) * 4)]
+ public int TitleOffset;
+ [FieldOffset(sizeof(int) * 5)]
+ public int NoteOffset;
+ [FieldOffset(sizeof(int) * 6)]
+ public int AllFrame;
+ [FieldOffset(sizeof(int) * 7)]
+ public int AllTime;
+ [FieldOffset(sizeof(int) * 8)]
+ public int SingleLenght;
+ [FieldOffset(sizeof(int) * 9)]
+ public long CreateTime;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct ReplayStep
+ {
+ [FieldOffset(0)]
+ public UInt64 All64Data;
+ [FieldOffset(0)]
+ public Int32 FrameStartID;
+ [FieldOffset(4)]
+ public UInt64 InPut;
+ }
+
+ public static class ReplayData
+ {
+ public static int HandlerLenght = sizeof(int) * 9 + sizeof(long);
+ public enum ReplayFormat : byte
+ {
+ None = 0,
+ FM32IPBYTE,
+ FM32IP16,
+ FM32IP32,
+ FM32IP64,
+ }
+ public static void GetStringByteData(string str,out byte[] data,out int lenghtWithEnd,Encoding encoding)
+ {
+ data = encoding.GetBytes(str);
+ lenghtWithEnd = data.Length + 1;
+ }
+
+ public static byte[] GetHandlerData(ReplayHandler replayhandler)
+ {
+ int size = Marshal.SizeOf(typeof(ReplayHandler));
+ byte[] arr = new byte[size];
+
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try
+ {
+ Marshal.StructureToPtr(replayhandler, ptr, false);
+ Marshal.Copy(ptr, arr, 0, size);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return arr;
+ }
+
+ public static ReplayHandler GetReplayHandlerFromData(byte[] data)
+ {
+ if (data == null || data.Length < ReplayData.HandlerLenght)
+ {
+ throw new ArgumentException("Invalid data length or null data.");
+ }
+
+ IntPtr ptr = Marshal.AllocHGlobal(ReplayData.HandlerLenght);
+ try
+ {
+ // 将byte数组的内容复制到非托管内存中
+ Marshal.Copy(data, 0, ptr, ReplayData.HandlerLenght);
+ // 从非托管内存将内容转换回ReplayHandler结构体
+ return (ReplayHandler)Marshal.PtrToStructure(ptr, typeof(ReplayHandler));
+ }
+ finally
+ {
+ // 释放非托管内存
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+ }
+}
diff --git a/ReplayReader.cs b/ReplayReader.cs
new file mode 100644
index 0000000..7410eb7
--- /dev/null
+++ b/ReplayReader.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using static AxiReplay.ReplayData;
+
+namespace AxiReplay
+{
+ public class ReplayReader : IDisposable
+ {
+ public ReplayData.ReplayFormat mFormat { get; private set; }
+ public Encoding TexEncoding { get; private set; }
+ ReplayHandler handler;
+ string mTitle;
+ string mNote;
+ int mAllFrame;
+ int mAllTime;
+ long mData;
+ int mSingleInputLenght;
+ int mSingleDataLenght;
+ FileStream mStream;
+ BinaryReader mBinaryReader;
+
+ int mCurrFrame = -1;
+ byte[] mNextOutbytes;
+ ReplayStep currStep;
+ ReplayStep nextStep;
+ bool bEnd;
+
+ List dbgList = new List();
+ bool bdbg = false;
+ string dumpPath;
+
+ public ReplayReader(string path, bool bWithDump = false, string dumppath = null)
+ {
+ dbgList.Clear();
+ bdbg = bWithDump;
+ dumpPath = dumppath;
+ mStream = new FileStream(path, FileMode.Open, FileAccess.Read);
+ mBinaryReader = new BinaryReader(mStream);
+ byte[] Outbytes;
+ Outbytes = mBinaryReader.ReadBytes(ReplayData.HandlerLenght);
+ handler = ReplayData.GetReplayHandlerFromData(Outbytes);
+ mFormat = (ReplayFormat)handler.Format;
+ switch (mFormat)
+ {
+ case ReplayData.ReplayFormat.FM32IP64: mSingleInputLenght = sizeof(UInt64); break;
+ case ReplayData.ReplayFormat.FM32IP32: mSingleInputLenght = sizeof(UInt32); break;
+ case ReplayData.ReplayFormat.FM32IP16: mSingleInputLenght = sizeof(UInt16); break;
+ case ReplayData.ReplayFormat.FM32IPBYTE: mSingleInputLenght = sizeof(byte); break;
+ }
+ //Frame+Lenght
+ mSingleDataLenght = (sizeof(UInt32)) + mSingleInputLenght;
+ nextStep = new ReplayStep();
+ nextStep.FrameStartID = -1;
+ bEnd = false;
+
+ dbgList.Add($"Format => {handler.Format}");
+ dbgList.Add($"DataOffset => {handler.DataOffset}");
+ dbgList.Add($"CreateTime => {handler.CreateTime}");
+ dbgList.Add($"AllFrame => {handler.AllFrame}");
+ dbgList.Add($"SingleLenght => {handler.SingleLenght}");
+
+
+ mNextOutbytes = new byte[mSingleDataLenght];
+
+ if (bWithDump)
+ {
+ int TestFrameIdx = -1;
+ while (!bEnd)
+ {
+ UpdateNextFrame(TestFrameIdx++);
+ }
+ File.WriteAllLines(dumppath, dbgList);
+ }
+ else
+ {
+ UpdateNextFrame(0);
+ }
+ }
+
+
+ void UpdateNextFrame(int targetFrame)
+ {
+ //如果已经超过
+ while (targetFrame > nextStep.FrameStartID)
+ {
+ if (nextStep.FrameStartID >= handler.AllFrame)
+ {
+ bEnd = true;
+ break;
+ }
+ mBinaryReader.Read(mNextOutbytes, 0, mSingleDataLenght);
+ switch (mFormat)
+ {
+ case ReplayFormat.FM32IP64:
+ {
+ nextStep.FrameStartID = BitConverter.ToInt32(mNextOutbytes, 0);
+ nextStep.InPut = BitConverter.ToUInt64(mNextOutbytes, sizeof(UInt32));
+ }
+ break;
+ case ReplayFormat.FM32IP32:
+ {
+ nextStep.All64Data = BitConverter.ToUInt64(mNextOutbytes, 0);
+ }
+ break;
+ case ReplayFormat.FM32IP16:
+ {
+ nextStep.All64Data = BitConverter.ToUInt64(mNextOutbytes, 0);
+ }
+ break;
+ case ReplayFormat.FM32IPBYTE:
+ {
+ nextStep.All64Data = BitConverter.ToUInt64(mNextOutbytes, 0);
+ }
+ break;
+ }
+ dbgList.Add($"{nextStep.FrameStartID} | {nextStep.InPut}");
+
+ targetFrame++;
+ }
+ }
+
+ int byFrameIdx = 0;
+ ///
+ /// 往前推进帧的,指定帧下标
+ ///
+ public bool NextFramebyFrameIdx(int FrameID,out ReplayStep data)
+ {
+ bool res = TakeFrame(FrameID - byFrameIdx, out data);
+ byFrameIdx = FrameID;
+ return res;
+ }
+
+ ///
+ /// 往前推进1帧的Input(返回是否变化)
+ ///
+ public bool NextFrame(out ReplayStep data)
+ {
+ return TakeFrame(1,out data);
+ }
+
+ ///
+ /// 往前推进指定帧数量的Input (返回是否变化)
+ ///
+ ///
+ public bool TakeFrame(int addFrame,out ReplayStep data)
+ {
+ bool Changed = false;
+ mCurrFrame += addFrame;
+ if (mCurrFrame >= nextStep.FrameStartID)
+ {
+ Changed = currStep.InPut != nextStep.InPut;
+ currStep = nextStep;
+ data = currStep;
+ UpdateNextFrame(mCurrFrame);
+ }
+ else
+ {
+ data = currStep;
+ }
+ return Changed;
+ }
+
+ public void Dispose()
+ {
+ mStream.Dispose();
+ mBinaryReader.Dispose();
+ //TODO
+ }
+
+ }
+}
diff --git a/ReplayWriter.cs b/ReplayWriter.cs
new file mode 100644
index 0000000..3d0f516
--- /dev/null
+++ b/ReplayWriter.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace AxiReplay
+{
+ public class ReplayWriter : IDisposable
+ {
+ public ReplayData.ReplayFormat mFormat { get; private set; }
+ public Encoding TexEncoding { get; private set; }
+ ReplayHandler handler;
+ string mTitle;
+ string mNote;
+ int mAllFrame;
+ int mAllTime;
+ long mData;
+ int mSingleInputLenght;
+ int mSingleDataLenght;
+ MemoryStream mStream;
+ BinaryWriter mBinaryWriter;
+
+ int mCurrFrame;
+ UInt64 mCurrInput;
+ ReplayStep wirteStep;
+
+ List dbgList = new List();
+
+ public ReplayWriter(string Title, string Note, ReplayData.ReplayFormat format, Encoding encoding)
+ {
+ mTitle = Title;
+ mNote = Note;
+ TexEncoding = encoding;
+ mFormat = format;
+ switch (mFormat)
+ {
+ case ReplayData.ReplayFormat.FM32IP64: mSingleInputLenght = sizeof(UInt64); break;
+ case ReplayData.ReplayFormat.FM32IP32: mSingleInputLenght = sizeof(UInt32); break;
+ case ReplayData.ReplayFormat.FM32IP16: mSingleInputLenght = sizeof(UInt16); break;
+ case ReplayData.ReplayFormat.FM32IPBYTE: mSingleInputLenght = sizeof(byte); break;
+ }
+ mSingleDataLenght = (sizeof(UInt32)) + mSingleInputLenght;
+
+ mStream = new MemoryStream();
+ mBinaryWriter = new BinaryWriter(mStream);
+
+ mCurrFrame = -1;
+ mCurrInput = int.MaxValue;
+ wirteStep = new ReplayStep();
+
+ dbgList.Clear();
+
+ }
+
+ int byFrameIdx = 0;
+ ///
+ /// 往前推进帧的,指定帧下标
+ ///
+ ///
+ public void NextFramebyFrameIdx(int FrameID,UInt64 frameInput)
+ {
+ TakeFrame(FrameID - byFrameIdx, frameInput);
+ byFrameIdx = FrameID;
+ }
+
+ ///
+ /// 往前推进1帧的Input
+ ///
+ ///
+ public void NextFrame(UInt64 frameInput)
+ {
+ TakeFrame(1, frameInput);
+ }
+
+ ///
+ /// 往前推进指定帧数量的Input
+ ///
+ ///
+ public void TakeFrame(int addFrame, UInt64 frameInput)
+ {
+ if (addFrame < 0)
+ {
+
+ }
+ mCurrFrame += addFrame;
+ if (mCurrInput == frameInput)
+ return;
+ mCurrInput = frameInput;
+
+ wirteStep.FrameStartID = mCurrFrame;
+ wirteStep.InPut = mCurrInput;
+ dbgList.Add($"{mCurrFrame} | {mCurrInput}");
+
+ switch (mFormat)
+ {
+ case ReplayData.ReplayFormat.FM32IP64:
+ mBinaryWriter.Write(wirteStep.FrameStartID);
+ mBinaryWriter.Write(wirteStep.InPut);
+ break;
+ case ReplayData.ReplayFormat.FM32IP32:
+ mBinaryWriter.Write(BitConverter.GetBytes(wirteStep.All64Data), 0, 4 + 4);
+ break;
+ case ReplayData.ReplayFormat.FM32IP16:
+ mBinaryWriter.Write(BitConverter.GetBytes(wirteStep.All64Data), 0, 4 + 2);
+ break;
+ case ReplayData.ReplayFormat.FM32IPBYTE:
+ mBinaryWriter.Write(BitConverter.GetBytes(wirteStep.All64Data), 0, 4 + 1);
+ break;
+ }
+ }
+
+ public void SaveData(string path, bool bWithDump = false, string dumppath = null)
+ {
+ ReplayData.GetStringByteData(mTitle, out byte[] titleData, out int titleLenghtWithEnd, TexEncoding);
+ ReplayData.GetStringByteData(mNote, out byte[] noteData, out int noteLenghtWithEnd, TexEncoding);
+
+ ReplayHandler handler = new ReplayHandler();
+ handler.Format = (int)this.mFormat;
+ handler.DataOffset = ReplayData.HandlerLenght;
+ handler.CreateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+ handler.AllFrame = wirteStep.FrameStartID;
+ handler.SingleLenght = mSingleDataLenght;
+
+ using (FileStream fs = new FileStream(path, FileMode.Create))
+ {
+ using (BinaryWriter bw = new BinaryWriter(fs))
+ {
+ //写入Handler
+ bw.Write(ReplayData.GetHandlerData(handler));
+ //写入Data
+ bw.Write(mStream.ToArray());
+ }
+ }
+
+ if (bWithDump)
+ {
+ List temp = new List();
+ temp.Add($"Format => {handler.Format}");
+ temp.Add($"DataOffset => {handler.DataOffset}");
+ temp.Add($"CreateTime => {handler.CreateTime}");
+ temp.Add($"AllFrame => {handler.AllFrame}");
+ temp.Add($"SingleLenght => {handler.SingleLenght}");
+ dbgList.InsertRange(0,temp);
+ File.WriteAllLines(dumppath, dbgList);
+ }
+ }
+
+ public void Dispose()
+ {
+ mStream.Dispose();
+ mBinaryWriter.Dispose();
+ //TODO
+ }
+
+ }
+}