初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -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();
}
}