// ┌─────────────────────────────────────────────────────────────────────────────────────┐
// │ "The Hermes On Your Disk!".cs │
// │ │
// │ It's a dump, stupid game archive system, but FAST. │
// │ │
// │ # HOW TO IMPORT? │
// │ │
// │ Just drag into your project :) │
// │ │
// │ # LICENSE │
// │ │
// │ Copyright (c) 2026 Weicao-CatilGrass │
// │ │
// │ Permission is hereby granted, free of charge, to any person obtaining a copy │
// │ of this software and associated documentation files (the "Software"), to deal │
// │ in the Software without restriction, including without limitation the rights │
// │ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │
// │ copies of the Software, and to permit persons to whom the Software is │
// │ furnished to do so, subject to the following conditions: │
// │ │
// │ The above copyright notice and this permission notice shall be included in all │
// │ copies or substantial portions of the Software. │
// │ │
// │ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │
// │ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │
// │ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │
// │ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │
// │ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, │
// │ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │
// │ SOFTWARE. │
// └─────────────────────────────────────────────────────────────────────────────────────┘
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if UNITY_2020_1_OR_NEWER
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
#endif
namespace HermesOnDisk
{
#region Core
// ┌───────────┐
// │ USAGE │
// └───────────┘
class Usage
{
private void YourMain()
{
// Set save location
Hermes.Root = new DirectoryInfo(".");
// Read boolean
var doorOpened = Hermes.Boolean[2];
// Set boolean
Hermes.Boolean[0] = true;
// Read float
var health = Hermes.Float[0];
// Set float
Hermes.Float[0] = 0;
// Read name
var name = Hermes.String[0];
// Set name
Hermes.String[0] = "Peter";
// Save
Hermes.Store();
// Garbage collect variable-length data
Hermes.GC();
}
}
#if UNITY_2020_1_OR_NEWER
#else
public static class HermesOnDisk
{
///
/// Returns true if there are uncommitted changes in memory.
///
public static bool IsDirty => Hermes.IsDirty;
///
/// Boolean indexer
///
public static Hermes.BoolIndexer Boolean => Hermes.Boolean;
///
/// Float indexer
///
public static Hermes.FloatIndexer Float => Hermes.Float;
///
/// String indexer
///
public static Hermes.StringIndexer String => Hermes.String;
///
/// Variable-length byte array indexer
///
public static Hermes.ByteIndexer Bytes => Hermes.Bytes;
///
/// Root directory
///
public static DirectoryInfo Root
{
get => Hermes.Root;
set => Hermes.Root = value;
}
///
/// Store to disk
///
public static void Store()
=> Hermes.Store();
///
/// Asynchronously fully rewrites pack.dat
///
public static void AsyncGC()
=> Hermes.AsyncGC();
///
/// Full rewrite of pack.dat, reclaiming freed space
///
public static void GC()
=> Hermes.GC();
///
/// Backup pack.dat → pack.dat.backup
///
public static void BackupPack()
=> Hermes.BackupPack();
}
#endif
public static class Hermes
{
private static HermesPathfinder _hermesPathfinder = new(new DirectoryInfo("."));
///
/// Returns true if there are uncommitted changes in memory.
///
public static bool IsDirty =>
ModifiedBooleans.Count > 0 || ModifiedFloat.Count > 0 || ModifiedByteMap.Count > 0;
// ┌─────────────────────────────────────────────┐
// │ Pack file constants (COW + pointer table) │
// └─────────────────────────────────────────────┘
private const string PackDataFile = "pack.dat";
private const string PackIndexFile = "pack.idx";
private const string PackGcFile = "pack.gc";
///
/// Boolean indexer
///
public static BoolIndexer Boolean { get; } = new();
///
/// Float indexer
///
public static FloatIndexer Float { get; } = new();
///
/// Variable-length byte array indexer
///
public static ByteIndexer Bytes { get; } = new();
///
/// String indexer
///
public static StringIndexer String { get; } = new();
///
/// Root directory
///
public static DirectoryInfo Root
{
set => _hermesPathfinder = new HermesPathfinder(value);
get => _hermesPathfinder.RootDirectory;
}
///
/// Store to disk
///
public static void Store()
{
if (IsDirty) return;
if (ModifiedByteMap.Count > 0) BackupPack();
StoreAll();
}
///
/// Backup pack.dat → pack.dat.backup
///
public static void BackupPack()
{
var dataFile = _hermesPathfinder.RootDirectory.JoinToFile(PackDataFile);
if (dataFile.Exists)
{
var backup = new FileInfo(dataFile.FullName + ".backup");
dataFile.CopyTo(backup.FullName, true);
}
}
///
/// Full rewrite of pack.dat, reclaiming freed space
///
public static void GC()
{
GcCore();
}
///
/// Asynchronously fully rewrites pack.dat
///
public static void AsyncGC()
{
System.Threading.Tasks.Task.Run(() => GcCore());
}
private static void GcCore()
{
var dataFile = _hermesPathfinder.RootDirectory.JoinToFile(PackDataFile);
var idxFile = _hermesPathfinder.RootDirectory.JoinToFile(PackIndexFile);
if (!idxFile.Exists) return;
long idxLen = idxFile.Length;
int numEntries = (int)(idxLen / 8);
if (numEntries == 0) return;
var liveEntries = new List<(uint index, byte[] data, uint oldLen)>();
bool hasGarbage = false;
for (uint i = 0; i < (uint)numEntries; i++)
{
if (!TryReadPackIndex(i, out uint offset, out uint length) || length == 0)
{
hasGarbage = true;
continue;
}
if (TryReadSlice(dataFile, offset, length, out byte[] buffer))
liveEntries.Add((i, buffer, length));
else
hasGarbage = true;
}
var gcFile = _hermesPathfinder.RootDirectory.JoinToFile(PackGcFile);
if (!hasGarbage && (!gcFile.Exists || gcFile.Length == 0))
return;
var tmpDataFile = _hermesPathfinder.RootDirectory.JoinToFile(PackDataFile + ".tmp");
var tmpIdxFile = _hermesPathfinder.RootDirectory.JoinToFile(PackIndexFile + ".tmp");
if (tmpDataFile.Directory != null) tmpDataFile.Directory.Create();
var liveMap = liveEntries.ToDictionary(e => e.index, e => (e.data, e.oldLen));
using (var dataFs = new FileStream(tmpDataFile.FullName, FileMode.Create, FileAccess.Write))
using (var idxFs = new FileStream(tmpIdxFile.FullName, FileMode.Create, FileAccess.Write))
{
byte[] indexBuffer = new byte[8];
for (uint i = 0; i < (uint)numEntries; i++)
{
if (liveMap.TryGetValue(i, out var entry))
{
// Live entry: write data to pack, update index with new offset/length
uint newOffset = (uint)dataFs.Position;
dataFs.Write(entry.data, 0, entry.data.Length);
BitConverter.GetBytes(newOffset).CopyTo(indexBuffer, 0);
BitConverter.GetBytes((uint)entry.data.Length).CopyTo(indexBuffer, 4);
}
else
{
// Dead entry: clear to zero (offset=0, length=0)
Array.Clear(indexBuffer, 0, 8);
}
long idxPos = i * 8;
if (idxFs.Length < idxPos + 8)
idxFs.SetLength(idxPos + 8);
idxFs.Seek(idxPos, SeekOrigin.Begin);
idxFs.Write(indexBuffer, 0, 8);
}
}
if (dataFile.Exists) dataFile.Delete();
if (idxFile.Exists) idxFile.Delete();
tmpDataFile.MoveTo(dataFile.FullName);
tmpIdxFile.MoveTo(idxFile.FullName);
if (gcFile.Exists) gcFile.Delete();
}
private static readonly SortedDictionary CacheBooleans = new();
private static readonly SortedDictionary CacheFloats = new();
private static readonly SortedDictionary CacheByteMap = new();
private static readonly HashSet ModifiedBooleans = new();
private static readonly HashSet ModifiedFloat = new();
private static readonly HashSet ModifiedByteMap = new();
///
/// Attempts to read a boolean value at the specified index.
///
/// The global index of the boolean.
/// When this method returns, contains the boolean value if successful; otherwise, false.
/// true if the value was successfully read; otherwise, false.
private static bool ReadBool(uint index, out bool value)
{
// Try to get the boolean from the cache
if (CacheBooleans.TryGetValue(index, out value))
{
// Cache hit, return success
return true;
}
// Cache miss, attempt to load from disk
var file = _hermesPathfinder.GetBoolPathFromIndex(index);
var localIndex = _hermesPathfinder.IndexToLocalIndex(index);
var offset = localIndex;
var length = 1;
// Try to read the slice from the file
if (TryReadSlice(file, offset, length, out byte[] buffer))
{
value = buffer[0] == 1;
// Store into cache
CacheBooleans[index] = value;
return true;
}
value = false;
return false;
}
///
/// Writes a boolean value at the specified index.
///
/// The global index of the boolean.
/// The boolean value to write.
private static void WriteBool(uint index, bool value)
{
// Write to cache
CacheBooleans[index] = value;
// Mark the value as modified
ModifiedBooleans.Add(index);
}
///
/// Attempts to read a float value at the specified index.
///
/// The global index of the float.
/// When this method returns, contains the float value if successful; otherwise, 0f.
/// true if the value was successfully read; otherwise, false.
private static bool ReadFloat(uint index, out float value)
{
// Try to get the float from the cache
if (CacheFloats.TryGetValue(index, out value))
{
// Cache hit, return success
return true;
}
// Cache miss, attempt to load from disk
var file = _hermesPathfinder.GetFloatPathFromIndex(index);
var localIndex = _hermesPathfinder.IndexToLocalIndex(index);
var offset = localIndex * 4;
var length = 4;
// Try to read the slice from the file
if (TryReadSlice(file, offset, length, out byte[] buffer))
{
value = BitConverter.ToSingle(buffer, 0);
// Store into cache
CacheFloats[index] = value;
return true;
}
value = 0f;
return false;
}
///
/// Writes a float value at the specified index.
///
/// The global index of the float.
/// The float value to write.
private static void WriteFloat(uint index, float value)
{
// Write to cache
CacheFloats[index] = value;
// Mark the value as modified
ModifiedFloat.Add(index);
}
///
/// Attempts to read a byte array value at the specified index from the pack file system.
///
/// The global index of the byte array.
/// When this method returns, contains the byte array if successful; otherwise, null.
/// true if the value was successfully read; otherwise, false.
private static bool ReadByteArray(uint index, out byte[] value)
{
if (CacheByteMap.TryGetValue(index, out value))
return true;
if (TryReadPackIndex(index, out uint offset, out uint length) && length > 0)
{
var dataFile = _hermesPathfinder.RootDirectory.JoinToFile(PackDataFile);
if (TryReadSlice(dataFile, offset, length, out byte[] buffer))
{
value = buffer;
CacheByteMap[index] = value;
return true;
}
}
value = null;
return false;
}
///
/// Writes a byte array value at the specified index.
/// The data is appended to the pack file, and the old space (if any) is marked for garbage collection.
///
/// The global index of the byte array.
/// The byte array value to write.
private static void WriteByteArray(uint index, byte[] value)
{
if (value != null)
CacheByteMap[index] = value;
else
CacheByteMap.Remove(index);
ModifiedByteMap.Add(index);
}
///
/// Attempts to read a pointer entry from the pack index file.
///
/// The entry index in the pointer table.
/// When this method returns, contains the offset in pack.dat if successful; otherwise, 0.
/// When this method returns, contains the length of the data if successful; otherwise, 0.
/// true if the pointer entry was successfully read; otherwise, false.
private static bool TryReadPackIndex(uint index, out uint offset, out uint length)
{
offset = 0;
length = 0;
var idxFile = _hermesPathfinder.RootDirectory.JoinToFile(PackIndexFile);
if (!idxFile.Exists) return false;
long pos = index * 8;
if (pos + 8 > idxFile.Length) return false;
byte[] buffer = new byte[8];
using var fs = idxFile.OpenRead();
fs.Seek(pos, SeekOrigin.Begin);
if (fs.Read(buffer, 0, 8) != 8) return false;
offset = BitConverter.ToUInt32(buffer, 0);
length = BitConverter.ToUInt32(buffer, 4);
return true;
}
///
/// Writes a pointer entry (offset and length) to the pack index file at the specified index.
///
/// The entry index in the pointer table.
/// The offset in pack.dat where the data begins.
/// The length of the data in bytes.
private static void WritePackIndex(uint index, uint offset, uint length)
{
var idxFile = _hermesPathfinder.RootDirectory.JoinToFile(PackIndexFile);
if (idxFile.Directory != null) idxFile.Directory.Create();
long pos = index * 8;
long needed = pos + 8;
using var fs = new FileStream(idxFile.FullName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
if (fs.Length < needed)
fs.SetLength(needed);
byte[] buffer = new byte[8];
BitConverter.GetBytes(offset).CopyTo(buffer, 0);
BitConverter.GetBytes(length).CopyTo(buffer, 4);
fs.Seek(pos, SeekOrigin.Begin);
fs.Write(buffer, 0, 8);
}
///
/// Appends data to the end of the pack data file and returns the offset and length of the appended data.
///
/// The byte array to append to the pack data file.
/// A tuple containing the offset (position in file) and length of the appended data.
private static (uint offset, uint length) AppendToPack(byte[] data)
{
var dataFile = _hermesPathfinder.RootDirectory.JoinToFile(PackDataFile);
if (dataFile.Directory != null) dataFile.Directory.Create();
using var fs = new FileStream(dataFile.FullName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
fs.Seek(0, SeekOrigin.End);
uint offset = (uint)fs.Position;
uint length = (uint)data.Length;
fs.Write(data, 0, data.Length);
return (offset, length);
}
///
/// Appends a garbage collection record (offset and length) to the GC file.
/// These records mark regions in pack.dat that are no longer in use and can be reclaimed during GC.
///
/// The offset of the stale data in pack.dat.
/// The length of the stale data in bytes.
private static void AppendToGc(uint offset, uint length)
{
var gcFile = _hermesPathfinder.RootDirectory.JoinToFile(PackGcFile);
gcFile.Directory.Create();
byte[] buffer = new byte[8];
BitConverter.GetBytes(offset).CopyTo(buffer, 0);
BitConverter.GetBytes(length).CopyTo(buffer, 4);
using var fs = new FileStream(gcFile.FullName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
fs.Seek(0, SeekOrigin.End);
fs.Write(buffer, 0, 8);
}
///
/// Store all modifications to disk
///
private static void StoreAll()
{
// Handle booleans
if (ModifiedBooleans.Count > 0)
{
uint[] indices = ModifiedBooleans.ToArray();
bool[] values = new bool[indices.Length];
for (int i = 0; i < indices.Length; i++)
values[i] = CacheBooleans[indices[i]];
HelperWriteValueToFile(indices, values);
ModifiedBooleans.Clear();
}
// Handle floats
if (ModifiedFloat.Count > 0)
{
uint[] indices = ModifiedFloat.ToArray();
float[] values = new float[indices.Length];
for (int i = 0; i < indices.Length; i++)
values[i] = CacheFloats[indices[i]];
HelperWriteValueToFile(indices, values);
ModifiedFloat.Clear();
}
// Handle variable-length byte arrays
if (ModifiedByteMap.Count > 0)
{
uint[] indices = ModifiedByteMap.ToArray();
foreach (uint index in indices)
{
// Read old pointer -> mark old space for GC
if (TryReadPackIndex(index, out uint oldOffset, out uint oldLen) && oldLen > 0)
AppendToGc(oldOffset, oldLen);
if (CacheByteMap.TryGetValue(index, out byte[] data) && data != null)
{
// Append new data to pack.dat
var (newOffset, newLen) = AppendToPack(data);
// Update pack.idx pointer
WritePackIndex(index, newOffset, newLen);
}
else
{
// Value is null (deleted): write zero offset/length to mark as empty slot
WritePackIndex(index, 0, 0);
CacheByteMap.Remove(index);
}
}
ModifiedByteMap.Clear();
}
}
///
/// Writes data blocks to files
///
/// List of global indices to write
/// List of values to write
/// Type to write
/// true if successful; otherwise, false
private static bool HelperWriteValueToFile(uint[] globalIndices, T[] values)
{
if (globalIndices == null || values == null || globalIndices.Length != values.Length || globalIndices.Length == 0)
return false;
int typeSize;
Func getFileByIndex;
if (typeof(T) == typeof(float))
{
typeSize = 4;
getFileByIndex = _hermesPathfinder.GetFloatPathFromIndex;
}
else if (typeof(T) == typeof(bool))
{
typeSize = 1;
getFileByIndex = _hermesPathfinder.GetBoolPathFromIndex;
}
else
return false;
var groups = new Dictionary localIndices, List values)>();
for (int i = 0; i < globalIndices.Length; i++)
{
uint globalIdx = globalIndices[i];
FileInfo file = getFileByIndex(globalIdx);
string fileKey = file.FullName;
uint localIdx = _hermesPathfinder.IndexToLocalIndex(globalIdx);
if (!groups.TryGetValue(fileKey, out var group))
{
group = (new List(), new List());
groups[fileKey] = group;
}
group.localIndices.Add(localIdx);
group.values.Add(values[i]);
}
bool allSuccess = true;
foreach (var kvp in groups)
{
FileInfo file = new FileInfo(kvp.Key);
var localIndices = kvp.Value.localIndices.ToArray();
var vals = kvp.Value.values.ToArray();
if (!WriteValuesToFile(localIndices, vals, file, typeSize))
allSuccess = false;
}
return allSuccess;
}
///
/// Writes data blocks that belong to the same file
///
private static bool WriteValuesToFile(uint[] localIndices, T[] values, FileInfo file, int typeSize)
{
try
{
const int maxLocalIndex = 65535;
long totalSize = (maxLocalIndex + 1) * typeSize;
file.Directory?.Create();
using var fs = new FileStream(file.FullName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
if (fs.Length != totalSize)
fs.SetLength(totalSize);
for (int i = 0; i < localIndices.Length; i++)
{
int idx = Convert.ToInt32(localIndices[i]);
if (idx < 0 || idx > maxLocalIndex) continue;
long offset = idx * typeSize;
fs.Seek(offset, SeekOrigin.Begin);
// Fascinating!
var bytes =
typeof(T) == typeof(float) ?
BitConverter.GetBytes(
(float)(object) values[i]) :
new[]
{
(byte)(
(bool)(object) values[i] ?
1 : 0
)
};
fs.Write(bytes, 0, bytes.Length);
}
return true;
}
catch
{
return false;
}
}
///
/// Attempts to read a slice of data from a file
///
/// The file
/// Offset value
/// Length to read
/// Output buffer
///
private static bool TryReadSlice(FileInfo file, uint offset, long length, out byte[] buffer)
{
buffer = null;
if (file == null || !file.Exists)
return false;
try
{
long fileLength = file.Length;
if (offset + length > fileLength)
return false;
buffer = new byte[length];
using (FileStream fs = file.OpenRead())
{
fs.Seek(offset, SeekOrigin.Begin);
int bytesRead = 0;
while (bytesRead < length)
{
int read = fs.Read(buffer, bytesRead, (int)(length - bytesRead));
if (read == 0)
{
buffer = null;
return false;
}
bytesRead += read;
}
}
return true;
}
catch (Exception)
{
buffer = null;
return false;
}
}
public class BoolIndexer
{
public bool this[uint index]
{
get => ReadBool(index, out var value) && value;
set => WriteBool(index, value);
}
}
public class FloatIndexer
{
public float this[uint index]
{
get => ReadFloat(index, out var value) ? value : 0f;
set => WriteFloat(index, value);
}
}
public class ByteIndexer
{
public byte[] this[uint index]
{
get => ReadByteArray(index, out var value) ? value : null;
set => WriteByteArray(index, value);
}
}
public class StringIndexer
{
public string this[uint index]
{
get
{
var data = Hermes.Bytes[index];
return data != null ? System.Text.Encoding.UTF8.GetString(data) : null;
}
set => Hermes.Bytes[index] = value != null ? System.Text.Encoding.UTF8.GetBytes(value) : null;
}
}
}
///
/// HermesPathfinder
/// Type used for locating data positions
///
public class HermesPathfinder
{
private readonly DirectoryInfo _rootDirectory;
///
/// Get root directory
///
public DirectoryInfo RootDirectory => _rootDirectory;
public HermesPathfinder(DirectoryInfo rootDirectory)
=> _rootDirectory = rootDirectory;
///
/// Gets the file path of the float data file for the specified data index
///
/// Data index (0-based)
/// File information containing the float data file for the given index
public FileInfo GetFloatPathFromIndex(uint index)
=> CombinePathByChunkId(IndexToChunkId(index), "f", ".dat");
///
/// Gets the file path of the float data file for the specified chunk ID
///
/// Chunk Id (each chunk contains 65536 indices)
/// File information containing the float data file for the given chunk
public FileInfo GetFloatPathFromChunkId(uint chunkId)
=> CombinePathByChunkId(chunkId, "f", ".dat");
///
/// Gets the file path of the boolean data file for the specified data index
///
/// Data index (0-based)
/// File information containing the boolean data file for the given index
public FileInfo GetBoolPathFromIndex(uint index)
=> CombinePathByChunkId(IndexToChunkId(index), "b", ".dat");
///
/// Gets the file path of the boolean data file for the specified chunk ID
///
/// Chunk Id (each chunk contains 65536 indices)
/// File information containing the boolean data file for the given chunk
public FileInfo GetBoolPathFromChunkId(uint chunkId)
=> CombinePathByChunkId(chunkId, "b", ".dat");
///
/// Convert chunkId to index
///
/// File Chunk Id
/// Index
public uint ChunkIdToIndex(uint chunkId)
=> chunkId * 65536;
///
/// Convert index to chunkId
///
/// Index
/// File Chunk Id
public uint IndexToChunkId(uint index)
=> index / 65536;
///
/// Convert global index to local index within a chunk
///
/// Global index
/// Local index within the chunk (0–65535)
public uint IndexToLocalIndex(uint index)
{
return index % 65536;
}
///
/// Convert chunk id and local index to global index
///
/// File Chunk Id
/// Local index within the chunk
/// Global index
public uint LocalIndexToIndex(uint chunkId, uint localIndex)
{
return chunkId * 65536 + localIndex;
}
private FileInfo CombinePathByChunkId(uint chunkId, string prefix, string suffix)
=> _rootDirectory.JoinToFile($"{prefix}{chunkId}{suffix}");
}
public static class DirectoryInfoExtensions
{
public static DirectoryInfo Join(this DirectoryInfo dir, params string[] paths)
{
string full = Path.Combine(dir.FullName, Path.Combine(paths));
return new DirectoryInfo(full);
}
public static FileInfo JoinToFile(this DirectoryInfo dir, params string[] paths)
{
string full = Path.Combine(dir.FullName, Path.Combine(paths));
return new FileInfo(full);
}
}
#endregion
#region UNITY
#if UNITY_2020_1_OR_NEWER
public class HermesOnDisk : MonoBehaviour
{
private enum SaveLocationType
{
PersistentDataPath,
TemporaryCachePath
}
///
/// Returns true if there are uncommitted changes in memory.
///
public static bool IsDirty => Hermes.IsDirty;
///
/// Boolean indexer
///
public static Hermes.BoolIndexer Boolean => Hermes.Boolean;
///
/// Float indexer
///
public static Hermes.FloatIndexer Float => Hermes.Float;
///
/// String indexer
///
public static Hermes.StringIndexer String => Hermes.String;
///
/// Variable-length byte array indexer
///
public static Hermes.ByteIndexer Bytes => Hermes.Bytes;
[Header("Save Location")]
[SerializeField] private SaveLocationType saveLocation = SaveLocationType.PersistentDataPath;
[SerializeField] private string relativePath = "Saves";
private void Awake()
{
string basePath = saveLocation switch
{
SaveLocationType.PersistentDataPath => Application.persistentDataPath,
SaveLocationType.TemporaryCachePath => Application.temporaryCachePath,
_ => Application.persistentDataPath
};
string root = Path.Combine(basePath, relativePath);
Hermes.Root = new DirectoryInfo(root);
}
private void OnDestroy()
{
Hermes.Store();
}
[ContextMenu("GC And Store")]
public void DoGCAndSave()
{
Hermes.GC();
Hermes.Store();
}
[ContextMenu("GC")]
public void DoGC()
{
Hermes.GC();
}
[ContextMenu("Store")]
public void DoStore()
{
Hermes.Store();
}
public void Save() => Hermes.Store();
public void GC() => Hermes.GC();
}
#endif
#endregion
}