AquaDX/AquaMai.ErrorReport/CrashForm.cs
2025-02-14 12:22:01 +08:00

291 lines
11 KiB
C#

using System.Diagnostics;
using System.IO.Compression;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Matt.Encoding.Fountain;
using Matt.Random.Adapters;
using QRCoder;
using Timer = System.Windows.Forms.Timer;
namespace AquaMai.ErrorReport;
public partial class CrashForm : Form
{
public CrashForm()
{
InitializeComponent();
// 找那个竖着的屏幕
var screen = Screen.AllScreens.FirstOrDefault(s => s.Bounds.Width < s.Bounds.Height);
if (screen == null)
{
screen = Screen.PrimaryScreen;
}
// 把窗口大小设置成屏幕最短边的三分之二
var minSize = Math.Min(screen.Bounds.Width, screen.Bounds.Height) * 2 / 3;
Size = new Size(minSize, minSize);
// 如果屏幕是竖的,就放在屏幕下方那个正方形 1:1 位置的中间
if (screen.Bounds.Width < screen.Bounds.Height)
{
Location = new Point(screen.Bounds.Left + (screen.Bounds.Width - minSize) / 2, screen.Bounds.Top + (screen.Bounds.Height - screen.Bounds.Width) + (screen.Bounds.Width - minSize) / 2);
}
}
private string? zipFile = null;
private IEnumerator<Slice>? fountain = null;
private Timer? timer = null;
private byte[]? dataBuf = null;
private uint chksum = 0;
private async void CrashForm_Load(object sender, EventArgs e)
{
labelStatus.Text = "正在生成错误报告... Gathering error log...";
var exePath = Path.GetDirectoryName(Application.ExecutablePath);
var gameDir = Path.GetDirectoryName(exePath);
if (!File.Exists(Path.Combine(gameDir, "Sinmai.exe")))
{
gameDir = Environment.CurrentDirectory;
}
if (!File.Exists(Path.Combine(gameDir, "Sinmai.exe")))
{
labelStatus.Text = "未找到游戏文件夹 Game directory not found";
return;
}
var errorLogPath = Path.Combine(gameDir, "Errorlog");
if (!Directory.Exists(errorLogPath))
{
Directory.CreateDirectory(errorLogPath);
}
try
{
labelVersion.Text = "AquaMai v" + FileVersionInfo.GetVersionInfo(Path.Combine(gameDir, "Mods", "AquaMai.dll")).ProductVersion;
}
catch
{
labelVersion.Text = "AquaMai (Version Unknown)";
}
try
{
var logFiles = Directory.GetFiles(errorLogPath, "*.log");
zipFile = Path.Combine(errorLogPath, $"AquaMaiErrorReport_{DateTime.Now:yyyyMMddHHmmss}.zip");
using var zip = ZipFile.Open(zipFile, ZipArchiveMode.Create);
long latestLogTime = 0;
foreach (var logFile in logFiles)
{
zip.CreateEntryFromFile(logFile, Path.GetFileName(logFile));
if (long.TryParse(Path.GetFileNameWithoutExtension(logFile), out var time) && time > latestLogTime)
{
latestLogTime = time;
}
}
if (latestLogTime != 0)
{
var latestLogFile = Path.Combine(errorLogPath, $"{latestLogTime}.log");
var latestLogPng = Path.Combine(errorLogPath, $"{latestLogTime}.png");
if (File.Exists(latestLogFile))
{
textLog.Text = File.ReadAllText(latestLogFile).Replace("\r\n", "\n").Replace("\n", "\r\n");
}
// if (File.Exists(latestLogPng))
// {
// zip.CreateEntryFromFile(latestLogPng, Path.GetFileName(latestLogPng));
// }
}
await CreateZipTxtFromDirContent(zip, Path.Combine(gameDir, "Sinmai_Data", "StreamingAssets"));
await CreateZipTxtFromDirContent(zip, Path.Combine(gameDir, "Mods"), true);
await CreateZipTxtFromDirContent(zip, Path.Combine(gameDir, "UserLibs"));
await CreateZipTxtFromDirContent(zip, Path.Combine(gameDir, "LocalAssets"));
await CreateZipTxtFromDirContent(zip, gameDir, true);
AddFileToZipIfExist(zip, Path.Combine(gameDir, "AquaMai.toml"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "mai2.ini"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "segatools.ini"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "daemon/segatools.ini"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../AMDaemon/segatools.ini"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "DEVICE/aime.txt"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "daemon/DEVICE/aime.txt"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../AMDaemon/DEVICE/aime.txt"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "start.bat"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "start.cmd"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "启动.bat"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "启动.cmd"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "!启动.bat"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "!启动.cmd"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../start.bat"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../start.cmd"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../启动.bat"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../启动.cmd"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../!启动.bat"));
AddFileToZipIfExist(zip, Path.Combine(gameDir, "../!启动.cmd"));
var melonLog = Path.Combine(gameDir, "MelonLoader", "Latest.log");
if (File.Exists(melonLog))
{
// zip.CreateEntryFromFile(melonLog, Path.GetFileName(melonLog));
var entry = zip.CreateEntry("MelonLoader.txt");
using var stream = entry.Open();
using var fs = new FileStream(melonLog, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
await fs.CopyToAsync(stream);
}
labelStatus.Text = $"Open https://qrss.netlify.app/ or scan the QRCode to get error report\n{zipFile}";
}
catch (Exception ex)
{
labelStatus.Text = $"生成错误报告失败 Failed to generate error report";
if (string.IsNullOrWhiteSpace(textLog.Text))
{
textLog.Text = ex.ToString();
}
}
finally
{
textLog.Select(0, 0);
}
try
{
var zipBuf = File.ReadAllBytes(zipFile);
dataBuf = AppendFileHeaderMetaToBuffer(zipBuf, new FileHeaderMeta()
{
Filename = Path.GetFileName(zipFile),
ContentType = "application/zip"
});
fountain = SliceHelpers.CreateGenerator(dataBuf, 500, () => new RandomAdapter(new Random())).GetEnumerator();
}
catch
{
labelStatus.Text = "生成喷泉码失败 Failed to generate fountain code";
}
timer = new Timer { Interval = 50 };
timer.Tick += Tick;
timer.Start();
}
private static void AddFileToZipIfExist(ZipArchive zip, string file)
{
if (File.Exists(file))
{
zip.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
private static byte[] AppendFileHeaderMetaToBuffer(byte[] data, FileHeaderMeta meta)
{
string json = $"{{\"filename\":\"{meta.Filename}\",\"contentType\":\"{meta.ContentType}\"}}";
byte[] metaBuffer = Encoding.UTF8.GetBytes(json);
return MergeByteArrays(metaBuffer, data);
}
QRCodeGenerator qrGenerator = new QRCodeGenerator();
private void Tick(object _, EventArgs _ea)
{
if (fountain == null)
{
return;
}
if (fountain.MoveNext())
{
var slice = fountain.Current;
int[] indices = slice.Coefficients
.Select((value, index) => (value, index)) // 生成 (值, 索引) 对
.Where(pair => pair.value) // 仅保留值为 true 的
.Select(pair => pair.index) // 提取索引
.ToArray();
using var data = new MemoryStream();
using var writer = new BinaryWriter(data);
writer.Write((uint)indices.Length);
foreach (var index in indices)
{
writer.Write((uint)index);
}
writer.Write((uint)slice.Coefficients.Count);
var sliceData = slice.Data.ToArray();
writer.Write((uint)dataBuf.Length);
if (chksum == 0)
{
chksum = ChecksumCalculator.GetChecksum(dataBuf, (uint)slice.Coefficients.Count);
}
writer.Write((uint)chksum);
writer.Write(sliceData);
writer.Flush();
using QRCodeData qrCodeData = qrGenerator.CreateQrCode($"https://qrss.netlify.app/#{Convert.ToBase64String(data.ToArray())}", QRCodeGenerator.ECCLevel.M);
pictureBox1.Image = new QRCode(qrCodeData).GetGraphic(20);
}
}
private async Task CreateZipTxtFromDirContent(ZipArchive zip, string dir, bool includeMd5 = false)
{
if (!Directory.Exists(dir)) return;
var subFiles = Directory.GetFileSystemEntries(dir);
using var subZip = zip.CreateEntry($"{Path.GetFileName(dir)}.txt").Open();
using var writer = new StreamWriter(subZip);
foreach (var subFile in subFiles)
{
if (includeMd5 && File.Exists(subFile))
{
await writer.WriteLineAsync($"{subFile} {GetFileMD5(subFile)}");
}
else
{
await writer.WriteLineAsync(subFile);
}
}
await writer.FlushAsync();
}
public static byte[] MergeByteArrays(byte[] array1, byte[] array2)
{
byte[][] arrays = new byte[][] { array1, array2 };
int totalLength = arrays.Sum(arr => arr.Length + 4); // 4 bytes for each length (Uint32)
byte[] mergedArray = new byte[totalLength];
int offset = 0;
foreach (var arr in arrays)
{
int length = arr.Length;
// Store the length as a 4-byte Uint32
mergedArray[offset++] = (byte)((length >> 24) & 0xFF);
mergedArray[offset++] = (byte)((length >> 16) & 0xFF);
mergedArray[offset++] = (byte)((length >> 8) & 0xFF);
mergedArray[offset++] = (byte)(length & 0xFF);
// Copy data
Array.Copy(arr, 0, mergedArray, offset, length);
offset += length;
}
return mergedArray;
}
private static string GetFileMD5(string filePath)
{
using var md5 = MD5.Create();
using var stream = File.OpenRead(filePath);
byte[] hashBytes = md5.ComputeHash(stream);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
private void CrashForm_KeyDown(object sender, KeyEventArgs e)
{
Application.Exit();
}
}