CI / Go Backend (push) Canceled after 0s
初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
762 lines
28 KiB
C#
762 lines
28 KiB
C#
using System.Diagnostics;
|
||
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.Drawing.Imaging;
|
||
using System.IO;
|
||
using System.Net;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media.Imaging;
|
||
using PCL.Core.App;
|
||
using PCL.Core.App.Tools;
|
||
using PCL.Core.IO;
|
||
using PCL.Core.IO.Net;
|
||
using PCL.Core.UI;
|
||
using PCL.Core.Utils.OS;
|
||
using PCL.Core.Utils.Secret;
|
||
using PCL.Core.Utils.Validate;
|
||
using PCL.Network;
|
||
using PCL.Network.Loaders;
|
||
using PCL.Core.App.Localization;
|
||
using System.Globalization;
|
||
|
||
namespace PCL;
|
||
|
||
public partial class PageToolsTest
|
||
{
|
||
private Bitmap currentSkinBitmap;
|
||
private Bitmap generatedHeadBitmap;
|
||
|
||
private int headSize = 64;
|
||
private string skinPath = "";
|
||
|
||
public PageToolsTest()
|
||
{
|
||
InitializeComponent();
|
||
BtnSelectSkin.Click += BtnSelectSkin_Click;
|
||
CmbHeadSize.SelectionChanged += CmbHeadSize_SelectionChanged;
|
||
Loaded += (_, _) => MeLoaded();
|
||
#if DEBUG
|
||
BtnCrash.Visibility = Visibility.Visible;
|
||
#endif
|
||
}
|
||
|
||
private void MeLoaded()
|
||
{
|
||
BtnDownloadStart.IsEnabled = false;
|
||
|
||
TextDownloadFolder.Text = States.Tool.DownloadFolder;
|
||
TextDownloadFolder.Validate();
|
||
|
||
if (!string.IsNullOrEmpty(TextDownloadFolder.ValidateResult) || string.IsNullOrEmpty(TextDownloadFolder.Text))
|
||
TextDownloadFolder.Text = ModBase.exePath + @"PCL\MyDownload\";
|
||
|
||
TextDownloadFolder.Validate();
|
||
TextDownloadName.Validate();
|
||
TextDownloadUrl.Validate();
|
||
StartButtonRefresh();
|
||
}
|
||
|
||
private void StartButtonRefresh()
|
||
{
|
||
BtnDownloadStart.IsEnabled = string.IsNullOrEmpty(TextDownloadFolder.ValidateResult) &&
|
||
string.IsNullOrEmpty(TextDownloadUrl.ValidateResult) &&
|
||
string.IsNullOrEmpty(TextDownloadName.ValidateResult);
|
||
|
||
BtnDownloadOpen.IsEnabled = string.IsNullOrEmpty(TextDownloadFolder.ValidateResult);
|
||
|
||
BtnAchievementPreview.IsEnabled = string.IsNullOrEmpty(AchievementBlockTextBox.ValidateResult) &&
|
||
string.IsNullOrEmpty(AchievementTitleTextBox.ValidateResult) &&
|
||
string.IsNullOrEmpty(AchievementString1TextBox.ValidateResult);
|
||
|
||
BtnAchievementSave.IsEnabled = string.IsNullOrEmpty(AchievementBlockTextBox.ValidateResult) &&
|
||
string.IsNullOrEmpty(AchievementTitleTextBox.ValidateResult) &&
|
||
string.IsNullOrEmpty(AchievementString1TextBox.ValidateResult);
|
||
}
|
||
|
||
private void SaveCacheDownloadFolder(object sender, RoutedEventArgs e)
|
||
{
|
||
States.Tool.DownloadFolder = TextDownloadFolder.Text;
|
||
TextDownloadName.Validate();
|
||
}
|
||
|
||
private static void DownloadState(ModLoader.LoaderCombo<int> loader)
|
||
{
|
||
try
|
||
{
|
||
switch (loader.State)
|
||
{
|
||
case ModBase.LoadState.Finished:
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.CustomDownload.Finished", loader.name), HintType.Success);
|
||
Console.Beep();
|
||
break;
|
||
}
|
||
case ModBase.LoadState.Failed:
|
||
{
|
||
ModBase.Log(
|
||
loader.Error,
|
||
$"{loader.name}失败",
|
||
ModBase.LogLevel.Msgbox,
|
||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||
Console.Beep();
|
||
break;
|
||
}
|
||
case ModBase.LoadState.Aborted:
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.CustomDownload.Aborted", loader.name));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
}
|
||
}
|
||
|
||
public static void StartCustomDownload(string url, string fileName, string folder = null, string userAgent = "")
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(folder))
|
||
{
|
||
folder = SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.CustomDownload.SelectLocation"), fileName);
|
||
if (!folder.Contains(@"\")) return;
|
||
if (folder.EndsWith(fileName)) folder = folder[..^fileName.Length];
|
||
}
|
||
|
||
folder = folder.Replace("/", @"\").TrimEnd(new[] { '\\' }) + @"\";
|
||
try
|
||
{
|
||
Directory.CreateDirectory(folder);
|
||
ModBase.CheckPermissionWithException(folder);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
$"访问文件夹失败({folder})",
|
||
ModBase.LogLevel.Hint,
|
||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||
return;
|
||
}
|
||
|
||
ModBase.Log("[Download] 自定义下载文件名:" + fileName);
|
||
ModBase.Log("[Download] 自定义下载文件目标:" + folder);
|
||
var uuid = ModBase.GetUuid();
|
||
ModLoader.LoaderBase loaderdownload;
|
||
if (new HttpValidator().Validate(url).IsValid)
|
||
loaderdownload = new LoaderDownload(Lang.Text("Tools.Test.CustomDownload.LoaderName", fileName),
|
||
new List<DownloadFile> { new(new[] { url }, folder + fileName, null, true, userAgent) });
|
||
else // UNC 路径
|
||
loaderdownload = new LoaderDownloadUnc(Lang.Text("Tools.Test.CustomDownload.LoaderName", fileName),
|
||
new Tuple<string, string>(url, folder + fileName));
|
||
var loaderCombo = new ModLoader.LoaderCombo<int>(Lang.Text("Tools.Test.CustomDownload.LoaderTitle", uuid), new[] { loaderdownload })
|
||
{ OnStateChanged = a => DownloadState((ModLoader.LoaderCombo<int>)a) };
|
||
loaderCombo.Start();
|
||
ModLoader.LoaderTaskbarAdd(loaderCombo);
|
||
ModMain.frmMain.BtnExtraDownload.ShowRefresh();
|
||
ModMain.frmMain.BtnExtraDownload.Ribble();
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
"开始自定义下载失败",
|
||
ModBase.LogLevel.Feedback,
|
||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||
}
|
||
}
|
||
|
||
public static void Jrrp()
|
||
{
|
||
var random = new Random(GenerateDailySeed());
|
||
var luckValue = random.Next(0, 101);
|
||
var rating = GetRating(luckValue);
|
||
var title = Lang.Text("Tools.Test.Luck.MsgboxTitle", Lang.Date(DateTime.Now, "d"));
|
||
|
||
if (luckValue >= 60)
|
||
ModMain.MyMsgBox(Lang.Text("Tools.Test.Luck.MessageGood", luckValue, rating), title);
|
||
else
|
||
ModMain.MyMsgBox(Lang.Text("Tools.Test.Luck.MessageBad", luckValue, rating), title, isWarn: luckValue <= 30);
|
||
}
|
||
|
||
public static void RubbishClear()
|
||
{
|
||
ModBase.RunInUi(() =>
|
||
{
|
||
if (ModMain.frmToolsTest is not null && ModMain.frmToolsTest.BtnClear is not null)
|
||
ModMain.frmToolsTest.BtnClear.IsEnabled = false;
|
||
});
|
||
// 只有当没有运行中的Minecraft游戏且启动器不在加载状态时才能清理
|
||
|
||
// 清理的文件数量
|
||
// 所有 Minecraft 文件夹
|
||
|
||
|
||
// 寻找所有 Minecraft 文件夹
|
||
|
||
// 删除 Minecraft 的缓存
|
||
// 删除日志和崩溃报告并计数
|
||
|
||
// 删除 Natives 文件
|
||
|
||
// 删除 PCL 的缓存
|
||
|
||
ModBase.RunInNewThread(() =>
|
||
{
|
||
try
|
||
{
|
||
if (!ModWatcher.hasRunningMinecraft && ModLaunch.mcLaunchLoader.State != ModBase.LoadState.Loading)
|
||
{
|
||
if (ModNet.HasDownloadingTask())
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Clean.WaitForDownload"));
|
||
return;
|
||
}
|
||
|
||
if (!ModFolder.mcFolderList.Any()) ModFolder.mcFolderListLoader.Start();
|
||
if (States.Hint.CleanJunkFile <= 2)
|
||
{
|
||
if (ModMain.MyMsgBox(
|
||
Lang.Text("Tools.Test.Clean.ConfirmMessage"),
|
||
Lang.Text("Tools.Test.Clean.ConfirmTitle"),
|
||
Lang.Text("Common.Action.Confirm"),
|
||
Lang.Text("Common.Action.Cancel")
|
||
) == 2) return;
|
||
States.Hint.CleanJunkFile += 1;
|
||
}
|
||
|
||
var num = 0;
|
||
var cleanMcFolderList = new List<DirectoryInfo>();
|
||
if (!ModFolder.mcFolderList.Any()) ModFolder.mcFolderListLoader.WaitForExit();
|
||
foreach (var mcFolder in ModFolder.mcFolderList)
|
||
{
|
||
cleanMcFolderList.Add(new DirectoryInfo(mcFolder.Location));
|
||
var dirInfo = new DirectoryInfo(mcFolder.Location + "versions");
|
||
if (dirInfo.Exists)
|
||
foreach (var item in dirInfo.EnumerateDirectories())
|
||
cleanMcFolderList.Add(item);
|
||
}
|
||
|
||
foreach (var dirInfo in cleanMcFolderList)
|
||
{
|
||
num += ModBase.DeleteDirectory(
|
||
dirInfo.FullName + (dirInfo.FullName.EndsWith(@"\") ? "" : @"\") + @"crash-reports\", true);
|
||
num += ModBase.DeleteDirectory(
|
||
dirInfo.FullName + (dirInfo.FullName.EndsWith(@"\") ? "" : @"\") + @"logs\", true);
|
||
foreach (var fileInfo in dirInfo.EnumerateFiles("*"))
|
||
if (fileInfo.Name.StartsWith("hs_err_pid") || fileInfo.Name.EndsWith(".log") ||
|
||
fileInfo.Name == "WailaErrorOutput.txt")
|
||
{
|
||
fileInfo.Delete();
|
||
num += 1;
|
||
}
|
||
|
||
foreach (var dirInfo2 in dirInfo.EnumerateDirectories())
|
||
if ((dirInfo2.Name ?? "") == (dirInfo2.Name + "-natives" ?? "") ||
|
||
dirInfo2.Name == "natives-windows-x86_64")
|
||
num += ModBase.DeleteDirectory(dirInfo2.FullName, true);
|
||
}
|
||
|
||
num += ModBase.DeleteDirectory(ModBase.pathTemp, true);
|
||
num += ModBase.DeleteDirectory(Path.Combine(SystemPaths.DriveLetter, "ProgramData", "PCL"), true);
|
||
if (num != 0)
|
||
{
|
||
ModMain.MyMsgBox(Lang.Text("Tools.Test.Clean.ClearedMessage", num),
|
||
Lang.Text("Tools.Test.Clean.Cleared"), Lang.Text("Common.Action.Confirm"), "", "", false, true, true);
|
||
Process.Start(new ProcessStartInfo(Basics.ExecutablePath));
|
||
FormMain.EndProgramForce();
|
||
}
|
||
else
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Clean.NoFiles"));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Clean.CloseGameFirst"));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(
|
||
ex,
|
||
"清理垃圾失败",
|
||
ModBase.LogLevel.Hint,
|
||
userSummary: Lang.Text("Tools.Test.Error.OperationFailed"));
|
||
}
|
||
finally
|
||
{
|
||
ModBase.RunInUiWait(() =>
|
||
{
|
||
if (ModMain.frmToolsTest is not null && ModMain.frmToolsTest.BtnClear is not null)
|
||
ModMain.frmToolsTest.BtnClear.IsEnabled = true;
|
||
});
|
||
}
|
||
}, "Rubbish Clear");
|
||
}
|
||
|
||
public static string GetRandomCave()
|
||
{
|
||
return Lang.Text("Tools.Test.CeNotice");
|
||
}
|
||
|
||
public static string GetRandomHint()
|
||
{
|
||
return Lang.Text("Tools.Test.CeNotice");
|
||
}
|
||
|
||
public static string GetRandomPresetHint()
|
||
{
|
||
return Lang.Text("Tools.Test.CeNotice");
|
||
}
|
||
|
||
private void TextDownloadUrl_TextChanged(object sender, TextChangedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
if (!string.IsNullOrEmpty(TextDownloadName.Text) || string.IsNullOrEmpty(TextDownloadUrl.Text)) return;
|
||
TextDownloadName.Text = ModBase.GetFileNameFromPath(WebUtility.UrlDecode(TextDownloadUrl.Text));
|
||
StartButtonRefresh();
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
|
||
private void MyTextButton_Click(object sender, EventArgs e)
|
||
{
|
||
var text = SystemDialogs.SelectFolder();
|
||
if (!string.IsNullOrEmpty(text)) TextDownloadFolder.Text = text;
|
||
}
|
||
|
||
private void BtnDownloadOpen_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
var text = TextDownloadFolder.Text;
|
||
Directory.CreateDirectory(text);
|
||
Basics.OpenPath(text);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "打开下载文件夹失败");
|
||
}
|
||
}
|
||
|
||
private void BtnDownloadStart_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
StartCustomDownload(TextDownloadUrl.Text, TextDownloadName.Text, TextDownloadFolder.Text);
|
||
TextDownloadUrl.Text = "";
|
||
TextDownloadUrl.Validate();
|
||
TextDownloadUrl.ForceShowAsSuccess();
|
||
TextDownloadName.Text = "";
|
||
TextDownloadName.Validate();
|
||
TextDownloadName.ForceShowAsSuccess();
|
||
StartButtonRefresh();
|
||
}
|
||
|
||
private void TextDownloadUrl_ValidateChanged(object sender, RoutedEventArgs e)
|
||
{
|
||
StartButtonRefresh();
|
||
}
|
||
|
||
private void TextDownloadFolder_ValidateChanged(object sender, EventArgs e)
|
||
{
|
||
StartButtonRefresh();
|
||
}
|
||
|
||
private void TextDownloadName_ValidateChanged(object sender, EventArgs e)
|
||
{
|
||
StartButtonRefresh();
|
||
}
|
||
|
||
private void BtnClear_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
RubbishClear();
|
||
}
|
||
|
||
// 下载正版玩家皮肤
|
||
private void BtnSkinSave_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
var id = TextSkinID.Text;
|
||
HintService.Hint(Lang.Text("Tools.Test.Skin.Fetching"));
|
||
ModBase.RunInNewThread(() =>
|
||
{
|
||
try
|
||
{
|
||
if (id.Length < 3)
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Skin.InvalidId"));
|
||
}
|
||
else
|
||
{
|
||
var result = (string)ModProfile.McLoginMojangUuid(id, true);
|
||
result = ModSkin.McSkinGetAddress(result, "Mojang");
|
||
result = ModSkin.McSkinDownload(result);
|
||
ModBase.RunInUi(() =>
|
||
{
|
||
var path = SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.Skin.Save"), $"{id}.png", Lang.Text("Tools.Test.Skin.FileFilter"));
|
||
ModBase.CopyFile(result, path);
|
||
HintService.Hint(Lang.Text("Tools.Test.Skin.Saved", id), HintType.Success);
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
if (ex.ToString().Contains("429"))
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Skin.TooFrequent"), HintType.Error);
|
||
ModBase.Log($"获取正版皮肤失败({id}):获取皮肤太过频繁,请 5 分钟后再试!");
|
||
}
|
||
else
|
||
{
|
||
ModBase.Log(ex, $"获取正版皮肤失败({id})");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 今日人品
|
||
private void BtnLuck_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
Jrrp();
|
||
}
|
||
|
||
public static int GenerateDailySeed()
|
||
{
|
||
var datePart = DateTime.Today.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||
|
||
return DJB2Hash(datePart + Identify.LauncherId);
|
||
}
|
||
|
||
private static int DJB2Hash(string str)
|
||
{
|
||
var hash = 5381L;
|
||
var prime = 33L;
|
||
foreach (var c in str)
|
||
{
|
||
long charValue = c;
|
||
hash = (hash * prime + charValue) % 0x100000000L;
|
||
}
|
||
|
||
return (int)(hash & 0x7FFFFFFFL);
|
||
}
|
||
|
||
public static string GetRating(int luckValue)
|
||
{
|
||
var key = luckValue switch
|
||
{
|
||
100 => "Tools.Test.Luck.Rating100",
|
||
>= 95 => "Tools.Test.Luck.Rating95",
|
||
>= 90 => "Tools.Test.Luck.Rating90",
|
||
>= 60 => "Tools.Test.Luck.Rating60",
|
||
>= 40 => "Tools.Test.Luck.Rating40",
|
||
>= 30 => "Tools.Test.Luck.Rating30",
|
||
>= 10 => "Tools.Test.Luck.Rating10",
|
||
_ => "Tools.Test.Luck.Rating0"
|
||
};
|
||
|
||
return Lang.Text(key);
|
||
}
|
||
|
||
private void BtnCreateShortcut_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
var shortcutName = Lang.Text("Tools.Test.Shortcut.FileName", ".lnk");
|
||
var desktopName = Lang.Text("Tools.Test.Shortcut.Desktop");
|
||
var startName = Lang.Text("Tools.Test.Shortcut.StartMenu");
|
||
var desktop = Paths.GetSpecialPath(Environment.SpecialFolder.Desktop, shortcutName);
|
||
var start = Paths.GetSpecialPath(Environment.SpecialFolder.StartMenu, @"Programs\" + shortcutName);
|
||
var choice =
|
||
ModMain.MyMsgBox(
|
||
Lang.Text("Tools.Test.Shortcut.ConfirmMessage", desktopName, desktop, startName, start),
|
||
Lang.Text("Tools.Test.Shortcut.SelectLocation"), Lang.Text("Common.Action.Cancel"), desktopName, startName);
|
||
if (choice == 1)
|
||
return;
|
||
var shortcutPath = choice == 2 ? desktop : start;
|
||
var locationName = choice == 2 ? desktopName : startName;
|
||
Files.CreateShortcut(shortcutPath, Basics.ExecutablePath);
|
||
HintService.Hint(Lang.Text("Tools.Test.Shortcut.Created", locationName), HintType.Success);
|
||
}
|
||
|
||
// 启动计数显示
|
||
private void BtnLaunchCount_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
ModMain.MyMsgBox(Lang.Text("Tools.Test.LaunchCount.Message", States.System.LaunchCount), Lang.Text("Tools.Test.LaunchCount.Title"));
|
||
}
|
||
|
||
private async void BtnAchievementPreview_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
var url = GetAchievementUrl();
|
||
ModBase.Log("[Net] 获取网络结果" + url);
|
||
await LoadImageAsync(url);
|
||
}
|
||
|
||
private async Task LoadImageAsync(string imageUrl)
|
||
{
|
||
var client = NetworkService.GetClient();
|
||
try
|
||
{
|
||
var response = await client.GetAsync(imageUrl);
|
||
if (response.IsSuccessStatusCode)
|
||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||
{
|
||
var bitmapImage = new BitmapImage();
|
||
bitmapImage.BeginInit();
|
||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||
bitmapImage.StreamSource = stream;
|
||
bitmapImage.EndInit();
|
||
bitmapImage.Freeze();
|
||
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
AchievementImage.Source = bitmapImage;
|
||
AchievementImage.Visibility = Visibility.Visible;
|
||
});
|
||
}
|
||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
ModBase.Log("获取成就图片失败(404)");
|
||
HintService.Hint(Lang.Text("Tools.Test.Achievement.FetchFailed"), HintType.Error);
|
||
});
|
||
|
||
else
|
||
Dispatcher.Invoke(() => ModBase.Log("获取成就图片失败(" + (int)response.StatusCode + ")"));
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
Dispatcher.Invoke(() => ModBase.Log(ex, "获取成就图片失败"));
|
||
}
|
||
}
|
||
|
||
private async void BtnAchievementSave_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
var url = GetAchievementUrl();
|
||
await DownloadImageToLocalAsync(url);
|
||
}
|
||
|
||
private async Task DownloadImageToLocalAsync(string imageUrl)
|
||
{
|
||
var savePath = ModBase.pathTemp + @"Download\" + ModBase.GetHash(imageUrl) + ".png";
|
||
var client = NetworkService.GetClient();
|
||
try
|
||
{
|
||
// 异步发送 GET 请求
|
||
var response = await client.GetAsync(imageUrl);
|
||
|
||
// 如果响应状态码是成功的,则继续
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
// 异步读取响应内容为字节流
|
||
var imageBytes = await response.Content.ReadAsByteArrayAsync();
|
||
|
||
// 将字节写入本地文件
|
||
File.WriteAllBytes(savePath, imageBytes);
|
||
|
||
var path =
|
||
SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.Achievement.Save"), AchievementTitleTextBox.Text + ".png", Lang.Text("Tools.Test.Achievement.FileFilter"));
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
ModBase.Log("用户取消了保存操作");
|
||
File.Delete(savePath);
|
||
return;
|
||
}
|
||
|
||
ModBase.CopyFile(savePath, path);
|
||
File.Delete(savePath);
|
||
HintService.Hint(Lang.Text("Tools.Test.Achievement.Saved"), HintType.Success);
|
||
}
|
||
// 下载成功,返回 True
|
||
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||
{
|
||
// 捕获 404 错误
|
||
ModBase.Log("获取成就图片失败(404)");
|
||
HintService.Hint(Lang.Text("Tools.Test.Achievement.FetchFailed"), HintType.Error);
|
||
}
|
||
else
|
||
{
|
||
// 处理其他非成功状态码
|
||
ModBase.Log("获取成就图片失败(" + (int)response.StatusCode + ")");
|
||
}
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
// 捕获所有其他异常(如网络连接问题)
|
||
ModBase.Log(ex, "获取成就图片失败");
|
||
}
|
||
}
|
||
|
||
private string GetAchievementUrl()
|
||
{
|
||
var block = AchievementBlockTextBox.Text.Trim();
|
||
var title = AchievementTitleTextBox.Text.Replace(" ", "..");
|
||
var str1 = AchievementString1TextBox.Text.Replace(" ", "..");
|
||
var str2 = AchievementString2TextBox.Text.Replace(" ", "..");
|
||
var url = $"https://minecraft-api.com/api/achivements/{block}/{title}/{str1}";
|
||
if (!string.IsNullOrEmpty(str2)) url += $"/{str2}";
|
||
return url;
|
||
}
|
||
|
||
private void BtnCrash_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
throw new Exception(Lang.Text("Tools.Test.Crash.ManualCrash"));
|
||
}
|
||
|
||
private int GetHeadSize() => CmbHeadSize.SelectedIndex switch
|
||
{
|
||
0 => 64,
|
||
1 => 96,
|
||
2 => 128,
|
||
_ => 64
|
||
};
|
||
|
||
private void BtnSelectSkin_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
var filePath = SystemDialogs.SelectFile(Lang.Text("Tools.Test.Avatar.FileFilter"),
|
||
Lang.Text("Tools.Test.Avatar.SelectSkinFile"));
|
||
if (!string.IsNullOrEmpty(filePath)) LoadAndGenerateHead(filePath);
|
||
}
|
||
|
||
private void LoadAndGenerateHead(string skinPath)
|
||
{
|
||
try
|
||
{
|
||
using (var stream = new FileStream(skinPath, FileMode.Open, FileAccess.Read))
|
||
{
|
||
currentSkinBitmap = new Bitmap(stream);
|
||
}
|
||
|
||
this.skinPath = skinPath;
|
||
|
||
if (currentSkinBitmap.Width != currentSkinBitmap.Height)
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Avatar.InvalidSize"), HintType.Error);
|
||
SkinPreviewBorder.Visibility = Visibility.Collapsed;
|
||
return;
|
||
}
|
||
|
||
generatedHeadBitmap = GenerateHeadFromSkin(currentSkinBitmap);
|
||
|
||
ImgFace.Source = BitmapToBitmapImage(generatedHeadBitmap);
|
||
ImgHair.Source = null;
|
||
|
||
SkinPreviewBorder.Visibility = Visibility.Visible;
|
||
HintService.Hint(Lang.Text("Tools.Test.Avatar.Generated"), HintType.Success);
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
ModBase.Log(ex, "生成头像失败");
|
||
HintService.Hint(Lang.Text("Tools.Test.Avatar.GenerateFailed", ex.Message), HintType.Error);
|
||
SkinPreviewBorder.Visibility = Visibility.Collapsed;
|
||
}
|
||
}
|
||
|
||
private Bitmap GenerateHeadFromSkin(Bitmap skinBitmap)
|
||
{
|
||
var scale = skinBitmap.Width / 64;
|
||
headSize = GetHeadSize();
|
||
var headBitmap = new Bitmap(headSize, headSize);
|
||
|
||
using (var g = Graphics.FromImage(headBitmap))
|
||
{
|
||
g.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||
g.PixelOffsetMode = PixelOffsetMode.Half;
|
||
|
||
DrawFaceLayer(g, skinBitmap, scale);
|
||
if (skinBitmap.Width >= 64) DrawHairLayer(headBitmap, skinBitmap, scale);
|
||
}
|
||
|
||
return headBitmap;
|
||
}
|
||
|
||
private void DrawFaceLayer(Graphics g, Bitmap skinBitmap, int scale)
|
||
{
|
||
var faceRect = new Rectangle(8 * scale, 8 * scale, 8 * scale, 8 * scale);
|
||
var faceSize = headSize - headSize / 8;
|
||
var faceScaled = new Bitmap(faceSize, faceSize);
|
||
|
||
using (var gFace = Graphics.FromImage(faceScaled))
|
||
{
|
||
gFace.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||
gFace.PixelOffsetMode = PixelOffsetMode.Half;
|
||
gFace.DrawImage(skinBitmap, new Rectangle(0, 0, faceSize, faceSize), faceRect, GraphicsUnit.Pixel);
|
||
}
|
||
|
||
var offset = headSize / 16;
|
||
g.DrawImage(faceScaled, offset, offset, faceSize, faceSize);
|
||
}
|
||
|
||
private void DrawHairLayer(Bitmap headBitmap, Bitmap skinBitmap, int scale)
|
||
{
|
||
var hairRect = new Rectangle(40 * scale, 8 * scale, 8 * scale, 8 * scale);
|
||
var hairScaled = new Bitmap(headSize, headSize);
|
||
|
||
using (var gHair = Graphics.FromImage(hairScaled))
|
||
{
|
||
gHair.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||
gHair.PixelOffsetMode = PixelOffsetMode.Half;
|
||
gHair.DrawImage(skinBitmap, new Rectangle(0, 0, headSize, headSize), hairRect, GraphicsUnit.Pixel);
|
||
}
|
||
|
||
for (int x = 0, loopTo = headSize - 1; x <= loopTo; x++)
|
||
for (int y = 0, loopTo1 = headSize - 1; y <= loopTo1; y++)
|
||
{
|
||
var pixel = hairScaled.GetPixel(x, y);
|
||
if (pixel.A > 0) headBitmap.SetPixel(x, y, pixel);
|
||
}
|
||
}
|
||
|
||
private void BtnSaveHead_Click(object sender, MouseButtonEventArgs e)
|
||
{
|
||
if (generatedHeadBitmap is null)
|
||
{
|
||
HintService.Hint(Lang.Text("Tools.Test.Avatar.SelectFirst"), HintType.Error);
|
||
return;
|
||
}
|
||
|
||
var savePath = SystemDialogs.SelectSaveFile(Lang.Text("Tools.Test.Avatar.Save"), "Head.png");
|
||
|
||
if (string.IsNullOrEmpty(savePath))
|
||
return;
|
||
|
||
generatedHeadBitmap.Save(savePath, ImageFormat.Png);
|
||
HintService.Hint(Lang.Text("Tools.Test.Avatar.Saved"), HintType.Success);
|
||
}
|
||
|
||
private void CmbHeadSize_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||
{
|
||
if (currentSkinBitmap is not null && skinPath is not null) LoadAndGenerateHead(skinPath);
|
||
}
|
||
|
||
private BitmapImage BitmapToBitmapImage(Bitmap bitmap)
|
||
{
|
||
using (var memoryStream = new MemoryStream())
|
||
{
|
||
bitmap.Save(memoryStream, ImageFormat.Png);
|
||
memoryStream.Position = 0L;
|
||
|
||
var bitmapImage = new BitmapImage();
|
||
bitmapImage.BeginInit();
|
||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||
bitmapImage.StreamSource = memoryStream;
|
||
bitmapImage.EndInit();
|
||
bitmapImage.Freeze();
|
||
|
||
return bitmapImage;
|
||
}
|
||
}
|
||
|
||
private void TextDownloadFolder_OnValidatedTextChanged(object sender, RoutedEventArgs e)
|
||
{
|
||
SaveCacheDownloadFolder(sender, e);
|
||
TextDownloadName_ValidateChanged(sender, e);
|
||
}
|
||
}
|