NHSE/NHSE.Core/Save/Meta/EncryptedFilePair.cs
Kurt b88c518d5c
Update FieldItemEditor for 3.0.0 (#716)
Updates the Field Item Editor to render layers based on the entire map, and the per-patch positioning of each layer.
Import/export will gracefully handle upgrade/downgrade, and viewport import/export will gracefully update tiles rather than a per-acre basis.

Performance has also been slightly improved; no allocation is done anymore when updating the image.
2026-01-25 16:55:38 -06:00

90 lines
2.9 KiB
C#

using System;
using System.Collections.Generic;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core;
/// <summary>
/// Represents two files -- <see cref="NameData"/> and <see cref="NameHeader"/> and their decrypted data.
/// </summary>
public abstract class EncryptedFilePair
{
private readonly byte[] RawData;
private readonly byte[] RawHeader;
private readonly ISaveFileProvider Provider;
protected Memory<byte> Raw => RawData;
public Span<byte> Data => RawData;
public Span<byte> Header => RawHeader;
public readonly FileHeaderInfo Info;
public readonly string NameData;
public readonly string NameHeader;
/// <summary>
/// Checks if the file pair exists in the specified provider.
/// </summary>
public static bool Exists(ISaveFileProvider provider, string name)
{
var nameData = $"{name}.dat";
var nameHeader = $"{name}Header.dat";
return provider.FileExists(nameHeader) && provider.FileExists(nameData);
}
protected EncryptedFilePair(ISaveFileProvider provider, string name)
{
Provider = provider;
NameData = $"{name}.dat";
NameHeader = $"{name}Header.dat";
var hd = provider.ReadFile(NameHeader);
var md = provider.ReadFile(NameData);
Encryption.Decrypt(hd, md);
RawHeader = hd;
RawData = md;
Info = Header[..FileHeaderInfo.SIZE].ToArray().ToClass<FileHeaderInfo>();
}
public void Save(uint seed)
{
var encrypt = Encryption.Encrypt(Data, seed, Header);
Provider.WriteFile(NameData, encrypt.Data.Span);
Provider.WriteFile(NameHeader, encrypt.Header.Span);
}
/// <summary>
/// Updates all hashes of <see cref="Data"/>.
/// </summary>
public void Hash()
{
var ver = Info.GetKnownRevisionIndex();
var hash = RevisionChecker.HashInfo[ver];
var details = hash.GetFile(NameData);
ArgumentNullException.ThrowIfNull(details, nameof(NameData));
foreach (var h in details.HashRegions)
WriteUInt32LittleEndian(Data[h.HashOffset..], Murmur3.Hash(Data[h.HashedRange]));
}
public IEnumerable<FileHashRegion> InvalidHashes()
{
var ver = Info.GetKnownRevisionIndex();
var hash = RevisionChecker.HashInfo[ver];
var details = hash.GetFile(NameData);
ArgumentNullException.ThrowIfNull(details, nameof(NameData));
foreach (var h in details.HashRegions)
{
var current = Murmur3.Hash(Data[h.HashedRange]);
var saved = ReadUInt32LittleEndian(Data[h.HashOffset..]);
if (current != saved)
yield return h;
}
}
protected string GetString(int offset, int maxLength) => StringUtil.GetString(Data, offset, maxLength);
protected static byte[] GetBytes(string value, int maxLength) => StringUtil.GetBytes(value, maxLength);
}