using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;
namespace BarrageGrab.Forms
{
///
/// 完整设置界面 —— 可视化编辑 AppConfig.json + 游戏集成
/// 配置项分8个Tab: 常规/网络/弹幕过滤/轮询/进程域名/直播伴侣/串口/游戏启动项
/// 游戏启动项 Tab 管理启动项 + 5个配置表路径
///
public class SettingsForm : Form
{
private TabControl tabMain;
private Label lblStatus;
private string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appConfig.json");
private string GameSettingsPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "game_settings.json");
// 常规
private ComboBox cmbPlatform;
private CheckBox cbHideConsole, cbShowWindow;
// 网络
private NumericUpDown nudWsPort, nudProxyPort;
private CheckBox cbListenAny, cbProxyEnabled;
private TextBox txtUpstreamProxy;
// 弹幕过滤
private CheckBox cbPrintBarrage, cbBarrageLog;
private CheckedListBox clbPrintFilter, clbPushFilter, clbLogFilter;
// 轮询
private CheckBox cbForcePolling;
private NumericUpDown nudPollingInterval;
// 进程/域名
private TextBox txtProcessFilter, txtHostNameList, txtWebRoomIds;
private CheckBox cbFilterHostName;
// 直播伴侣
private TextBox txtLiveCompanPath;
private CheckBox cbLiveCompanHook, cbAutoPause;
// 串口
private TextBox txtComPort, txtComBaud;
// 游戏启动项
private TextBox txtGameExePath, txtGameWorkDir;
private CheckBox cbAutoLaunchGame;
// 游戏配置表路径
private TextBox txtConfigDir;
private TextBox txtEffectFile, txtMacroFile, txtGiftFile, txtCmdFile, txtFkeyFile;
private readonly Tuple[] MsgTypeOptions = {
Tuple.Create(1, "1-弹幕消息"),
Tuple.Create(2, "2-点赞消息"),
Tuple.Create(3, "3-进入直播间"),
Tuple.Create(4, "4-关注消息"),
Tuple.Create(5, "5-礼物消息"),
Tuple.Create(6, "6-统计消息"),
Tuple.Create(7, "7-粉丝团消息"),
Tuple.Create(8, "8-直播间分享"),
Tuple.Create(9, "9-下播"),
};
// 默认值
private const string DefaultGameExe = @"..\Fantasy.Map.Simulator\FantasyMapSimulator.exe";
private const string DefaultGameDir = @"..\Fantasy.Map.Simulator";
private const string DefaultConfigDir = @"..\Fantasy.Map.Simulator\BepInEx\config";
public SettingsForm()
{
this.Text = "弹幕插件设置";
this.ClientSize = new Size(620, 620);
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
BuildUI();
LoadAll();
}
void BuildUI()
{
tabMain = new TabControl { Dock = DockStyle.Top, Height = 540, Name = "tabMain" };
this.Controls.Add(tabMain);
var pnlBottom = new Panel { Dock = DockStyle.Bottom, Height = 50 };
this.Controls.Add(pnlBottom);
var btnSave = new Button { Text = "保存并应用", Width = 110, Height = 30, Left = 140, Top = 10, BackColor = Color.LightGreen };
btnSave.Click += (s, e) => SaveAll();
pnlBottom.Controls.Add(btnSave);
var btnCancel = new Button { Text = "取消", Width = 80, Height = 30, Left = 260, Top = 10 };
btnCancel.Click += (s, e) => this.Close();
pnlBottom.Controls.Add(btnCancel);
var btnReload = new Button { Text = "重新加载", Width = 90, Height = 30, Left = 350, Top = 10 };
btnReload.Click += (s, e) => LoadAll();
pnlBottom.Controls.Add(btnReload);
lblStatus = new Label { AutoSize = true, Left = 450, Top = 18, ForeColor = Color.DimGray, Text = "" };
pnlBottom.Controls.Add(lblStatus);
BuildGeneralTab();
BuildNetworkTab();
BuildBarrageFilterTab();
BuildPollingTab();
BuildProcessDomainTab();
BuildLiveCompanionTab();
BuildComPortTab();
BuildGameTab();
}
void BuildGeneralTab()
{
var page = new TabPage("常规");
page.Controls.Add(new Label { Text = "直播平台:", Left = 20, Top = 20, AutoSize = true });
cmbPlatform = new ComboBox { Left = 120, Top = 17, Width = 150, DropDownStyle = ComboBoxStyle.DropDownList };
cmbPlatform.Items.AddRange(new object[] { "douyin", "kuaishou" });
page.Controls.Add(cmbPlatform);
cbHideConsole = new CheckBox { Text = "隐藏控制台", Left = 20, Top = 60, AutoSize = true };
page.Controls.Add(cbHideConsole);
cbShowWindow = new CheckBox { Text = "显示主窗体", Left = 20, Top = 90, AutoSize = true };
page.Controls.Add(cbShowWindow);
page.Controls.Add(new Label { Text = "提示: 平台切换需重启程序生效", Left = 20, Top = 150, AutoSize = true, ForeColor = Color.Gray });
tabMain.TabPages.Add(page);
}
void BuildNetworkTab()
{
var page = new TabPage("网络");
page.Controls.Add(new Label { Text = "WebSocket 端口:", Left = 20, Top = 20, AutoSize = true });
nudWsPort = new NumericUpDown { Left = 150, Top = 17, Width = 80, Minimum = 1, Maximum = 65535 };
page.Controls.Add(nudWsPort);
cbListenAny = new CheckBox { Text = "监听任意IP(0.0.0.0)", Left = 20, Top = 55, AutoSize = true };
page.Controls.Add(cbListenAny);
page.Controls.Add(new Label { Text = "系统代理端口:", Left = 20, Top = 90, AutoSize = true });
nudProxyPort = new NumericUpDown { Left = 150, Top = 87, Width = 80, Minimum = 1, Maximum = 65535 };
page.Controls.Add(nudProxyPort);
cbProxyEnabled = new CheckBox { Text = "启用系统代理", Left = 20, Top = 125, AutoSize = true };
page.Controls.Add(cbProxyEnabled);
page.Controls.Add(new Label { Text = "上游代理地址:", Left = 20, Top = 160, AutoSize = true });
txtUpstreamProxy = new TextBox { Left = 150, Top = 157, Width = 250 };
page.Controls.Add(txtUpstreamProxy);
page.Controls.Add(new Label { Text = "(例如: 127.0.0.1:11223, 不要带http://)", Left = 150, Top = 183, AutoSize = true, ForeColor = Color.Gray });
tabMain.TabPages.Add(page);
}
void BuildBarrageFilterTab()
{
var page = new TabPage("弹幕过滤");
cbPrintBarrage = new CheckBox { Text = "控制台打印弹幕", Left = 10, Top = 10, AutoSize = true };
page.Controls.Add(cbPrintBarrage);
cbBarrageLog = new CheckBox { Text = "弹幕写入日志文件", Left = 200, Top = 10, AutoSize = true };
page.Controls.Add(cbBarrageLog);
page.Controls.Add(new Label { Text = "打印过滤器:", Left = 10, Top = 40, AutoSize = true });
clbPrintFilter = CreateFilterListBox();
clbPrintFilter.Location = new Point(10, 60);
page.Controls.Add(clbPrintFilter);
page.Controls.Add(new Label { Text = "推送过滤器:", Left = 220, Top = 40, AutoSize = true });
clbPushFilter = CreateFilterListBox();
clbPushFilter.Location = new Point(220, 60);
page.Controls.Add(clbPushFilter);
page.Controls.Add(new Label { Text = "日志过滤器:", Left = 430, Top = 40, AutoSize = true });
clbLogFilter = CreateFilterListBox();
clbLogFilter.Location = new Point(430, 60);
page.Controls.Add(clbLogFilter);
tabMain.TabPages.Add(page);
}
CheckedListBox CreateFilterListBox()
{
var clb = new CheckedListBox { Width = 200, Height = 200 };
foreach (var opt in MsgTypeOptions) clb.Items.Add(opt.Item2, false);
return clb;
}
void BuildPollingTab()
{
var page = new TabPage("轮询");
cbForcePolling = new CheckBox { Text = "强制启用轮询模式获取弹幕", Left = 20, Top = 20, AutoSize = true };
page.Controls.Add(cbForcePolling);
page.Controls.Add(new Label { Text = "轮询间隔(ms):", Left = 20, Top = 60, AutoSize = true });
nudPollingInterval = new NumericUpDown { Left = 150, Top = 57, Width = 80, Minimum = 500, Maximum = 60000, Increment = 500 };
page.Controls.Add(nudPollingInterval);
page.Controls.Add(new Label { Text = "轮询模式更稳定但响应较慢", Left = 20, Top = 100, AutoSize = true, ForeColor = Color.Gray });
page.Controls.Add(new Label { Text = "不建议小于1000ms", Left = 20, Top = 120, AutoSize = true, ForeColor = Color.Gray });
tabMain.TabPages.Add(page);
}
void BuildProcessDomainTab()
{
var page = new TabPage("进程/域名");
page.Controls.Add(new Label { Text = "进程过滤器(逗号分隔):", Left = 20, Top = 20, AutoSize = true });
txtProcessFilter = new TextBox { Left = 20, Top = 40, Width = 550, Multiline = true, Height = 40 };
page.Controls.Add(txtProcessFilter);
page.Controls.Add(new Label { Text = "只监听这些进程的弹幕数据", Left = 20, Top = 85, AutoSize = true, ForeColor = Color.Gray });
cbFilterHostName = new CheckBox { Text = "启用域名过滤(降低CPU占用)", Left = 20, Top = 115, AutoSize = true };
page.Controls.Add(cbFilterHostName);
page.Controls.Add(new Label { Text = "域名白名单(逗号分隔):", Left = 20, Top = 145, AutoSize = true });
txtHostNameList = new TextBox { Left = 20, Top = 165, Width = 550 };
page.Controls.Add(txtHostNameList);
page.Controls.Add(new Label { Text = "Web房间ID过滤(逗号分隔):", Left = 20, Top = 195, AutoSize = true });
txtWebRoomIds = new TextBox { Left = 20, Top = 215, Width = 550 };
page.Controls.Add(txtWebRoomIds);
page.Controls.Add(new Label { Text = "只监听指定房间,直播伴侣不支持", Left = 20, Top = 240, AutoSize = true, ForeColor = Color.Gray });
tabMain.TabPages.Add(page);
}
void BuildLiveCompanionTab()
{
var page = new TabPage("直播伴侣");
page.Controls.Add(new Label { Text = "直播伴侣exe路径:", Left = 20, Top = 20, AutoSize = true });
txtLiveCompanPath = new TextBox { Left = 20, Top = 40, Width = 450 };
page.Controls.Add(txtLiveCompanPath);
var btnBrowse = new Button { Text = "浏览...", Left = 480, Top = 39, Width = 70 };
btnBrowse.Click += (s, e) =>
{
using (var dlg = new OpenFileDialog { Filter = "EXE文件|*.exe|所有文件|*.*" })
if (dlg.ShowDialog() == DialogResult.OK) txtLiveCompanPath.Text = dlg.FileName;
};
page.Controls.Add(btnBrowse);
page.Controls.Add(new Label { Text = "(留空则程序自动查找)", Left = 20, Top = 65, AutoSize = true, ForeColor = Color.Gray });
cbLiveCompanHook = new CheckBox { Text = "Hook直播伴侣代理", Left = 20, Top = 95, AutoSize = true };
page.Controls.Add(cbLiveCompanHook);
cbAutoPause = new CheckBox { Text = "进入直播间自动暂停", Left = 20, Top = 125, AutoSize = true };
page.Controls.Add(cbAutoPause);
tabMain.TabPages.Add(page);
}
void BuildComPortTab()
{
var page = new TabPage("串口");
page.Controls.Add(new Label { Text = "COM端口:", Left = 20, Top = 20, AutoSize = true });
txtComPort = new TextBox { Left = 120, Top = 17, Width = 100 };
page.Controls.Add(txtComPort);
page.Controls.Add(new Label { Text = "例如: COM1", Left = 230, Top = 20, AutoSize = true, ForeColor = Color.Gray });
page.Controls.Add(new Label { Text = "波特率:", Left = 20, Top = 55, AutoSize = true });
txtComBaud = new TextBox { Left = 120, Top = 52, Width = 100 };
page.Controls.Add(txtComBaud);
page.Controls.Add(new Label { Text = "例如: 9600", Left = 230, Top = 55, AutoSize = true, ForeColor = Color.Gray });
page.Controls.Add(new Label { Text = "留空则不启用串口抄送服务", Left = 20, Top = 95, AutoSize = true, ForeColor = Color.Gray });
tabMain.TabPages.Add(page);
}
void BuildGameTab()
{
var page = new TabPage("游戏启动项");
int y = 15;
// === 启动项 ===
page.Controls.Add(new Label { Text = "── 启动项 ──", Left = 20, Top = y, AutoSize = true, ForeColor = Color.SteelBlue, Font = new Font(Font, FontStyle.Bold) });
y += 25;
page.Controls.Add(new Label { Text = "游戏exe路径:", Left = 20, Top = y, AutoSize = true });
txtGameExePath = new TextBox { Left = 120, Top = y - 3, Width = 380 };
page.Controls.Add(txtGameExePath);
var btnBrowse1 = new Button { Text = "浏览...", Left = 510, Top = y - 4, Width = 70 };
btnBrowse1.Click += (s, e) =>
{
using (var dlg = new OpenFileDialog { Filter = "EXE文件|*.exe|所有文件|*.*" })
if (dlg.ShowDialog() == DialogResult.OK)
{
txtGameExePath.Text = ToRelativePath(dlg.FileName);
var dir = Path.GetDirectoryName(dlg.FileName);
if (!string.IsNullOrEmpty(dir)) txtGameWorkDir.Text = ToRelativePath(dir);
}
};
page.Controls.Add(btnBrowse1);
y += 27;
page.Controls.Add(new Label { Text = "工作目录:", Left = 20, Top = y, AutoSize = true });
txtGameWorkDir = new TextBox { Left = 120, Top = y - 3, Width = 380 };
page.Controls.Add(txtGameWorkDir);
y += 27;
cbAutoLaunchGame = new CheckBox { Text = "启动时自动打开游戏", Left = 20, Top = y, AutoSize = true };
page.Controls.Add(cbAutoLaunchGame);
y += 30;
// === 配置表路径 ===
page.Controls.Add(new Label { Text = "── 配置表路径 ──", Left = 20, Top = y, AutoSize = true, ForeColor = Color.SteelBlue, Font = new Font(Font, FontStyle.Bold) });
y += 25;
page.Controls.Add(new Label { Text = "配置表目录:", Left = 20, Top = y, AutoSize = true });
txtConfigDir = new TextBox { Left = 120, Top = y - 3, Width = 380 };
page.Controls.Add(txtConfigDir);
var btnBrowse2 = new Button { Text = "浏览...", Left = 510, Top = y - 4, Width = 70 };
btnBrowse2.Click += (s, e) =>
{
using (var dlg = new FolderBrowserDialog())
if (dlg.ShowDialog() == DialogResult.OK) txtConfigDir.Text = ToRelativePath(dlg.SelectedPath);
};
page.Controls.Add(btnBrowse2);
y += 30;
// 5个配置表文件名
txtEffectFile = AddConfigFileRow(page, ref y, "基础效果(effect):", "effect_config.txt");
txtMacroFile = AddConfigFileRow(page, ref y, "组合宏(macro):", "macro_config.txt");
txtGiftFile = AddConfigFileRow(page, ref y, "礼物事件(gift):", "gift_config.txt");
txtCmdFile = AddConfigFileRow(page, ref y, "弹幕指令(command):", "command_config.txt");
txtFkeyFile = AddConfigFileRow(page, ref y, "F键映射(fkey):", "fkey_config.txt");
y += 5;
// === 功能按钮 ===
var btnOpenGameConfig = new Button { Text = "打开配置编辑器", Left = 20, Top = y, Width = 130, Height = 30 };
btnOpenGameConfig.Click += (s, e) => { using (var f = new ConfigEditorForm()) f.ShowDialog(this); };
page.Controls.Add(btnOpenGameConfig);
var btnOpenConfigDir = new Button { Text = "打开配置目录", Left = 160, Top = y, Width = 110 };
btnOpenConfigDir.Click += (s, e) =>
{
var dir = Program.ResolveFullPath(txtConfigDir.Text.Trim());
if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir))
System.Diagnostics.Process.Start("explorer.exe", dir);
else
MessageBox.Show($"配置目录不存在:\n{dir}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
};
page.Controls.Add(btnOpenConfigDir);
var btnResetDefaults = new Button { Text = "恢复默认", Left = 280, Top = y, Width = 80 };
btnResetDefaults.Click += (s, e) => ResetGameDefaults();
page.Controls.Add(btnResetDefaults);
tabMain.TabPages.Add(page);
}
TextBox AddConfigFileRow(TabPage page, ref int y, string label, string defaultName)
{
page.Controls.Add(new Label { Text = label, Left = 30, Top = y, AutoSize = true });
var txt = new TextBox { Left = 180, Top = y - 3, Width = 200, Text = defaultName };
page.Controls.Add(txt);
y += 24;
return txt;
}
/// 将绝对路径转为相对插件目录的相对路径;若转换失败或已是相对路径则原样返回。
static string ToRelativePath(string absPath)
{
if (string.IsNullOrWhiteSpace(absPath)) return absPath;
if (!Path.IsPathRooted(absPath)) return absPath; // 已是相对路径
try
{
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
var fullBase = Path.GetFullPath(baseDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
var fullAbs = Path.GetFullPath(absPath);
var baseUri = new Uri(fullBase + Path.DirectorySeparatorChar);
var absUri = new Uri(fullAbs);
var rel = baseUri.MakeRelativeUri(absUri).ToString();
// Uri 返回的是用 / 分隔的相对路径,统一为 Windows 分隔符
rel = Uri.UnescapeDataString(rel).Replace('/', Path.DirectorySeparatorChar);
return rel;
}
catch { return absPath; }
}
void ResetGameDefaults()
{
txtGameExePath.Text = DefaultGameExe;
txtGameWorkDir.Text = DefaultGameDir;
txtConfigDir.Text = DefaultConfigDir;
txtEffectFile.Text = "effect_config.txt";
txtMacroFile.Text = "macro_config.txt";
txtGiftFile.Text = "gift_config.txt";
txtCmdFile.Text = "command_config.txt";
txtFkeyFile.Text = "fkey_config.txt";
cbAutoLaunchGame.Checked = true;
}
// ═══════════════ 加载 ═══════════════
void LoadAll()
{
try
{
LoadAppConfig();
LoadGameSettings();
lblStatus.Text = "已加载";
lblStatus.ForeColor = Color.DimGray;
}
catch (Exception ex)
{
lblStatus.Text = $"加载失败: {ex.Message}";
lblStatus.ForeColor = Color.Red;
}
}
void LoadAppConfig()
{
if (!File.Exists(ConfigPath)) return;
var config = JObject.Parse(File.ReadAllText(ConfigPath), new JsonLoadSettings { CommentHandling = CommentHandling.Ignore });
var app = config["app"];
var general = app["general"];
cmbPlatform.SelectedItem = general["platform"]?.Value() ?? "douyin";
cbHideConsole.Checked = general["hideConsole"]?.Value() ?? false;
cbShowWindow.Checked = general["showWindow"]?.Value() ?? false;
var proxy = app["network"]["proxy"];
nudProxyPort.Value = proxy["port"]?.Value() ?? 8827;
cbProxyEnabled.Checked = proxy["enabled"]?.Value() ?? true;
txtUpstreamProxy.Text = proxy["upstreamAddress"]?.Value() ?? "";
var ws = app["network"]["websocket"];
nudWsPort.Value = ws["listenPort"]?.Value() ?? 8888;
cbListenAny.Checked = ws["listenAny"]?.Value() ?? true;
var barrage = app["barrage"];
cbPrintBarrage.Checked = barrage["printEnabled"]?.Value() ?? true;
cbBarrageLog.Checked = barrage["barrageFileLog"]?.Value() ?? false;
SetFilterChecks(clbPrintFilter, barrage["printFilter"]?.Value() ?? "");
SetFilterChecks(clbPushFilter, barrage["pushFilter"]?.Value() ?? "");
SetFilterChecks(clbLogFilter, barrage["logFilter"]?.Value() ?? "1,2,4,5,6,7,8");
var polling = barrage["polling"];
cbForcePolling.Checked = polling["enabled"]?.Value() ?? false;
nudPollingInterval.Value = polling["interval"]?.Value() ?? 3000;
var filtering = app["filtering"];
txtProcessFilter.Text = filtering["processFilter"]?.Value() ?? "";
cbFilterHostName.Checked = filtering["hostNameEnabled"]?.Value() ?? true;
txtHostNameList.Text = filtering["hostNameList"]?.Value() ?? "";
txtWebRoomIds.Text = filtering["webRoomIds"]?.Value() ?? "";
var liveCompanion = app["liveCompanion"];
txtLiveCompanPath.Text = liveCompanion["path"]?.Value() ?? "";
cbLiveCompanHook.Checked = liveCompanion["hookEnabled"]?.Value() ?? false;
cbAutoPause.Checked = liveCompanion["autoPause"]?.Value() ?? true;
var comPortStr = app["comPort"]["config"]?.Value() ?? "";
if (!string.IsNullOrWhiteSpace(comPortStr))
{
var parts = comPortStr.Split(':');
if (parts.Length >= 2) { txtComPort.Text = parts[0].Trim(); txtComBaud.Text = parts[1].Trim(); }
}
}
void LoadGameSettings()
{
// 默认值
txtGameExePath.Text = DefaultGameExe;
txtGameWorkDir.Text = DefaultGameDir;
cbAutoLaunchGame.Checked = true;
txtConfigDir.Text = DefaultConfigDir;
txtEffectFile.Text = "effect_config.txt";
txtMacroFile.Text = "macro_config.txt";
txtGiftFile.Text = "gift_config.txt";
txtCmdFile.Text = "command_config.txt";
txtFkeyFile.Text = "fkey_config.txt";
if (File.Exists(GameSettingsPath))
{
try
{
var json = JObject.Parse(File.ReadAllText(GameSettingsPath));
txtGameExePath.Text = json["gameExePath"]?.Value() ?? txtGameExePath.Text;
txtGameWorkDir.Text = json["gameWorkDir"]?.Value() ?? txtGameWorkDir.Text;
cbAutoLaunchGame.Checked = json["autoLaunchGame"]?.Value() ?? true;
txtConfigDir.Text = json["configDir"]?.Value() ?? txtConfigDir.Text;
var cfg = json["configFiles"];
if (cfg != null)
{
txtEffectFile.Text = cfg["effect"]?.Value() ?? txtEffectFile.Text;
txtMacroFile.Text = cfg["macro"]?.Value() ?? txtMacroFile.Text;
txtGiftFile.Text = cfg["gift"]?.Value() ?? txtGiftFile.Text;
txtCmdFile.Text = cfg["command"]?.Value() ?? txtCmdFile.Text;
txtFkeyFile.Text = cfg["fkey"]?.Value() ?? txtFkeyFile.Text;
}
}
catch { }
}
Program.AutoLaunchGame = cbAutoLaunchGame.Checked;
}
void SetFilterChecks(CheckedListBox clb, string filterStr)
{
for (int i = 0; i < clb.Items.Count; i++) clb.SetItemChecked(i, false);
if (string.IsNullOrWhiteSpace(filterStr)) return;
var ids = filterStr.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToList();
for (int i = 0; i < clb.Items.Count; i++)
{
if (ids.Contains(MsgTypeOptions[i].Item1.ToString()))
clb.SetItemChecked(i, true);
}
}
string GetFilterString(CheckedListBox clb)
{
var ids = new List();
for (int i = 0; i < clb.Items.Count; i++)
if (clb.GetItemChecked(i)) ids.Add(MsgTypeOptions[i].Item1.ToString());
return string.Join(",", ids);
}
// ═══════════════ 保存 ═══════════════
void SaveAll()
{
try
{
SaveAppConfig();
SaveGameSettings();
AppSetting.Current.LoadFromJson(ConfigPath);
lblStatus.Text = "已保存(部分设置需重启生效)";
lblStatus.ForeColor = Color.Green;
}
catch (Exception ex)
{
lblStatus.Text = $"保存失败: {ex.Message}";
lblStatus.ForeColor = Color.Red;
}
}
void SaveAppConfig()
{
if (!File.Exists(ConfigPath))
{
var json = new JObject(new JProperty("app", new JObject(
new JProperty("general", new JObject(
new JProperty("hideConsole", cbHideConsole.Checked),
new JProperty("showWindow", cbShowWindow.Checked),
new JProperty("platform", cmbPlatform.SelectedItem?.ToString() ?? "douyin"))),
new JProperty("network", new JObject(
new JProperty("proxy", new JObject(
new JProperty("port", (int)nudProxyPort.Value),
new JProperty("enabled", cbProxyEnabled.Checked),
new JProperty("upstreamAddress", txtUpstreamProxy.Text.Trim()))),
new JProperty("websocket", new JObject(
new JProperty("listenPort", (int)nudWsPort.Value),
new JProperty("listenAny", cbListenAny.Checked))))),
new JProperty("filtering", new JObject(
new JProperty("processFilter", txtProcessFilter.Text.Trim()),
new JProperty("hostNameEnabled", cbFilterHostName.Checked),
new JProperty("hostNameList", txtHostNameList.Text.Trim()),
new JProperty("webRoomIds", txtWebRoomIds.Text.Trim()))),
new JProperty("barrage", new JObject(
new JProperty("printEnabled", cbPrintBarrage.Checked),
new JProperty("printFilter", GetFilterString(clbPrintFilter)),
new JProperty("pushFilter", GetFilterString(clbPushFilter)),
new JProperty("barrageFileLog", cbBarrageLog.Checked),
new JProperty("logFilter", GetFilterString(clbLogFilter)),
new JProperty("polling", new JObject(
new JProperty("enabled", cbForcePolling.Checked),
new JProperty("interval", (int)nudPollingInterval.Value))))),
new JProperty("liveCompanion", new JObject(
new JProperty("path", txtLiveCompanPath.Text.Trim()),
new JProperty("hookEnabled", cbLiveCompanHook.Checked),
new JProperty("autoPause", cbAutoPause.Checked))),
new JProperty("comPort", new JObject(
new JProperty("config", BuildComPortConfig()))))));
File.WriteAllText(ConfigPath, json.ToString(), Encoding.UTF8);
return;
}
var config = JObject.Parse(File.ReadAllText(ConfigPath), new JsonLoadSettings { CommentHandling = CommentHandling.Ignore });
var app = config["app"];
app["general"]["platform"] = cmbPlatform.SelectedItem?.ToString() ?? "douyin";
app["general"]["hideConsole"] = cbHideConsole.Checked;
app["general"]["showWindow"] = cbShowWindow.Checked;
app["network"]["proxy"]["port"] = (int)nudProxyPort.Value;
app["network"]["proxy"]["enabled"] = cbProxyEnabled.Checked;
app["network"]["proxy"]["upstreamAddress"] = txtUpstreamProxy.Text.Trim();
app["network"]["websocket"]["listenPort"] = (int)nudWsPort.Value;
app["network"]["websocket"]["listenAny"] = cbListenAny.Checked;
app["filtering"]["processFilter"] = txtProcessFilter.Text.Trim();
app["filtering"]["hostNameEnabled"] = cbFilterHostName.Checked;
app["filtering"]["hostNameList"] = txtHostNameList.Text.Trim();
app["filtering"]["webRoomIds"] = txtWebRoomIds.Text.Trim();
app["barrage"]["printEnabled"] = cbPrintBarrage.Checked;
app["barrage"]["printFilter"] = GetFilterString(clbPrintFilter);
app["barrage"]["pushFilter"] = GetFilterString(clbPushFilter);
app["barrage"]["barrageFileLog"] = cbBarrageLog.Checked;
app["barrage"]["logFilter"] = GetFilterString(clbLogFilter);
app["barrage"]["polling"]["enabled"] = cbForcePolling.Checked;
app["barrage"]["polling"]["interval"] = (int)nudPollingInterval.Value;
app["liveCompanion"]["path"] = txtLiveCompanPath.Text.Trim();
app["liveCompanion"]["hookEnabled"] = cbLiveCompanHook.Checked;
app["liveCompanion"]["autoPause"] = cbAutoPause.Checked;
app["comPort"]["config"] = BuildComPortConfig();
File.WriteAllText(ConfigPath, config.ToString(), Encoding.UTF8);
}
string BuildComPortConfig()
{
var port = txtComPort.Text.Trim();
var baud = txtComBaud.Text.Trim();
if (string.IsNullOrEmpty(port)) return "";
if (string.IsNullOrEmpty(baud)) baud = "9600";
return $"{port}:{baud}";
}
void SaveGameSettings()
{
var json = new JObject(
new JProperty("gameExePath", txtGameExePath.Text.Trim()),
new JProperty("gameWorkDir", txtGameWorkDir.Text.Trim()),
new JProperty("autoLaunchGame", cbAutoLaunchGame.Checked),
new JProperty("configDir", txtConfigDir.Text.Trim()),
new JProperty("configFiles", new JObject(
new JProperty("effect", txtEffectFile.Text.Trim()),
new JProperty("macro", txtMacroFile.Text.Trim()),
new JProperty("gift", txtGiftFile.Text.Trim()),
new JProperty("command", txtCmdFile.Text.Trim()),
new JProperty("fkey", txtFkeyFile.Text.Trim()))));
File.WriteAllText(GameSettingsPath, json.ToString(), Encoding.UTF8);
// 同步到 Program
Program.AutoLaunchGame = cbAutoLaunchGame.Checked;
Program.SetGamePaths(txtGameExePath.Text.Trim(), txtGameWorkDir.Text.Trim());
Program.SetConfigPaths(txtConfigDir.Text.Trim(),
txtEffectFile.Text.Trim(), txtMacroFile.Text.Trim(),
txtGiftFile.Text.Trim(), txtCmdFile.Text.Trim(),
txtFkeyFile.Text.Trim());
}
}
}