Compare commits
8 Commits
cffa1c879f
...
6c7119ced4
Author | SHA1 | Date |
---|---|---|
![]() |
6c7119ced4 | |
![]() |
9a354ed2fc | |
![]() |
da1915c6c0 | |
![]() |
eac4f23422 | |
![]() |
167212bbf2 | |
![]() |
63e495ffa7 | |
![]() |
1b2d418d6a | |
![]() |
0a3da756bf |
|
@ -50,4 +50,9 @@
|
|||
</StackLayout>
|
||||
</Shell.FlyoutFooter>
|
||||
|
||||
<!-- 注册开发者页面的路由,但不在TabBar中显示 -->
|
||||
<ShellContent
|
||||
Title="开发者模式"
|
||||
ContentTemplate="{DataTemplate page:DeveloperPage}"
|
||||
Route="DeveloperPage" />
|
||||
</Shell>
|
||||
|
|
|
@ -29,6 +29,8 @@ namespace installer
|
|||
Routing.RegisterRoute("DebugPage", typeof(Page.DebugPage));
|
||||
Routing.RegisterRoute("PlaybackPage", typeof(Page.PlaybackPage));
|
||||
Routing.RegisterRoute("LoginPage", typeof(Page.LoginPage));
|
||||
Routing.RegisterRoute("HelpPage", typeof(Page.HelpPage));
|
||||
Routing.RegisterRoute("DeveloperPage", typeof(Page.DeveloperPage));
|
||||
}
|
||||
|
||||
protected override void OnNavigating(ShellNavigatingEventArgs args)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# THUAI8 开发者指南
|
||||
|
||||
### 访问开发者模式
|
||||
|
||||
1. 打开"帮助"页面
|
||||
2. 在3秒内快速连续点击页面顶部的"THUAI8 帮助"标题5次
|
||||
|
||||
### 配置密钥步骤
|
||||
|
||||
1. 在开发者模式界面输入腾讯云SecretID和SecretKey
|
||||
2. 设置加密密码(仅用于本地加密,不会被保存)
|
||||
3. 点击"生成加密密钥"按钮
|
||||
4. 将生成的secured_key.csv文件添加为嵌入式资源
|
||||
5. 重新编译项目
|
||||
|
||||
### 嵌入式资源配置
|
||||
|
||||
项目已配置自动识别Resources\Raw目录下的secured_key.csv文件作为嵌入式资源:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Raw\secured_key.csv" Condition="Exists('Resources\Raw\secured_key.csv')" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
**添加方法**:
|
||||
1. **VS界面**:创建Resources/Raw文件夹 → 添加文件 → 属性设为"嵌入式资源"
|
||||
2. **手动方式**:复制文件到Resources\Raw目录 → 确认项目文件包含上述XML配置
|
||||
|
||||
|
||||
---
|
||||
|
||||
*注意:请妥善保管加密密码。如果忘记密码,将需要重新生成密钥。*
|
|
@ -24,115 +24,71 @@ namespace installer
|
|||
// public static Model.Logger logger = Model.LoggerProvider.FromFile(@"E:\bin\log\123.log");
|
||||
public static bool ErrorTrigger_WhileDebug = true;
|
||||
public static bool RefreshLogs_WhileDebug = false;
|
||||
public static string SecretID = "***";
|
||||
public static string SecretKey = "***";
|
||||
public static string? SecretID = null;
|
||||
public static string? SecretKey = null;
|
||||
|
||||
public static MauiApp CreateMauiApp()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 首先初始化调试工具
|
||||
// 确保初始化调试工具
|
||||
DebugTool.Initialize();
|
||||
DebugTool.Log("开始创建MAUI应用程序");
|
||||
DebugTool.Log("调试工具已初始化");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 无法记录日志,但尝试继续
|
||||
Debug.WriteLine($"初始化调试工具失败: {ex.Message}");
|
||||
}
|
||||
|
||||
// 捕获UI线程同步上下文
|
||||
UISynchronizationContext = SynchronizationContext.Current;
|
||||
DebugTool.Log("UI同步上下文已捕获");
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
try
|
||||
{
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.ConfigureFonts(fonts =>
|
||||
{
|
||||
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
|
||||
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
|
||||
});
|
||||
|
||||
// 初始化默认空密钥
|
||||
SecretID = "***";
|
||||
SecretKey = "***";
|
||||
|
||||
// read SecretID & SecretKey from filePath for debug
|
||||
var filePath = Debugger.IsAttached ? "D:\\Secret.csv" : Path.Combine(AppContext.BaseDirectory, "Secret.csv");
|
||||
try
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
// 首先尝试从嵌入式资源中读取密钥
|
||||
try
|
||||
{
|
||||
DebugTool.Log($"正在读取Secret文件: {filePath}");
|
||||
var lines = File.ReadAllLines(filePath);
|
||||
if (lines.Length >= 4)
|
||||
{
|
||||
try
|
||||
{
|
||||
lines = lines.Select(s => s.Trim().Trim('\r', '\n')).ToArray();
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
try
|
||||
{
|
||||
aes.Key = Convert.FromBase64String(lines[0]);
|
||||
aes.IV = Convert.FromBase64String(lines[1]);
|
||||
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||
|
||||
try
|
||||
{
|
||||
using (MemoryStream memory = new MemoryStream(Convert.FromBase64String(lines[2])))
|
||||
{
|
||||
using (CryptoStream crypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(crypto, Encoding.ASCII))
|
||||
SecretID = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
using (MemoryStream memory = new MemoryStream(Convert.FromBase64String(lines[3])))
|
||||
{
|
||||
using (CryptoStream crypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(crypto, Encoding.ASCII))
|
||||
SecretKey = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
DebugTool.Log("Secret文件解密完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "解密Secret内容");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "初始化AES解密器");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "处理Secret文件行");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTool.Log($"Secret文件格式不正确,行数: {lines.Length}");
|
||||
}
|
||||
LoadSecretFromEmbeddedResource();
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.Log($"从嵌入式资源加载密钥失败: {ex.Message}");
|
||||
}
|
||||
|
||||
// 如果嵌入式资源中没有密钥,则尝试从外部文件读取
|
||||
if (string.IsNullOrEmpty(SecretID) || SecretID == "***" || string.IsNullOrEmpty(SecretKey) || SecretKey == "***")
|
||||
{
|
||||
DebugTool.Log($"Secret文件不存在: {filePath}");
|
||||
// 创建一个简单的Secret.csv用于测试
|
||||
try
|
||||
{
|
||||
DebugTool.Log("创建默认Secret.csv文件");
|
||||
using (StreamWriter sw = new StreamWriter(filePath))
|
||||
{
|
||||
// 使用固定密钥,仅用于测试
|
||||
sw.WriteLine("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); // Key
|
||||
sw.WriteLine("AAAAAAAAAAAAAAAAAAAAAA=="); // IV
|
||||
sw.WriteLine("AAAAAAAAAAAAAAAAAAAAAA=="); // Encrypted SecretID
|
||||
sw.WriteLine("AAAAAAAAAAAAAAAAAAAAAA=="); // Encrypted SecretKey
|
||||
}
|
||||
LoadSecretFromExternalFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "创建默认Secret.csv");
|
||||
DebugTool.Log($"从外部文件加载密钥失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "读取Secret文件");
|
||||
// 确保任何密钥加载问题都不会导致应用崩溃
|
||||
DebugTool.LogException(ex, "密钥加载过程中发生未处理的异常");
|
||||
}
|
||||
|
||||
DebugTool.Log("开始配置MAUI应用程序");
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
.UseMauiCommunityToolkitCore();
|
||||
|
||||
try
|
||||
|
@ -206,11 +162,166 @@ namespace installer
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "创建MAUI应用");
|
||||
DebugTool.LogException(ex, "MAUI应用程序初始化");
|
||||
throw; // 重新抛出异常以便能够看到崩溃信息
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadSecretFromEmbeddedResource()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
string resourceName = "installer.Resources.Raw.secured_key.csv";
|
||||
|
||||
// 列出所有嵌入资源以进行调试
|
||||
var resources = assembly.GetManifestResourceNames();
|
||||
DebugTool.Log($"可用资源: {string.Join(", ", resources)}");
|
||||
|
||||
using (Stream? stream = assembly.GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(stream))
|
||||
{
|
||||
string content = reader.ReadToEnd();
|
||||
var lines = content.Split('\n').Select(s => s.Trim().Trim('\r')).ToArray();
|
||||
|
||||
if (lines.Length >= 4)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
aes.Key = Convert.FromBase64String(lines[0]);
|
||||
aes.IV = Convert.FromBase64String(lines[1]);
|
||||
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||
|
||||
try
|
||||
{
|
||||
// 解密SecretID
|
||||
using (MemoryStream memory = new MemoryStream(Convert.FromBase64String(lines[2])))
|
||||
using (CryptoStream crypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
|
||||
using (StreamReader cryptoReader = new StreamReader(crypto))
|
||||
{
|
||||
SecretID = cryptoReader.ReadToEnd();
|
||||
}
|
||||
|
||||
// 解密SecretKey
|
||||
using (MemoryStream memory = new MemoryStream(Convert.FromBase64String(lines[3])))
|
||||
using (CryptoStream crypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
|
||||
using (StreamReader cryptoReader = new StreamReader(crypto))
|
||||
{
|
||||
SecretKey = cryptoReader.ReadToEnd();
|
||||
}
|
||||
|
||||
DebugTool.Log("从嵌入式资源成功加载密钥");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "解密嵌入式资源密钥内容");
|
||||
// 不抛出异常,使程序可以继续运行
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "初始化嵌入式资源AES解密器");
|
||||
// 不抛出异常,使程序可以继续运行
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTool.Log($"嵌入式资源密钥文件格式不正确,行数: {lines.Length}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTool.Log("未找到嵌入式资源密钥文件");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "读取嵌入式资源出错");
|
||||
// 不抛出异常,允许回退到其他加载方式
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadSecretFromExternalFile()
|
||||
{
|
||||
// 保留原有的外部文件读取逻辑
|
||||
// read SecretID & SecretKey from filePath for debug
|
||||
var filePath = Debugger.IsAttached ? "D:\\Secret.csv" : Path.Combine(AppContext.BaseDirectory, "Secret.csv");
|
||||
try
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
DebugTool.Log($"正在读取Secret文件: {filePath}");
|
||||
var lines = File.ReadAllLines(filePath);
|
||||
if (lines.Length >= 4)
|
||||
{
|
||||
try
|
||||
{
|
||||
lines = lines.Select(s => s.Trim().Trim('\r', '\n')).ToArray();
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
try
|
||||
{
|
||||
aes.Key = Convert.FromBase64String(lines[0]);
|
||||
aes.IV = Convert.FromBase64String(lines[1]);
|
||||
var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||
|
||||
try
|
||||
{
|
||||
using (MemoryStream memory = new MemoryStream(Convert.FromBase64String(lines[2])))
|
||||
{
|
||||
using (CryptoStream crypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(crypto, Encoding.ASCII))
|
||||
SecretID = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
using (MemoryStream memory = new MemoryStream(Convert.FromBase64String(lines[3])))
|
||||
{
|
||||
using (CryptoStream crypto = new CryptoStream(memory, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(crypto, Encoding.ASCII))
|
||||
SecretKey = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
DebugTool.Log("Secret文件解密完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "解密Secret内容");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "初始化AES解密器");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "处理Secret文件行");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugTool.Log($"Secret文件格式不正确,行数: {lines.Length}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "读取外部Secret文件");
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddViewModelService(MauiAppBuilder builder)
|
||||
{
|
||||
var a = typeof(MauiProgram).Assembly;
|
||||
|
|
|
@ -36,11 +36,18 @@ namespace installer.Model
|
|||
public Logger Log;
|
||||
|
||||
public LoginStatus Status = LoginStatus.offline;
|
||||
public Tencent_Cos EEsast_Cos { get; protected set; } = new Tencent_Cos("1255334966", "ap-beijing", "eesast");//服务器应该还能用吧
|
||||
public Tencent_Cos EEsast_Cos { get; protected set; } = new Tencent_Cos("1255334966", "ap-beijing", "eesast");
|
||||
|
||||
public EEsast(Logger? _log = null)
|
||||
{
|
||||
Log = _log ?? LoggerProvider.FromConsole();
|
||||
Log.PartnerInfo = "[EESAST]";
|
||||
|
||||
// 使用全局密钥
|
||||
if (!string.IsNullOrEmpty(MauiProgram.SecretID) && !string.IsNullOrEmpty(MauiProgram.SecretKey))
|
||||
{
|
||||
EEsast_Cos.UpdateSecret(MauiProgram.SecretID, MauiProgram.SecretKey);
|
||||
}
|
||||
}
|
||||
public async Task LoginToEEsast(HttpClient client, string useremail = "", string userpassword = "")
|
||||
{
|
||||
|
|
|
@ -45,7 +45,25 @@ namespace installer.Model
|
|||
.SetRegion(Region) // 设置一个默认的存储桶地域
|
||||
.SetDebugLog(true) // 显示日志
|
||||
.Build(); // 创建 CosXmlConfig 对象
|
||||
QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider("***", "***", 1000);
|
||||
|
||||
// 使用全局密钥
|
||||
string secretId = MauiProgram.SecretID;
|
||||
string secretKey = MauiProgram.SecretKey;
|
||||
|
||||
// 确保密钥值有效,如果没有则使用默认值
|
||||
if (string.IsNullOrEmpty(secretId) || secretId == "***")
|
||||
{
|
||||
secretId = "***"; // 默认值或占位符
|
||||
Log.LogWarning("使用默认SecretID - 注意:这将导致API访问受限");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(secretKey) || secretKey == "***")
|
||||
{
|
||||
secretKey = "***"; // 默认值或占位符
|
||||
Log.LogWarning("使用默认SecretKey - 注意:这将导致API访问受限");
|
||||
}
|
||||
|
||||
QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider(secretId, secretKey, 1000);
|
||||
cosXml = new CosXmlServer(config, cosCredentialProvider);
|
||||
transfer = new TransferConfig()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="installer.Page.DeveloperPage"
|
||||
Title="开发者模式">
|
||||
<ScrollView>
|
||||
<VerticalStackLayout Padding="20" Spacing="20">
|
||||
<Frame BorderColor="Gray" Padding="15" CornerRadius="5">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="腾讯云密钥配置" FontSize="20" FontAttributes="Bold" />
|
||||
<Label Text="此配置仅供开发人员使用。普通用户无需设置此项,直接使用程序内置的密钥即可。"
|
||||
TextColor="Gray" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="10" RowSpacing="15">
|
||||
<Label Text="腾讯云SecretID:" Grid.Row="0" Grid.Column="0" VerticalOptions="Center" />
|
||||
<Entry x:Name="SecretIDEntry" Grid.Row="0" Grid.Column="1" IsPassword="True" />
|
||||
|
||||
<Label Text="腾讯云SecretKey:" Grid.Row="1" Grid.Column="0" VerticalOptions="Center" />
|
||||
<Entry x:Name="SecretKeyEntry" Grid.Row="1" Grid.Column="1" IsPassword="True" />
|
||||
</Grid>
|
||||
|
||||
<Label Text="加密密码(用于保护密钥,请务必记住此密码):" />
|
||||
<Entry x:Name="EncryptionPasswordEntry" IsPassword="True" />
|
||||
|
||||
<Button x:Name="GenerateKeyButton" Text="生成加密密钥" Clicked="OnGenerateKeyClicked"
|
||||
HorizontalOptions="Start" Margin="0,10,0,0" />
|
||||
|
||||
<Label x:Name="StatusLabel" TextColor="Gray" />
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<Frame BorderColor="Gray" Padding="15" CornerRadius="5" IsVisible="{Binding ResourceKeyGenerated}">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="嵌入式资源密钥" FontSize="18" FontAttributes="Bold" />
|
||||
<Label Text="请将以下文件添加为项目的嵌入式资源。添加后,所有用户都可以使用此密钥,无需额外配置。"
|
||||
TextColor="Gray" />
|
||||
|
||||
<Label Text="加密密钥文件路径:" />
|
||||
<Label x:Name="KeyFilePathLabel" TextColor="Blue" />
|
||||
|
||||
<Button x:Name="OpenFolderButton" Text="打开文件位置" Clicked="OnOpenFolderClicked"
|
||||
HorizontalOptions="Start" />
|
||||
|
||||
<Button x:Name="CopyPathButton" Text="复制路径" Clicked="OnCopyPathClicked"
|
||||
HorizontalOptions="Start" />
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<Frame BorderColor="Gray" Padding="15" CornerRadius="5">
|
||||
<VerticalStackLayout Spacing="10">
|
||||
<Label Text="使用说明" FontSize="18" FontAttributes="Bold" />
|
||||
<Label Text="1. 输入腾讯云SecretID、SecretKey和用于加密的密码" />
|
||||
<Label Text="2. 点击'生成加密密钥'按钮生成密钥文件" />
|
||||
<Label Text="3. 将生成的密钥文件添加为项目的嵌入式资源" />
|
||||
<Label Text="4. 重新编译项目后,所有用户都能无需配置地使用腾讯云服务" />
|
||||
<Label Text="注意:密码仅用于加密本地文件,不会被保存或上传" TextColor="Red" />
|
||||
</VerticalStackLayout>
|
||||
</Frame>
|
||||
|
||||
<Button x:Name="ExitButton"
|
||||
Text="退出开发者模式"
|
||||
Clicked="OnExitButtonClicked"
|
||||
BackgroundColor="#FFD2D2"
|
||||
TextColor="Black"
|
||||
Margin="0,20,0,0"
|
||||
HorizontalOptions="Center" />
|
||||
</VerticalStackLayout>
|
||||
</ScrollView>
|
||||
</ContentPage>
|
|
@ -0,0 +1,226 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.Storage;
|
||||
|
||||
namespace installer.Page
|
||||
{
|
||||
public partial class DeveloperPage : ContentPage
|
||||
{
|
||||
private bool _resourceKeyGenerated = false;
|
||||
private string _encryptedKeyFilePath = string.Empty;
|
||||
|
||||
public bool ResourceKeyGenerated
|
||||
{
|
||||
get => _resourceKeyGenerated;
|
||||
set
|
||||
{
|
||||
_resourceKeyGenerated = value;
|
||||
OnPropertyChanged(nameof(ResourceKeyGenerated));
|
||||
}
|
||||
}
|
||||
|
||||
public DeveloperPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = this;
|
||||
}
|
||||
|
||||
private async void OnGenerateKeyClicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SecretIDEntry.Text) ||
|
||||
string.IsNullOrWhiteSpace(SecretKeyEntry.Text) ||
|
||||
string.IsNullOrWhiteSpace(EncryptionPasswordEntry.Text))
|
||||
{
|
||||
await DisplayAlert("输入错误", "请填写所有必填字段", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示进度指示
|
||||
StatusLabel.Text = "正在生成密钥...";
|
||||
StatusLabel.TextColor = Colors.Blue;
|
||||
GenerateKeyButton.IsEnabled = false;
|
||||
|
||||
// 在后台线程中执行密钥生成
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建安全随机密钥和IV
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
// 从密码生成密钥
|
||||
using (var deriveBytes = new Rfc2898DeriveBytes(
|
||||
EncryptionPasswordEntry.Text,
|
||||
new byte[16], // 静态盐,在解密时需要相同
|
||||
10000,
|
||||
HashAlgorithmName.SHA256))
|
||||
{
|
||||
aes.Key = deriveBytes.GetBytes(32); // 256位密钥
|
||||
aes.IV = deriveBytes.GetBytes(16); // 128位IV
|
||||
}
|
||||
|
||||
// 加密SecretID和SecretKey
|
||||
var encryptedSecretID = EncryptString(SecretIDEntry.Text, aes.Key, aes.IV);
|
||||
var encryptedSecretKey = EncryptString(SecretKeyEntry.Text, aes.Key, aes.IV);
|
||||
|
||||
// 创建密钥文件内容
|
||||
string resourceContent = $"{Convert.ToBase64String(aes.Key)}\n{Convert.ToBase64String(aes.IV)}\n{encryptedSecretID}\n{encryptedSecretKey}";
|
||||
|
||||
try
|
||||
{
|
||||
// 为嵌入式资源创建目录
|
||||
string resourceDirPath = Path.Combine(AppContext.BaseDirectory, "Resources", "Raw");
|
||||
Directory.CreateDirectory(resourceDirPath);
|
||||
|
||||
// 保存密钥文件
|
||||
_encryptedKeyFilePath = Path.Combine(resourceDirPath, "secured_key.csv");
|
||||
File.WriteAllText(_encryptedKeyFilePath, resourceContent);
|
||||
|
||||
// 更新UI必须在主线程中执行
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
KeyFilePathLabel.Text = _encryptedKeyFilePath;
|
||||
ResourceKeyGenerated = true;
|
||||
StatusLabel.Text = "密钥已生成,请将其添加为嵌入式资源";
|
||||
StatusLabel.TextColor = Colors.Green;
|
||||
GenerateKeyButton.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "保存加密密钥文件");
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await DisplayAlert("错误", $"保存密钥文件失败: {ex.Message}", "确定");
|
||||
StatusLabel.Text = "保存密钥文件失败,请检查日志";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
GenerateKeyButton.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugTool.LogException(ex, "生成密钥加密过程");
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await DisplayAlert("错误", $"加密密钥失败: {ex.Message}", "确定");
|
||||
StatusLabel.Text = "加密密钥失败,请重试";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
GenerateKeyButton.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 处理UI线程异常
|
||||
await DisplayAlert("错误", $"生成密钥时出错: {ex.Message}", "确定");
|
||||
StatusLabel.Text = "密钥生成失败,请重试";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
GenerateKeyButton.IsEnabled = true;
|
||||
DebugTool.LogException(ex, "生成密钥");
|
||||
}
|
||||
}
|
||||
|
||||
private string EncryptString(string plainText, byte[] key, byte[] iv)
|
||||
{
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
|
||||
using (MemoryStream memoryStream = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream cryptoStream = new CryptoStream(
|
||||
memoryStream,
|
||||
aes.CreateEncryptor(),
|
||||
CryptoStreamMode.Write))
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(cryptoStream))
|
||||
{
|
||||
writer.Write(plainText);
|
||||
}
|
||||
}
|
||||
return Convert.ToBase64String(memoryStream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenFolderClicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_encryptedKeyFilePath) && File.Exists(_encryptedKeyFilePath))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select,\"{_encryptedKeyFilePath}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusLabel.Text = "密钥文件不存在,请先生成密钥";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusLabel.Text = $"无法打开文件位置: {ex.Message}";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
DebugTool.LogException(ex, "打开文件位置");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnCopyPathClicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_encryptedKeyFilePath))
|
||||
{
|
||||
await Clipboard.SetTextAsync(_encryptedKeyFilePath);
|
||||
StatusLabel.Text = "路径已复制到剪贴板";
|
||||
StatusLabel.TextColor = Colors.Green;
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusLabel.Text = "密钥文件不存在,请先生成密钥";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusLabel.Text = $"无法复制路径: {ex.Message}";
|
||||
StatusLabel.TextColor = Colors.Red;
|
||||
DebugTool.LogException(ex, "复制路径");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnExitButtonClicked(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 返回到主Shell
|
||||
await Shell.Current.GoToAsync("//InstallPage");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 如果导航失败,记录错误并尝试使用其他导航方式
|
||||
DebugTool.LogException(ex, "退出开发者模式");
|
||||
try
|
||||
{
|
||||
// 尝试返回
|
||||
await Shell.Current.GoToAsync("..");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,32 @@
|
|||
x:Class="installer.Page.HelpPage"
|
||||
Title="Help">
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style x:Key="DevModeHeaderStyle" TargetType="Label">
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="HorizontalOptions" Value="Center" />
|
||||
<Setter Property="TextColor" Value="Transparent" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<ScrollView
|
||||
WidthRequest="750"
|
||||
HeightRequest="500"
|
||||
HorizontalOptions="Center">
|
||||
<VerticalStackLayout>
|
||||
<VerticalStackLayout Padding="10" Spacing="10">
|
||||
|
||||
<Label x:Name="HeaderLabel"
|
||||
Text="THUAI8 帮助"
|
||||
FontSize="20"
|
||||
FontAttributes="Bold"
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,0,0,10">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="OnHeaderTapped" />
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
|
||||
<Label
|
||||
Text="Installer"
|
||||
|
|
|
@ -1,12 +1,72 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using installer.ViewModel;
|
||||
|
||||
namespace installer.Page;
|
||||
|
||||
public partial class HelpPage : ContentPage
|
||||
{
|
||||
private int _tapCount = 0;
|
||||
private DateTime _lastTapTime = DateTime.MinValue;
|
||||
|
||||
public HelpPage(HelpViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = viewModel;
|
||||
|
||||
// 初始化点击计数器
|
||||
_tapCount = 0;
|
||||
_lastTapTime = DateTime.MinValue;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
// 页面显示时重置计数器
|
||||
_tapCount = 0;
|
||||
_lastTapTime = DateTime.MinValue;
|
||||
|
||||
Debug.WriteLine("帮助页面已显示,点击计数器已重置");
|
||||
}
|
||||
|
||||
private async void OnHeaderTapped(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
// 检查是否在3秒内点击
|
||||
if ((now - _lastTapTime).TotalSeconds <= 3)
|
||||
{
|
||||
_tapCount++;
|
||||
Debug.WriteLine($"帮助页面标题被点击,当前点击次数: {_tapCount}");
|
||||
|
||||
// 给用户一些视觉反馈,但不明显
|
||||
await HeaderLabel.ScaleTo(1.05, 50);
|
||||
await HeaderLabel.ScaleTo(1.0, 50);
|
||||
|
||||
if (_tapCount >= 5)
|
||||
{
|
||||
_tapCount = 0;
|
||||
Debug.WriteLine("触发开发者模式");
|
||||
|
||||
// 导航到开发者页面
|
||||
await Shell.Current.GoToAsync("//DeveloperPage");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 超过3秒,重置计数器
|
||||
_tapCount = 1;
|
||||
Debug.WriteLine("点击计数器已重置(超时)");
|
||||
}
|
||||
|
||||
_lastTapTime = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"处理标题点击错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<Grid ColumnDefinitions="Auto, *" ColumnSpacing="15">
|
||||
<Label Grid.Column="0"
|
||||
Text="{Binding PlaybackSpeed, StringFormat='速度: {0:F2}x'}"
|
||||
Text="{Binding PlaybackSpeed, StringFormat='速度: {0:F1}x'}"
|
||||
VerticalOptions="Center"
|
||||
WidthRequest="100"
|
||||
TextColor="#2c3e50"/>
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace installer.ViewModel
|
|||
IP = Downloader.Data.Config.Commands.IP;
|
||||
Port = Downloader.Data.Config.Commands.Port;
|
||||
PlaybackFile = Downloader.Data.Config.Commands.PlaybackFile;
|
||||
PlaybackSpeed = Downloader.Data.Config.Commands.PlaybackSpeed.ToString();
|
||||
PlaybackSpeed = Downloader.Data.Config.Commands.PlaybackSpeed.ToString("F1");
|
||||
|
||||
ipChanged = false;
|
||||
portChanged = false;
|
||||
|
@ -127,8 +127,15 @@ namespace installer.ViewModel
|
|||
get => playbackSpeed;
|
||||
set
|
||||
{
|
||||
playbackSpeed = value;
|
||||
if (playbackSpeed == Downloader.Data.Config.Commands.PlaybackSpeed.ToString())
|
||||
if (double.TryParse(value, out double speed))
|
||||
{
|
||||
playbackSpeed = speed.ToString("F1");
|
||||
}
|
||||
else
|
||||
{
|
||||
playbackSpeed = value;
|
||||
}
|
||||
if (playbackSpeed == Downloader.Data.Config.Commands.PlaybackSpeed.ToString("F1"))
|
||||
playbackSpeedChanged = false;
|
||||
else
|
||||
playbackSpeedChanged = true;
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||
<ApplicationDisplayVersion>1.0.0</ApplicationDisplayVersion>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKBootstrapAutoInitialize>true</WindowsAppSDKBootstrapAutoInitialize>
|
||||
<UseRidGraph>true</UseRidGraph>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-windows10.0.19041.0|AnyCPU'">
|
||||
|
@ -131,7 +134,12 @@
|
|||
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">
|
||||
<PropertyGroup>
|
||||
<WindowsAppSDKBootstrapAutoInitialize>true</WindowsAppSDKBootstrapAutoInitialize>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Raw\secured_key.csv" Condition="Exists('Resources\Raw\secured_key.csv')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue