初始化奇妙小屏幕控制器项目
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MatrixImageEditor;
|
||||
|
||||
public static class ImageOperations
|
||||
{
|
||||
private static readonly UTF8Encoding Utf8NoBomStrict = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
||||
|
||||
public static ImageConfig ReadConfig(string imagePath)
|
||||
{
|
||||
imagePath = ImagePathRules.ValidateExistingImage(imagePath);
|
||||
VerifySidecarIfPresent(imagePath);
|
||||
using var disk = new Fat16Disk(imagePath, writable: false);
|
||||
return ImageConfigCodec.Read(disk.ReadFile("MSCCFG.BIN"));
|
||||
}
|
||||
|
||||
public static void WriteConfig(string imagePath, ImageConfig config)
|
||||
{
|
||||
imagePath = ImagePathRules.ValidateExistingImage(imagePath);
|
||||
VerifySidecarIfPresent(imagePath);
|
||||
using var disk = new Fat16Disk(imagePath, writable: true);
|
||||
var current = disk.ReadFile("MSCCFG.BIN");
|
||||
if (ImageConfigCodec.Read(current).SoftwareVersion != config.SoftwareVersion) throw new InvalidDataException("禁止通过编辑器改变软件版本");
|
||||
var update = ImageConfigCodec.EncodeUpdate(current, config);
|
||||
disk.WriteFileRange("MSCCFG.BIN", ImageConfigCodec.SlotOffset(update.TargetSlot), update.SlotData);
|
||||
var verified = ImageConfigCodec.Read(disk.ReadFile("MSCCFG.BIN"));
|
||||
if (!ImageConfigCodec.Serialize(verified).Equals(ImageConfigCodec.Serialize(config), StringComparison.Ordinal)) throw new IOException("配置写后校验失败");
|
||||
}
|
||||
|
||||
public static void WriteSha256(string imagePath, IProgress<int>? progress)
|
||||
{
|
||||
imagePath = ImagePathRules.ValidateExistingImage(imagePath);
|
||||
using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4 * 1024 * 1024, FileOptions.SequentialScan);
|
||||
using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
|
||||
var buffer = new byte[4 * 1024 * 1024];
|
||||
long completed = 0;
|
||||
int read;
|
||||
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
hash.AppendData(buffer, 0, read);
|
||||
completed += read;
|
||||
progress?.Report((int)(completed * 100 / stream.Length));
|
||||
}
|
||||
var digest = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
|
||||
progress?.Report(100);
|
||||
|
||||
var sidecar = imagePath + ".sha256";
|
||||
var temporary = sidecar + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
try
|
||||
{
|
||||
var bytes = Utf8NoBomStrict.GetBytes($"{digest} {Path.GetFileName(imagePath)}\n");
|
||||
using (var output = new FileStream(temporary, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
output.Write(bytes);
|
||||
output.Flush(flushToDisk: true);
|
||||
}
|
||||
File.Move(temporary, sidecar, overwrite: true);
|
||||
var stored = ReadSidecarDigest(sidecar, Path.GetFileName(imagePath));
|
||||
if (!stored.Equals(digest, StringComparison.Ordinal)) throw new IOException("镜像 SHA-256 摘要写后校验失败");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(temporary)) File.Delete(temporary);
|
||||
}
|
||||
}
|
||||
|
||||
private static void VerifySidecarIfPresent(string imagePath)
|
||||
{
|
||||
var sidecar = imagePath + ".sha256";
|
||||
if (!File.Exists(sidecar)) return;
|
||||
var expected = ReadSidecarDigest(sidecar, Path.GetFileName(imagePath));
|
||||
using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4 * 1024 * 1024, FileOptions.SequentialScan);
|
||||
var actual = Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant();
|
||||
if (!actual.Equals(expected, StringComparison.Ordinal)) throw new InvalidDataException("镜像内容与 SHA-256 摘要不符,禁止编辑");
|
||||
}
|
||||
|
||||
private static string ReadSidecarDigest(string sidecarPath, string expectedFileName)
|
||||
{
|
||||
if (new FileInfo(sidecarPath).Length > 128 * 1024) throw new InvalidDataException("镜像 SHA-256 摘要文件过大");
|
||||
var bytes = File.ReadAllBytes(sidecarPath);
|
||||
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) throw new InvalidDataException("镜像 SHA-256 摘要必须使用无 BOM UTF-8 格式");
|
||||
|
||||
string content;
|
||||
try
|
||||
{
|
||||
content = Utf8NoBomStrict.GetString(bytes);
|
||||
}
|
||||
catch (DecoderFallbackException exception)
|
||||
{
|
||||
throw new InvalidDataException("镜像 SHA-256 摘要不是有效的 UTF-8 文本", exception);
|
||||
}
|
||||
|
||||
if (content.Length < 68 || content[^1] != '\n' || content.AsSpan(0, content.Length - 1).ContainsAny('\r', '\n'))
|
||||
{
|
||||
throw new InvalidDataException("镜像 SHA-256 摘要文件必须是以 LF 结尾的单行规范格式");
|
||||
}
|
||||
|
||||
var line = content.AsSpan(0, content.Length - 1);
|
||||
var digest = line[..64];
|
||||
if (digest.ContainsAnyExcept("0123456789abcdef") || line[64] != ' ' || line[65] != ' ')
|
||||
{
|
||||
throw new InvalidDataException("镜像 SHA-256 摘要文件格式无效");
|
||||
}
|
||||
|
||||
var storedFileName = line[66..].ToString();
|
||||
if (!storedFileName.Equals(expectedFileName, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidDataException("镜像 SHA-256 摘要中的文件名与当前镜像不一致");
|
||||
}
|
||||
return digest.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user