1593 lines
74 KiB
C#
1593 lines
74 KiB
C#
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
|
|
namespace gregExtractor;
|
|
|
|
public sealed class MainFormV2 : Form
|
|
{
|
|
private enum UiLanguage
|
|
{
|
|
German,
|
|
English,
|
|
}
|
|
|
|
private readonly TextBox _txtRepoRoot = new();
|
|
private readonly TextBox _txtSourceRoot = new();
|
|
private readonly TextBox _txtGameRoot = new();
|
|
private readonly TextBox _txtMelonGeneratedRoot = new();
|
|
private readonly TextBox _txtTemplateOutput = new();
|
|
private readonly TextBox _txtTemplatePluginName = new();
|
|
private readonly TextBox _txtSummary = new();
|
|
private readonly TextBox _txtLog = new();
|
|
private readonly TextBox _txtHelp = new();
|
|
private readonly TextBox _txtModProjectPath = new();
|
|
private readonly TextBox _txtModAnalysisSummary = new();
|
|
private readonly TextBox _txtCoverageDetails = new();
|
|
private readonly TextBox _txtUsedHooks = new();
|
|
|
|
private readonly DataGridView _gridHooks = new();
|
|
private readonly DataGridView _gridAssemblyCoverage = new();
|
|
private readonly DataGridView _gridMigration = new();
|
|
private readonly DataGridView _gridFileInsights = new();
|
|
|
|
private readonly ComboBox _cmbCategory = new();
|
|
private readonly ComboBox _cmbLanguage = new();
|
|
|
|
private readonly Label _lblCoverage = new();
|
|
private readonly Label _lblGregCoreCoverage = new();
|
|
private readonly Label _lblAssemblyCoverage = new();
|
|
private readonly Label _lblStatus = new();
|
|
|
|
private readonly Label _lblTitle = new();
|
|
private readonly Label _lblLanguage = new();
|
|
|
|
private readonly Button _btnScan = new();
|
|
private readonly Button _btnSync = new();
|
|
private readonly Button _btnRegenerate = new();
|
|
private readonly Button _btnBuild = new();
|
|
private readonly Button _btnImportGame = new();
|
|
private readonly Button _btnImportMelon = new();
|
|
private readonly Button _btnGenerateTemplate = new();
|
|
private readonly Button _btnOpenTemplateOutput = new();
|
|
private readonly Button _btnBrowseModProject = new();
|
|
private readonly Button _btnAnalyzeModProject = new();
|
|
private readonly Button _btnOpenModProject = new();
|
|
|
|
private readonly CheckBox _chkAutoRegenerate = new();
|
|
private readonly CheckBox _chkBuildAfterRegenerate = new();
|
|
private readonly CheckBox _chkWatch = new();
|
|
|
|
private readonly SnapshotStore _store;
|
|
private readonly HookAutomationService _automation;
|
|
private readonly ModProjectAnalyzer _modAnalyzer = new();
|
|
|
|
private readonly SemaphoreSlim _operationLock = new(1, 1);
|
|
private readonly System.Windows.Forms.Timer _debounceTimer = new();
|
|
private readonly List<FileSystemWatcher> _watchers = new();
|
|
|
|
private List<HookCatalogRow> _allHookRows = new();
|
|
private volatile bool _pendingFileChange;
|
|
private UiLanguage _language = UiLanguage.German;
|
|
|
|
private string _lastGeneratedTemplateDirectory = string.Empty;
|
|
|
|
public MainFormV2()
|
|
{
|
|
string repoRoot = ResolveRepoRoot();
|
|
string sourceRoot = HookAutomationService.SuggestDefaultIl2CppAssembliesPath();
|
|
if (string.IsNullOrWhiteSpace(sourceRoot))
|
|
sourceRoot = Path.Combine(repoRoot, "gregReferences", "Assembly-CSharp");
|
|
string gameRoot = HookAutomationService.SuggestDefaultGameDirectory();
|
|
string melonGeneratedRoot = HookAutomationService.SuggestDefaultMelonGeneratedPath();
|
|
string templateOutput = Path.Combine(repoRoot, "gregExtractor", "generated-template");
|
|
string stateRoot = Path.Combine(repoRoot, "gregExtractor", "state");
|
|
|
|
_store = new SnapshotStore(stateRoot);
|
|
_automation = new HookAutomationService(new SourceScanner(), _store);
|
|
|
|
Text = "gregExtractor";
|
|
Width = 1400;
|
|
Height = 950;
|
|
MinimumSize = new System.Drawing.Size(1280, 860);
|
|
StartPosition = FormStartPosition.CenterScreen;
|
|
|
|
BuildUi(repoRoot, sourceRoot, gameRoot, melonGeneratedRoot, templateOutput);
|
|
ConfigureWatcherDebounce();
|
|
|
|
AppendLog(_language == UiLanguage.German ? "gregExtractor gestartet." : "gregExtractor started.");
|
|
AppendLog($"RepoRoot: {repoRoot}");
|
|
AppendLog($"SourceRoot: {sourceRoot}");
|
|
if (!string.IsNullOrWhiteSpace(gameRoot))
|
|
AppendLog($"GameRoot: {gameRoot}");
|
|
if (!string.IsNullOrWhiteSpace(melonGeneratedRoot))
|
|
AppendLog($"MelonGeneratedRoot: {melonGeneratedRoot}");
|
|
|
|
RefreshHookRows(_store.TryLoadSnapshot());
|
|
ApplyLanguage();
|
|
DarkTheme.Apply(this);
|
|
}
|
|
|
|
private void BuildUi(string repoRoot, string sourceRoot, string gameRoot, string melonGeneratedRoot, string templateOutput)
|
|
{
|
|
var headerPanel = new Panel
|
|
{
|
|
Dock = DockStyle.Top,
|
|
Height = 44,
|
|
Padding = new Padding(10, 8, 10, 6),
|
|
};
|
|
Controls.Add(headerPanel);
|
|
|
|
_lblTitle.AutoSize = true;
|
|
_lblTitle.Left = 10;
|
|
_lblTitle.Top = 12;
|
|
headerPanel.Controls.Add(_lblTitle);
|
|
|
|
_lblLanguage.AutoSize = true;
|
|
_lblLanguage.Top = 12;
|
|
_lblLanguage.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
|
headerPanel.Controls.Add(_lblLanguage);
|
|
|
|
_cmbLanguage.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
_cmbLanguage.Items.AddRange(new[] { "Deutsch", "English" });
|
|
_cmbLanguage.SelectedIndex = 0;
|
|
_cmbLanguage.Width = 120;
|
|
_cmbLanguage.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
|
_cmbLanguage.SelectedIndexChanged += (_, _) =>
|
|
{
|
|
_language = _cmbLanguage.SelectedIndex == 1 ? UiLanguage.English : UiLanguage.German;
|
|
ApplyLanguage();
|
|
};
|
|
headerPanel.Controls.Add(_cmbLanguage);
|
|
|
|
headerPanel.Resize += (_, _) =>
|
|
{
|
|
_cmbLanguage.Left = headerPanel.Width - _cmbLanguage.Width - 12;
|
|
_cmbLanguage.Top = 8;
|
|
_lblLanguage.Left = _cmbLanguage.Left - _lblLanguage.Width - 8;
|
|
};
|
|
|
|
var statusPanel = new Panel
|
|
{
|
|
Dock = DockStyle.Bottom,
|
|
Height = 30,
|
|
Padding = new Padding(12, 6, 12, 4),
|
|
};
|
|
Controls.Add(statusPanel);
|
|
|
|
_lblStatus.Dock = DockStyle.Fill;
|
|
_lblStatus.AutoEllipsis = true;
|
|
statusPanel.Controls.Add(_lblStatus);
|
|
|
|
var tabs = new TabControl
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
};
|
|
Controls.Add(tabs);
|
|
|
|
tabs.TabPages.Add(BuildWorkflowTab(repoRoot, sourceRoot, gameRoot, melonGeneratedRoot, templateOutput));
|
|
tabs.TabPages.Add(BuildCoverageTab());
|
|
tabs.TabPages.Add(BuildModAnalysisTab());
|
|
tabs.TabPages.Add(BuildLogTab());
|
|
tabs.TabPages.Add(BuildHelpTab());
|
|
}
|
|
|
|
private TabPage BuildWorkflowTab(string repoRoot, string sourceRoot, string gameRoot, string melonGeneratedRoot, string templateOutput)
|
|
{
|
|
var tab = new TabPage();
|
|
|
|
var split = new SplitContainer
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
Orientation = Orientation.Horizontal,
|
|
SplitterDistance = 330,
|
|
};
|
|
tab.Controls.Add(split);
|
|
|
|
var pathsGroup = new GroupBox
|
|
{
|
|
Name = "WorkflowPathsGroup",
|
|
Dock = DockStyle.Fill,
|
|
Padding = new Padding(10),
|
|
};
|
|
split.Panel1.Controls.Add(pathsGroup);
|
|
|
|
var table = new TableLayoutPanel
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
ColumnCount = 4,
|
|
RowCount = 9,
|
|
};
|
|
table.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 140));
|
|
table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
|
table.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120));
|
|
table.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 260));
|
|
for (int row = 0; row < table.RowCount; row++)
|
|
table.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
|
pathsGroup.Controls.Add(table);
|
|
|
|
AddPathRow(table, 0, "PathRepoLabel", _txtRepoRoot, repoRoot, _ => BrowseFolder(_txtRepoRoot));
|
|
AddPathRow(table, 1, "PathSourceLabel", _txtSourceRoot, sourceRoot, _ => BrowseFolder(_txtSourceRoot));
|
|
|
|
_btnImportMelon.Click += async (_, _) => await ImportFromMelonAsync();
|
|
AddPathRow(table, 2, "PathMelonLabel", _txtMelonGeneratedRoot, melonGeneratedRoot, _ => BrowseFolder(_txtMelonGeneratedRoot), _btnImportMelon);
|
|
|
|
_btnImportGame.Click += async (_, _) => await ImportFromGameAsync();
|
|
AddPathRow(table, 3, "PathGameLabel", _txtGameRoot, gameRoot, _ => BrowseFolder(_txtGameRoot), _btnImportGame);
|
|
|
|
AddPathRow(table, 4, "PathTemplateLabel", _txtTemplateOutput, templateOutput, _ => BrowseFolder(_txtTemplateOutput));
|
|
|
|
var lblPlugin = new Label { Name = "PathPluginLabel", Text = "Plugin Name", Anchor = AnchorStyles.Left, AutoSize = true, Margin = new Padding(4, 8, 4, 4) };
|
|
table.Controls.Add(lblPlugin, 0, 5);
|
|
|
|
_txtTemplatePluginName.Text = "greg.Plugin.HookTemplate";
|
|
_txtTemplatePluginName.Dock = DockStyle.Fill;
|
|
_txtTemplatePluginName.Margin = new Padding(4);
|
|
table.Controls.Add(_txtTemplatePluginName, 1, 5);
|
|
table.SetColumnSpan(_txtTemplatePluginName, 2);
|
|
|
|
_btnGenerateTemplate.Dock = DockStyle.Fill;
|
|
_btnGenerateTemplate.Margin = new Padding(4);
|
|
_btnGenerateTemplate.Click += async (_, _) => await GenerateTemplateAsync();
|
|
table.Controls.Add(_btnGenerateTemplate, 3, 5);
|
|
|
|
var actionPanel = new FlowLayoutPanel
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
FlowDirection = FlowDirection.LeftToRight,
|
|
WrapContents = true,
|
|
AutoSize = true,
|
|
Margin = new Padding(4),
|
|
};
|
|
|
|
_btnScan.AutoSize = true;
|
|
_btnScan.Click += async (_, _) => await ScanOnlyAsync();
|
|
|
|
_btnSync.AutoSize = true;
|
|
_btnSync.Click += async (_, _) => await ScanAndSyncIfNeededAsync();
|
|
|
|
_btnRegenerate.AutoSize = true;
|
|
_btnRegenerate.Click += async (_, _) => await RegenerateHooksAsync(_chkBuildAfterRegenerate.Checked);
|
|
|
|
_btnBuild.AutoSize = true;
|
|
_btnBuild.Click += async (_, _) => await BuildGregCoreAsync();
|
|
|
|
_btnOpenTemplateOutput.AutoSize = true;
|
|
_btnOpenTemplateOutput.Enabled = false;
|
|
_btnOpenTemplateOutput.Click += (_, _) => OpenTemplateOutputFolder();
|
|
|
|
actionPanel.Controls.Add(_btnScan);
|
|
actionPanel.Controls.Add(_btnSync);
|
|
actionPanel.Controls.Add(_btnRegenerate);
|
|
actionPanel.Controls.Add(_btnBuild);
|
|
actionPanel.Controls.Add(_btnOpenTemplateOutput);
|
|
|
|
table.Controls.Add(actionPanel, 0, 6);
|
|
table.SetColumnSpan(actionPanel, 4);
|
|
|
|
var optionsPanel = new FlowLayoutPanel
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
FlowDirection = FlowDirection.LeftToRight,
|
|
WrapContents = true,
|
|
AutoSize = true,
|
|
Margin = new Padding(4),
|
|
};
|
|
|
|
_chkWatch.AutoSize = true;
|
|
_chkWatch.CheckedChanged += (_, _) => ToggleWatchers(_chkWatch.Checked);
|
|
|
|
_chkAutoRegenerate.AutoSize = true;
|
|
_chkAutoRegenerate.Checked = true;
|
|
|
|
_chkBuildAfterRegenerate.AutoSize = true;
|
|
_chkBuildAfterRegenerate.Checked = true;
|
|
|
|
optionsPanel.Controls.Add(_chkWatch);
|
|
optionsPanel.Controls.Add(_chkAutoRegenerate);
|
|
optionsPanel.Controls.Add(_chkBuildAfterRegenerate);
|
|
|
|
table.Controls.Add(optionsPanel, 0, 7);
|
|
table.SetColumnSpan(optionsPanel, 4);
|
|
|
|
var hintLabel = new Label
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
AutoSize = true,
|
|
Margin = new Padding(4, 2, 4, 2),
|
|
Name = "WorkflowHintLabel",
|
|
};
|
|
table.Controls.Add(hintLabel, 0, 8);
|
|
table.SetColumnSpan(hintLabel, 4);
|
|
|
|
var summaryGroup = new GroupBox
|
|
{
|
|
Name = "WorkflowSummaryGroup",
|
|
Dock = DockStyle.Fill,
|
|
Padding = new Padding(8),
|
|
};
|
|
split.Panel2.Controls.Add(summaryGroup);
|
|
|
|
_txtSummary.Dock = DockStyle.Fill;
|
|
_txtSummary.Multiline = true;
|
|
_txtSummary.ScrollBars = ScrollBars.Both;
|
|
_txtSummary.Font = new System.Drawing.Font("Consolas", 10f);
|
|
summaryGroup.Controls.Add(_txtSummary);
|
|
|
|
return tab;
|
|
}
|
|
|
|
private static void AddPathRow(TableLayoutPanel table, int row, string labelName, TextBox textBox, string initialText, Action<object?> onBrowse, Button? actionButton = null)
|
|
{
|
|
var label = new Label
|
|
{
|
|
Name = labelName,
|
|
Text = labelName,
|
|
AutoSize = true,
|
|
Anchor = AnchorStyles.Left,
|
|
Margin = new Padding(4, 8, 4, 4),
|
|
};
|
|
table.Controls.Add(label, 0, row);
|
|
|
|
textBox.Text = initialText;
|
|
textBox.Dock = DockStyle.Fill;
|
|
textBox.Margin = new Padding(4);
|
|
table.Controls.Add(textBox, 1, row);
|
|
|
|
var btnBrowse = new Button
|
|
{
|
|
Name = labelName + "_Browse",
|
|
Dock = DockStyle.Fill,
|
|
Margin = new Padding(4),
|
|
Text = "Browse",
|
|
};
|
|
btnBrowse.Click += (_, arg) => onBrowse(arg);
|
|
table.Controls.Add(btnBrowse, 2, row);
|
|
|
|
if (actionButton != null)
|
|
{
|
|
actionButton.Dock = DockStyle.Fill;
|
|
actionButton.Margin = new Padding(4);
|
|
table.Controls.Add(actionButton, 3, row);
|
|
}
|
|
}
|
|
|
|
private TabPage BuildCoverageTab()
|
|
{
|
|
var tab = new TabPage();
|
|
|
|
var root = new TableLayoutPanel
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
ColumnCount = 1,
|
|
RowCount = 2,
|
|
Padding = new Padding(8),
|
|
};
|
|
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
|
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
tab.Controls.Add(root);
|
|
|
|
var toolbar = new TableLayoutPanel
|
|
{
|
|
Dock = DockStyle.Top,
|
|
ColumnCount = 6,
|
|
RowCount = 1,
|
|
Height = 36,
|
|
};
|
|
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 90));
|
|
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 220));
|
|
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
|
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 360));
|
|
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 260));
|
|
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 220));
|
|
|
|
var lblCategory = new Label { Name = "CategoryLabel", AutoSize = true, Anchor = AnchorStyles.Left };
|
|
toolbar.Controls.Add(lblCategory, 0, 0);
|
|
|
|
_cmbCategory.Dock = DockStyle.Fill;
|
|
_cmbCategory.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
_cmbCategory.SelectedIndexChanged += (_, _) => ApplyHookFilter();
|
|
toolbar.Controls.Add(_cmbCategory, 1, 0);
|
|
|
|
_lblCoverage.Dock = DockStyle.Fill;
|
|
_lblCoverage.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
|
toolbar.Controls.Add(_lblCoverage, 2, 0);
|
|
|
|
_lblGregCoreCoverage.Dock = DockStyle.Fill;
|
|
_lblGregCoreCoverage.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
|
toolbar.Controls.Add(_lblGregCoreCoverage, 3, 0);
|
|
|
|
_lblAssemblyCoverage.Dock = DockStyle.Fill;
|
|
_lblAssemblyCoverage.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
|
toolbar.Controls.Add(_lblAssemblyCoverage, 4, 0);
|
|
|
|
var btnRefreshCoverage = new Button
|
|
{
|
|
Name = "RefreshCoverageButton",
|
|
Dock = DockStyle.Fill,
|
|
};
|
|
btnRefreshCoverage.Click += (_, _) => RefreshHookRows(_store.TryLoadSnapshot());
|
|
toolbar.Controls.Add(btnRefreshCoverage, 5, 0);
|
|
|
|
root.Controls.Add(toolbar, 0, 0);
|
|
|
|
var coverageTabs = new TabControl
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
Name = "CoverageTabs",
|
|
};
|
|
root.Controls.Add(coverageTabs, 0, 1);
|
|
|
|
var overviewTab = new TabPage { Name = "CoverageOverviewTab" };
|
|
_txtCoverageDetails.Dock = DockStyle.Fill;
|
|
_txtCoverageDetails.Multiline = true;
|
|
_txtCoverageDetails.ReadOnly = true;
|
|
_txtCoverageDetails.ScrollBars = ScrollBars.Both;
|
|
_txtCoverageDetails.Font = new System.Drawing.Font("Consolas", 10f);
|
|
overviewTab.Controls.Add(_txtCoverageDetails);
|
|
coverageTabs.TabPages.Add(overviewTab);
|
|
|
|
var hooksTab = new TabPage { Name = "CoverageHooksTab" };
|
|
_gridHooks.Dock = DockStyle.Fill;
|
|
_gridHooks.AllowUserToAddRows = false;
|
|
_gridHooks.AllowUserToDeleteRows = false;
|
|
_gridHooks.ReadOnly = true;
|
|
_gridHooks.AutoGenerateColumns = false;
|
|
_gridHooks.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
_gridHooks.MultiSelect = false;
|
|
_gridHooks.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(HookCatalogRow.Il2CppHookEvent), Name = "HookEventColumn", Width = 240 });
|
|
_gridHooks.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(HookCatalogRow.GregApiCall), Name = "GregApiColumn", Width = 320 });
|
|
_gridHooks.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(HookCatalogRow.Category), Name = "CategoryColumn", Width = 160 });
|
|
_gridHooks.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(HookCatalogRow.PatchTarget), Name = "PatchTargetColumn", AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill });
|
|
hooksTab.Controls.Add(_gridHooks);
|
|
coverageTabs.TabPages.Add(hooksTab);
|
|
|
|
var assemblyTab = new TabPage { Name = "CoverageAssembliesTab" };
|
|
_gridAssemblyCoverage.Dock = DockStyle.Fill;
|
|
_gridAssemblyCoverage.AllowUserToAddRows = false;
|
|
_gridAssemblyCoverage.AllowUserToDeleteRows = false;
|
|
_gridAssemblyCoverage.ReadOnly = true;
|
|
_gridAssemblyCoverage.AutoGenerateColumns = false;
|
|
_gridAssemblyCoverage.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
_gridAssemblyCoverage.MultiSelect = false;
|
|
_gridAssemblyCoverage.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(AssemblyCoverageRow.Assembly), Name = "AssemblyColumn", Width = 220 });
|
|
_gridAssemblyCoverage.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(AssemblyCoverageRow.CoveragePercent), Name = "CoveragePercentColumn", Width = 130, DefaultCellStyle = new DataGridViewCellStyle { Format = "F2" } });
|
|
_gridAssemblyCoverage.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(AssemblyCoverageRow.CoveredUnique), Name = "CoveredColumn", Width = 100 });
|
|
_gridAssemblyCoverage.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(AssemblyCoverageRow.ExpectedUnique), Name = "ExpectedColumn", Width = 100 });
|
|
_gridAssemblyCoverage.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(AssemblyCoverageRow.MissingUnique), Name = "MissingColumn", Width = 100 });
|
|
_gridAssemblyCoverage.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(AssemblyCoverageRow.HookedUnique), Name = "HookedColumn", Width = 100 });
|
|
assemblyTab.Controls.Add(_gridAssemblyCoverage);
|
|
coverageTabs.TabPages.Add(assemblyTab);
|
|
|
|
return tab;
|
|
}
|
|
|
|
private TabPage BuildModAnalysisTab()
|
|
{
|
|
var tab = new TabPage();
|
|
|
|
var root = new TableLayoutPanel
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
ColumnCount = 1,
|
|
RowCount = 2,
|
|
Padding = new Padding(8),
|
|
};
|
|
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
|
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
|
|
tab.Controls.Add(root);
|
|
|
|
var top = new TableLayoutPanel
|
|
{
|
|
Dock = DockStyle.Top,
|
|
ColumnCount = 5,
|
|
RowCount = 1,
|
|
Height = 36,
|
|
};
|
|
top.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
|
|
top.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
|
|
top.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120));
|
|
top.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 120));
|
|
top.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 180));
|
|
|
|
var lbl = new Label { Name = "ModProjectLabel", AutoSize = true, Anchor = AnchorStyles.Left };
|
|
top.Controls.Add(lbl, 0, 0);
|
|
|
|
_txtModProjectPath.Dock = DockStyle.Fill;
|
|
_txtModProjectPath.Margin = new Padding(4);
|
|
_txtModProjectPath.Text = Path.Combine(ResolveRepoRoot(), "gregMod.GregifyEmployees");
|
|
top.Controls.Add(_txtModProjectPath, 1, 0);
|
|
|
|
_btnBrowseModProject.Dock = DockStyle.Fill;
|
|
_btnBrowseModProject.Click += (_, _) => BrowseFolder(_txtModProjectPath);
|
|
top.Controls.Add(_btnBrowseModProject, 2, 0);
|
|
|
|
_btnOpenModProject.Dock = DockStyle.Fill;
|
|
_btnOpenModProject.Click += (_, _) => OpenDirectory(_txtModProjectPath.Text.Trim(), "mod-project");
|
|
top.Controls.Add(_btnOpenModProject, 3, 0);
|
|
|
|
_btnAnalyzeModProject.Dock = DockStyle.Fill;
|
|
_btnAnalyzeModProject.Click += async (_, _) => await AnalyzeModProjectAsync();
|
|
top.Controls.Add(_btnAnalyzeModProject, 4, 0);
|
|
|
|
root.Controls.Add(top, 0, 0);
|
|
|
|
var modTabs = new TabControl
|
|
{
|
|
Dock = DockStyle.Fill,
|
|
Name = "ModAnalysisTabs",
|
|
};
|
|
root.Controls.Add(modTabs, 0, 1);
|
|
|
|
var summaryTab = new TabPage { Name = "ModSummaryTab" };
|
|
_txtModAnalysisSummary.Dock = DockStyle.Fill;
|
|
_txtModAnalysisSummary.Multiline = true;
|
|
_txtModAnalysisSummary.ScrollBars = ScrollBars.Both;
|
|
_txtModAnalysisSummary.Font = new System.Drawing.Font("Consolas", 10f);
|
|
summaryTab.Controls.Add(_txtModAnalysisSummary);
|
|
modTabs.TabPages.Add(summaryTab);
|
|
|
|
var opportunitiesTab = new TabPage { Name = "ModOpportunitiesTab" };
|
|
_gridMigration.Dock = DockStyle.Fill;
|
|
_gridMigration.AllowUserToAddRows = false;
|
|
_gridMigration.AllowUserToDeleteRows = false;
|
|
_gridMigration.ReadOnly = true;
|
|
_gridMigration.AutoGenerateColumns = false;
|
|
_gridMigration.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
_gridMigration.MultiSelect = false;
|
|
_gridMigration.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(MigrationOpportunityRow.Type), Name = "MigrationTypeColumn", Width = 150 });
|
|
_gridMigration.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(MigrationOpportunityRow.CurrentPattern), Name = "CurrentPatternColumn", Width = 280 });
|
|
_gridMigration.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(MigrationOpportunityRow.SuggestedGregHook), Name = "SuggestedHookColumn", Width = 320 });
|
|
_gridMigration.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(MigrationOpportunityRow.Suggestion), Name = "SuggestionColumn", AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill });
|
|
opportunitiesTab.Controls.Add(_gridMigration);
|
|
modTabs.TabPages.Add(opportunitiesTab);
|
|
|
|
var fileInsightsTab = new TabPage { Name = "ModFileInsightsTab" };
|
|
_gridFileInsights.Dock = DockStyle.Fill;
|
|
_gridFileInsights.AllowUserToAddRows = false;
|
|
_gridFileInsights.AllowUserToDeleteRows = false;
|
|
_gridFileInsights.ReadOnly = true;
|
|
_gridFileInsights.AutoGenerateColumns = false;
|
|
_gridFileInsights.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
_gridFileInsights.MultiSelect = false;
|
|
_gridFileInsights.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(ModProjectFileInsightRow.FilePath), Name = "FilePathColumn", AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill });
|
|
_gridFileInsights.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(ModProjectFileInsightRow.HarmonyPatches), Name = "FileHarmonyColumn", Width = 90 });
|
|
_gridFileInsights.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(ModProjectFileInsightRow.GregSubscriptions), Name = "FileGregSubsColumn", Width = 90 });
|
|
_gridFileInsights.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(ModProjectFileInsightRow.GregApiReferences), Name = "FileGregApiColumn", Width = 90 });
|
|
_gridFileInsights.Columns.Add(new DataGridViewCheckBoxColumn { DataPropertyName = nameof(ModProjectFileInsightRow.NeedsMigration), Name = "FileNeedsMigrationColumn", Width = 90 });
|
|
_gridFileInsights.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(ModProjectFileInsightRow.Recommendation), Name = "FileRecommendationColumn", Width = 360 });
|
|
fileInsightsTab.Controls.Add(_gridFileInsights);
|
|
modTabs.TabPages.Add(fileInsightsTab);
|
|
|
|
var hooksTab = new TabPage { Name = "ModUsedHooksTab" };
|
|
_txtUsedHooks.Dock = DockStyle.Fill;
|
|
_txtUsedHooks.Multiline = true;
|
|
_txtUsedHooks.ReadOnly = true;
|
|
_txtUsedHooks.ScrollBars = ScrollBars.Both;
|
|
_txtUsedHooks.Font = new System.Drawing.Font("Consolas", 10f);
|
|
hooksTab.Controls.Add(_txtUsedHooks);
|
|
modTabs.TabPages.Add(hooksTab);
|
|
|
|
return tab;
|
|
}
|
|
|
|
private TabPage BuildLogTab()
|
|
{
|
|
var tab = new TabPage();
|
|
|
|
_txtLog.Dock = DockStyle.Fill;
|
|
_txtLog.Multiline = true;
|
|
_txtLog.ScrollBars = ScrollBars.Both;
|
|
_txtLog.Font = new System.Drawing.Font("Consolas", 10f);
|
|
tab.Controls.Add(_txtLog);
|
|
|
|
return tab;
|
|
}
|
|
|
|
private TabPage BuildHelpTab()
|
|
{
|
|
var tab = new TabPage();
|
|
_txtHelp.Dock = DockStyle.Fill;
|
|
_txtHelp.Multiline = true;
|
|
_txtHelp.ReadOnly = true;
|
|
_txtHelp.ScrollBars = ScrollBars.Both;
|
|
_txtHelp.Font = new System.Drawing.Font("Consolas", 10f);
|
|
tab.Controls.Add(_txtHelp);
|
|
return tab;
|
|
}
|
|
|
|
private static string ResolveRepoRoot()
|
|
{
|
|
string current = AppContext.BaseDirectory;
|
|
DirectoryInfo? dir = new DirectoryInfo(current);
|
|
|
|
while (dir != null)
|
|
{
|
|
bool hasGregCore = Directory.Exists(Path.Combine(dir.FullName, "gregCore"));
|
|
bool hasRefs = Directory.Exists(Path.Combine(dir.FullName, "gregReferences"));
|
|
if (hasGregCore && hasRefs)
|
|
return dir.FullName;
|
|
|
|
dir = dir.Parent;
|
|
}
|
|
|
|
return Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", ".."));
|
|
}
|
|
|
|
private void BrowseFolder(TextBox target)
|
|
{
|
|
using var dialog = new FolderBrowserDialog
|
|
{
|
|
SelectedPath = target.Text,
|
|
UseDescriptionForTitle = true,
|
|
Description = _language == UiLanguage.German ? "Ordner auswählen" : "Select folder",
|
|
};
|
|
|
|
if (dialog.ShowDialog(this) == DialogResult.OK)
|
|
target.Text = dialog.SelectedPath;
|
|
}
|
|
|
|
private void ConfigureWatcherDebounce()
|
|
{
|
|
_debounceTimer.Interval = 2500;
|
|
_debounceTimer.Tick += async (_, _) =>
|
|
{
|
|
_debounceTimer.Stop();
|
|
if (!_pendingFileChange)
|
|
return;
|
|
|
|
_pendingFileChange = false;
|
|
await ScanAndSyncIfNeededAsync();
|
|
};
|
|
}
|
|
|
|
private void ToggleWatchers(bool enable)
|
|
{
|
|
foreach (FileSystemWatcher watcher in _watchers)
|
|
watcher.Dispose();
|
|
_watchers.Clear();
|
|
|
|
if (!enable)
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? "Watcher deaktiviert." : "Watcher disabled.");
|
|
return;
|
|
}
|
|
|
|
string sourceRoot = _txtSourceRoot.Text.Trim();
|
|
if (!Directory.Exists(sourceRoot))
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? "Watcher konnte nicht gestartet werden: SourceRoot nicht gefunden." : "Watcher not started: source root not found.");
|
|
_chkWatch.Checked = false;
|
|
return;
|
|
}
|
|
|
|
string[] directories = Directory.GetDirectories(sourceRoot)
|
|
.Where(path =>
|
|
{
|
|
string name = Path.GetFileName(path);
|
|
return name.StartsWith("Il2Cpp", StringComparison.OrdinalIgnoreCase)
|
|
|| name.StartsWith("Unity", StringComparison.OrdinalIgnoreCase)
|
|
|| name.StartsWith("UnityEngine", StringComparison.OrdinalIgnoreCase);
|
|
})
|
|
.ToArray();
|
|
|
|
foreach (string dir in directories)
|
|
{
|
|
var watcher = new FileSystemWatcher(dir, "*.cs")
|
|
{
|
|
IncludeSubdirectories = true,
|
|
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size,
|
|
EnableRaisingEvents = true,
|
|
};
|
|
|
|
watcher.Changed += OnWatchedFileChanged;
|
|
watcher.Created += OnWatchedFileChanged;
|
|
watcher.Deleted += OnWatchedFileChanged;
|
|
watcher.Renamed += (_, _) => OnWatchedFileChanged(null, null!);
|
|
_watchers.Add(watcher);
|
|
}
|
|
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Watcher aktiv: {directories.Length} Source-Ordner."
|
|
: $"Watcher enabled: {directories.Length} source folders.");
|
|
}
|
|
|
|
private void OnWatchedFileChanged(object? sender, FileSystemEventArgs args)
|
|
{
|
|
_pendingFileChange = true;
|
|
_debounceTimer.Stop();
|
|
_debounceTimer.Start();
|
|
}
|
|
|
|
private async Task ScanOnlyAsync()
|
|
{
|
|
await RunExclusiveAsync(async () =>
|
|
{
|
|
string sourceRoot = _txtSourceRoot.Text.Trim();
|
|
SourceSnapshot current = _automation.ScanCurrent(sourceRoot);
|
|
ChangeReport report = _automation.CompareWithPrevious(current);
|
|
_automation.Persist(current, report);
|
|
|
|
SetSummary(report, current);
|
|
RefreshHookRows(current);
|
|
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Scan fertig. Methods: {current.Methods.Count}, Added: {report.Added}, Removed: {report.Removed}, SignatureChanged: {report.SignatureChanged}, BodyChanged: {report.BodyChanged}"
|
|
: $"Scan complete. Methods: {current.Methods.Count}, Added: {report.Added}, Removed: {report.Removed}, SignatureChanged: {report.SignatureChanged}, BodyChanged: {report.BodyChanged}");
|
|
}, _language == UiLanguage.German ? "Scanne Änderungen..." : "Scanning changes...");
|
|
}
|
|
|
|
private async Task ScanAndSyncIfNeededAsync()
|
|
{
|
|
await RunExclusiveAsync(async () =>
|
|
{
|
|
await ScanAndSyncCoreAsync();
|
|
}, _language == UiLanguage.German ? "Scanne + synchronisiere..." : "Scanning + syncing...");
|
|
}
|
|
|
|
private async Task ImportFromMelonAsync()
|
|
{
|
|
await RunExclusiveAsync(async () =>
|
|
{
|
|
string melonRoot = _txtMelonGeneratedRoot.Text.Trim();
|
|
string sourceRoot = _txtSourceRoot.Text.Trim();
|
|
|
|
MelonImportResult result = _automation.ImportMelonGeneratedSources(melonRoot, sourceRoot);
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Melon-Import fertig. Dirs: {result.CopiedDirectories}, Files: {result.CopiedFiles}"
|
|
: $"Melon import completed. Dirs: {result.CopiedDirectories}, Files: {result.CopiedFiles}");
|
|
AppendLog(_language == UiLanguage.German ? $"Quelle: {result.SourceRootUsed}" : $"Source: {result.SourceRootUsed}");
|
|
AppendLog(_language == UiLanguage.German ? $"Ziel: {result.TargetRoot}" : $"Target: {result.TargetRoot}");
|
|
|
|
await ScanAndSyncCoreAsync();
|
|
}, _language == UiLanguage.German ? "Importiere Melon-generated Dateien..." : "Importing Melon generated files...");
|
|
}
|
|
|
|
private async Task ImportFromGameAsync()
|
|
{
|
|
await RunExclusiveAsync(async () =>
|
|
{
|
|
string gameRoot = _txtGameRoot.Text.Trim();
|
|
string sourceRoot = _txtSourceRoot.Text.Trim();
|
|
|
|
MelonImportResult result = _automation.ImportFromGameDirectory(gameRoot, sourceRoot);
|
|
_txtMelonGeneratedRoot.Text = result.SourceRootUsed;
|
|
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Game-Import fertig. Dirs: {result.CopiedDirectories}, Files: {result.CopiedFiles}"
|
|
: $"Game import completed. Dirs: {result.CopiedDirectories}, Files: {result.CopiedFiles}");
|
|
AppendLog(_language == UiLanguage.German ? $"Spiel: {gameRoot}" : $"Game: {gameRoot}");
|
|
AppendLog(_language == UiLanguage.German ? $"Quelle: {result.SourceRootUsed}" : $"Source: {result.SourceRootUsed}");
|
|
AppendLog(_language == UiLanguage.German ? $"Ziel: {result.TargetRoot}" : $"Target: {result.TargetRoot}");
|
|
|
|
await ScanAndSyncCoreAsync();
|
|
}, _language == UiLanguage.German ? "Importiere Daten aus Spielordner..." : "Importing data from game directory...");
|
|
}
|
|
|
|
private async Task GenerateTemplateAsync()
|
|
{
|
|
await RunExclusiveAsync(async () =>
|
|
{
|
|
if (_allHookRows.Count == 0)
|
|
RefreshHookRows(_store.TryLoadSnapshot());
|
|
|
|
string repoRoot = _txtRepoRoot.Text.Trim();
|
|
string outputDir = _txtTemplateOutput.Text.Trim();
|
|
string pluginName = string.IsNullOrWhiteSpace(_txtTemplatePluginName.Text)
|
|
? "greg.Plugin.HookTemplate"
|
|
: _txtTemplatePluginName.Text.Trim();
|
|
|
|
string rootNamespace = pluginName.Replace('-', '_').Replace(' ', '_');
|
|
string className = BuildClassNameFromPluginName(pluginName);
|
|
|
|
(string csprojPath, string mainCsPath, string readmePath) = _automation.GeneratePluginTemplate(
|
|
repoRoot,
|
|
outputDir,
|
|
pluginName,
|
|
rootNamespace,
|
|
className,
|
|
"gregExtractor",
|
|
_allHookRows);
|
|
|
|
_lastGeneratedTemplateDirectory = outputDir;
|
|
_btnOpenTemplateOutput.Enabled = Directory.Exists(outputDir);
|
|
|
|
AppendLog(_language == UiLanguage.German ? "Plugin-Template erzeugt:" : "Plugin template generated:");
|
|
AppendLog($"- {csprojPath}");
|
|
AppendLog($"- {mainCsPath}");
|
|
AppendLog($"- {readmePath}");
|
|
|
|
await Task.CompletedTask;
|
|
}, _language == UiLanguage.German ? "Erzeuge Plugin-Template..." : "Generating plugin template...");
|
|
}
|
|
|
|
private void OpenTemplateOutputFolder()
|
|
{
|
|
string path = !string.IsNullOrWhiteSpace(_lastGeneratedTemplateDirectory)
|
|
? _lastGeneratedTemplateDirectory
|
|
: _txtTemplateOutput.Text.Trim();
|
|
|
|
OpenDirectory(path, "template-output");
|
|
}
|
|
|
|
private void OpenDirectory(string path, string context)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path))
|
|
{
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Ordner nicht gefunden ({context}): {path}"
|
|
: $"Directory not found ({context}): {path}");
|
|
return;
|
|
}
|
|
|
|
Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = path,
|
|
UseShellExecute = true,
|
|
});
|
|
}
|
|
|
|
private async Task AnalyzeModProjectAsync()
|
|
{
|
|
await RunExclusiveAsync(async () =>
|
|
{
|
|
string projectPath = _txtModProjectPath.Text.Trim();
|
|
if (string.IsNullOrWhiteSpace(projectPath) || !Directory.Exists(projectPath))
|
|
throw new DirectoryNotFoundException(_language == UiLanguage.German ? "Modprojekt-Pfad nicht gefunden." : "Mod project path not found.");
|
|
|
|
string repoRoot = _txtRepoRoot.Text.Trim();
|
|
List<HookCatalogRow> gregCoreRows = _automation.LoadGregCoreImplementedHookRows(repoRoot).ToList();
|
|
if (gregCoreRows.Count == 0)
|
|
gregCoreRows = _automation.LoadHookCatalogRows(repoRoot, _txtMelonGeneratedRoot.Text.Trim()).ToList();
|
|
|
|
ModProjectAnalysisResult analysis = _modAnalyzer.Analyze(projectPath, gregCoreRows);
|
|
SetModAnalysisSummary(analysis);
|
|
_gridMigration.DataSource = analysis.Opportunities;
|
|
_gridFileInsights.DataSource = analysis.FileInsights;
|
|
|
|
var usedHooksBuilder = new StringBuilder();
|
|
if (_language == UiLanguage.German)
|
|
{
|
|
usedHooksBuilder.AppendLine("Im Projekt genutzte greg Hooks:");
|
|
foreach (string hook in analysis.UsedHooks)
|
|
usedHooksBuilder.AppendLine($"- {hook}");
|
|
|
|
usedHooksBuilder.AppendLine();
|
|
usedHooksBuilder.AppendLine("Empfohlene zusätzliche Hooks:");
|
|
foreach (string hook in analysis.SuggestedHooks.Take(30))
|
|
usedHooksBuilder.AppendLine($"- {hook}");
|
|
}
|
|
else
|
|
{
|
|
usedHooksBuilder.AppendLine("greg hooks used in project:");
|
|
foreach (string hook in analysis.UsedHooks)
|
|
usedHooksBuilder.AppendLine($"- {hook}");
|
|
|
|
usedHooksBuilder.AppendLine();
|
|
usedHooksBuilder.AppendLine("Recommended additional hooks:");
|
|
foreach (string hook in analysis.SuggestedHooks.Take(30))
|
|
usedHooksBuilder.AppendLine($"- {hook}");
|
|
}
|
|
|
|
_txtUsedHooks.Text = usedHooksBuilder.ToString();
|
|
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Modprojekt analysiert: {projectPath}"
|
|
: $"Mod project analyzed: {projectPath}");
|
|
|
|
await Task.CompletedTask;
|
|
}, _language == UiLanguage.German ? "Analysiere Modprojekt..." : "Analyzing mod project...");
|
|
}
|
|
|
|
private void SetModAnalysisSummary(ModProjectAnalysisResult analysis)
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (_language == UiLanguage.German)
|
|
{
|
|
sb.AppendLine("Modprojekt-Analyse");
|
|
sb.AppendLine("------------------------------------------------------------");
|
|
sb.AppendLine($"Projekt : {analysis.ProjectRoot}");
|
|
sb.AppendLine($"C# Dateien : {analysis.CSharpFileCount}");
|
|
sb.AppendLine($"Harmony Patches : {analysis.HarmonyPatchCount}");
|
|
sb.AppendLine($"MelonMod Vererbungen : {analysis.MelonModInheritanceCount}");
|
|
sb.AppendLine($"gregPluginBase Klassen : {analysis.GregPluginInheritanceCount}");
|
|
sb.AppendLine($"greg Event Subs : {analysis.GregEventSubscriptionCount}");
|
|
sb.AppendLine($"greg API Referenzen : {analysis.GregApiReferenceCount}");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"Migrationsgrad : {analysis.MigrationPercent:F2}%");
|
|
sb.AppendLine($"Integrationspunkte : {analysis.IntegrationPointsMigrated}/{analysis.IntegrationPointsTotal}");
|
|
sb.AppendLine($"Noch offen : {analysis.IntegrationPointsRemaining}");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"Bekannte gregCore Hooks: {analysis.KnownGregCoreHooks}");
|
|
sb.AppendLine($"Im Projekt genutzt : {analysis.UsedGregCoreHooks}");
|
|
sb.AppendLine($"Noch nicht genutzt : {analysis.MissingGregCoreHooks}");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Empfohlene nächste Schritte:");
|
|
foreach (string hook in analysis.SuggestedHooks.Take(10))
|
|
sb.AppendLine($"- Prüfen: {hook}");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("Mod Project Analysis");
|
|
sb.AppendLine("------------------------------------------------------------");
|
|
sb.AppendLine($"Project : {analysis.ProjectRoot}");
|
|
sb.AppendLine($"C# files : {analysis.CSharpFileCount}");
|
|
sb.AppendLine($"Harmony patches : {analysis.HarmonyPatchCount}");
|
|
sb.AppendLine($"MelonMod inheritances : {analysis.MelonModInheritanceCount}");
|
|
sb.AppendLine($"gregPluginBase classes : {analysis.GregPluginInheritanceCount}");
|
|
sb.AppendLine($"greg event subs : {analysis.GregEventSubscriptionCount}");
|
|
sb.AppendLine($"greg API references : {analysis.GregApiReferenceCount}");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"Migration level : {analysis.MigrationPercent:F2}%");
|
|
sb.AppendLine($"Integration points : {analysis.IntegrationPointsMigrated}/{analysis.IntegrationPointsTotal}");
|
|
sb.AppendLine($"Remaining : {analysis.IntegrationPointsRemaining}");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"Known gregCore hooks : {analysis.KnownGregCoreHooks}");
|
|
sb.AppendLine($"Used in project : {analysis.UsedGregCoreHooks}");
|
|
sb.AppendLine($"Not used yet : {analysis.MissingGregCoreHooks}");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Recommended next steps:");
|
|
foreach (string hook in analysis.SuggestedHooks.Take(10))
|
|
sb.AppendLine($"- Evaluate: {hook}");
|
|
}
|
|
|
|
_txtModAnalysisSummary.Text = sb.ToString();
|
|
}
|
|
|
|
private async Task ScanAndSyncCoreAsync()
|
|
{
|
|
string sourceRoot = _txtSourceRoot.Text.Trim();
|
|
SourceSnapshot current = _automation.ScanCurrent(sourceRoot);
|
|
ChangeReport report = _automation.CompareWithPrevious(current);
|
|
_automation.Persist(current, report);
|
|
SetSummary(report, current);
|
|
RefreshHookRows(current);
|
|
|
|
if (!report.HasChanges)
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? "Keine Änderungen erkannt." : "No changes detected.");
|
|
return;
|
|
}
|
|
|
|
AppendLog(_language == UiLanguage.German
|
|
? $"Änderungen erkannt: +{report.Added} / -{report.Removed} / SigΔ {report.SignatureChanged} / BodyΔ {report.BodyChanged}"
|
|
: $"Changes detected: +{report.Added} / -{report.Removed} / SigΔ {report.SignatureChanged} / BodyΔ {report.BodyChanged}");
|
|
|
|
if (_chkAutoRegenerate.Checked)
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? "Auto-Regenerate aktiv -> Generator startet." : "Auto-regenerate enabled -> starting generator.");
|
|
await RegenerateHooksCoreAsync(_chkBuildAfterRegenerate.Checked);
|
|
}
|
|
else
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? "Auto-Regenerate ist aus. Nur Report aktualisiert." : "Auto-regenerate disabled. Report updated only.");
|
|
}
|
|
}
|
|
|
|
private async Task RegenerateHooksAsync(bool buildAfter)
|
|
{
|
|
await RunExclusiveAsync(async () => await RegenerateHooksCoreAsync(buildAfter), _language == UiLanguage.German ? "Regeneriere Hooks..." : "Regenerating hooks...");
|
|
}
|
|
|
|
private async Task RegenerateHooksCoreAsync(bool buildAfter)
|
|
{
|
|
string repoRoot = _txtRepoRoot.Text.Trim();
|
|
(int exitCode, string output) = await _automation.RunGeneratorAsync(repoRoot, CancellationToken.None);
|
|
|
|
AppendLog("--- Generator Output ---");
|
|
AppendLog(output);
|
|
|
|
if (exitCode != 0)
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? $"Generator fehlgeschlagen (ExitCode={exitCode})." : $"Generator failed (ExitCode={exitCode}).");
|
|
return;
|
|
}
|
|
|
|
AppendLog(_language == UiLanguage.German ? "Generator erfolgreich." : "Generator successful.");
|
|
RefreshHookRows(_store.TryLoadSnapshot());
|
|
|
|
if (buildAfter)
|
|
await BuildGregCoreCoreAsync();
|
|
}
|
|
|
|
private async Task BuildGregCoreAsync()
|
|
{
|
|
await RunExclusiveAsync(async () => await BuildGregCoreCoreAsync(), _language == UiLanguage.German ? "Baue gregCore..." : "Building gregCore...");
|
|
}
|
|
|
|
private async Task BuildGregCoreCoreAsync()
|
|
{
|
|
string repoRoot = _txtRepoRoot.Text.Trim();
|
|
(int exitCode, string output) = await _automation.BuildGregCoreAsync(repoRoot, CancellationToken.None);
|
|
|
|
AppendLog("--- Build Output ---");
|
|
AppendLog(output);
|
|
|
|
AppendLog(exitCode == 0
|
|
? (_language == UiLanguage.German ? "Build erfolgreich." : "Build successful.")
|
|
: (_language == UiLanguage.German ? $"Build fehlgeschlagen (ExitCode={exitCode})." : $"Build failed (ExitCode={exitCode})."));
|
|
}
|
|
|
|
private async Task RunExclusiveAsync(Func<Task> action, string status)
|
|
{
|
|
if (!await _operationLock.WaitAsync(0))
|
|
{
|
|
AppendLog(_language == UiLanguage.German ? "Eine Operation läuft bereits." : "Another operation is already running.");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
SetBusyUi(true, status);
|
|
await action();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AppendLog($"{(_language == UiLanguage.German ? "FEHLER" : "ERROR")}: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
SetBusyUi(false, _language == UiLanguage.German ? "Bereit" : "Ready");
|
|
_operationLock.Release();
|
|
}
|
|
}
|
|
|
|
private void SetBusyUi(bool busy, string status)
|
|
{
|
|
_btnScan.Enabled = !busy;
|
|
_btnSync.Enabled = !busy;
|
|
_btnRegenerate.Enabled = !busy;
|
|
_btnBuild.Enabled = !busy;
|
|
_btnImportGame.Enabled = !busy;
|
|
_btnImportMelon.Enabled = !busy;
|
|
_btnGenerateTemplate.Enabled = !busy;
|
|
_btnOpenTemplateOutput.Enabled = !busy && Directory.Exists(_txtTemplateOutput.Text.Trim());
|
|
_btnBrowseModProject.Enabled = !busy;
|
|
_btnAnalyzeModProject.Enabled = !busy;
|
|
_btnOpenModProject.Enabled = !busy;
|
|
_cmbLanguage.Enabled = !busy;
|
|
_lblStatus.Text = $"{T("status")}: {status}";
|
|
}
|
|
|
|
private void SetSummary(ChangeReport report, SourceSnapshot snapshot)
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (_language == UiLanguage.German)
|
|
{
|
|
sb.AppendLine("gregExtractor - Änderungsübersicht");
|
|
sb.AppendLine("------------------------------------------------------------");
|
|
sb.AppendLine($"Zeit (UTC) : {report.CreatedUtc:yyyy-MM-dd HH:mm:ss}");
|
|
sb.AppendLine($"SourceRoot : {snapshot.SourceRoot}");
|
|
sb.AppendLine($"Source Dateien : {snapshot.FileCount}");
|
|
sb.AppendLine($"Vorherige Methoden : {report.PreviousCount}");
|
|
sb.AppendLine($"Aktuelle Methoden : {report.CurrentCount}");
|
|
sb.AppendLine($"Added : {report.Added}");
|
|
sb.AppendLine($"Removed : {report.Removed}");
|
|
sb.AppendLine($"Signature Changed : {report.SignatureChanged}");
|
|
sb.AppendLine($"Body Changed : {report.BodyChanged}");
|
|
sb.AppendLine($"Hat Änderungen : {report.HasChanges}");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Hinweis:");
|
|
sb.AppendLine("- Signature Changed: API-/Signaturänderung erkannt.");
|
|
sb.AppendLine("- Body Changed: Verhalten geändert, Signatur gleich.");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("gregExtractor - Change Summary");
|
|
sb.AppendLine("------------------------------------------------------------");
|
|
sb.AppendLine($"Time (UTC) : {report.CreatedUtc:yyyy-MM-dd HH:mm:ss}");
|
|
sb.AppendLine($"SourceRoot : {snapshot.SourceRoot}");
|
|
sb.AppendLine($"Source files : {snapshot.FileCount}");
|
|
sb.AppendLine($"Previous methods : {report.PreviousCount}");
|
|
sb.AppendLine($"Current methods : {report.CurrentCount}");
|
|
sb.AppendLine($"Added : {report.Added}");
|
|
sb.AppendLine($"Removed : {report.Removed}");
|
|
sb.AppendLine($"Signature changed : {report.SignatureChanged}");
|
|
sb.AppendLine($"Body changed : {report.BodyChanged}");
|
|
sb.AppendLine($"Has changes : {report.HasChanges}");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Notes:");
|
|
sb.AppendLine("- Signature changed: API/signature changes detected.");
|
|
sb.AppendLine("- Body changed: behavior changed, same signature.");
|
|
}
|
|
|
|
_txtSummary.Text = sb.ToString();
|
|
}
|
|
|
|
private void RefreshHookRows(SourceSnapshot? currentSnapshot)
|
|
{
|
|
try
|
|
{
|
|
string repoRoot = _txtRepoRoot.Text.Trim();
|
|
_allHookRows = _automation.LoadHookCatalogRows(repoRoot, _txtMelonGeneratedRoot.Text.Trim()).ToList();
|
|
List<HookCatalogRow> gregCoreRows = _automation.LoadGregCoreImplementedHookRows(repoRoot).ToList();
|
|
|
|
UpdateCategoryOptions();
|
|
ApplyHookFilter();
|
|
|
|
SourceSnapshot? snapshot = currentSnapshot ?? _store.TryLoadSnapshot();
|
|
if (snapshot == null)
|
|
{
|
|
_lblCoverage.Text = _language == UiLanguage.German ? $"Katalog: n/a | Hooks: {_allHookRows.Count}" : $"Catalog: n/a | Hooks: {_allHookRows.Count}";
|
|
_lblGregCoreCoverage.Text = _language == UiLanguage.German
|
|
? $"gregCore Umsetzung: n/a | Hooks: {gregCoreRows.Count}"
|
|
: $"gregCore implementation: n/a | hooks: {gregCoreRows.Count}";
|
|
_lblAssemblyCoverage.Text = "Assemblies: n/a";
|
|
_txtCoverageDetails.Text = _language == UiLanguage.German
|
|
? "Keine Snapshot-Daten vorhanden. Bitte zuerst importieren und scannen."
|
|
: "No snapshot data available. Please import and scan first.";
|
|
_gridAssemblyCoverage.DataSource = null;
|
|
return;
|
|
}
|
|
|
|
HookCoverage catalogCoverage = _automation.CalculateCoverage(snapshot, _allHookRows);
|
|
HookCoverage gregCoreCoverage = _automation.CalculateCoverage(snapshot, gregCoreRows);
|
|
bool pluginReady = Math.Abs(gregCoreCoverage.CoveragePercent - 100d) < 0.0001;
|
|
|
|
List<AssemblyCoverageRow> assemblyRows = _automation.CalculateCoverageByAssembly(snapshot, gregCoreRows).ToList();
|
|
|
|
if (_language == UiLanguage.German)
|
|
{
|
|
_lblCoverage.Text = $"Katalog: {catalogCoverage.CoveragePercent:F2}% ({catalogCoverage.CoveredUnique}/{catalogCoverage.ExpectedUnique}) Missing: {catalogCoverage.MissingUnique}";
|
|
_lblGregCoreCoverage.Text = $"gregCore Umsetzung: {gregCoreCoverage.CoveragePercent:F2}% ({gregCoreCoverage.CoveredUnique}/{gregCoreCoverage.ExpectedUnique}) Missing: {gregCoreCoverage.MissingUnique} | Plugin-Ready: {(pluginReady ? "JA" : "NEIN")}";
|
|
_lblAssemblyCoverage.Text = $"gregCore Assemblies: {assemblyRows.Count}";
|
|
}
|
|
else
|
|
{
|
|
_lblCoverage.Text = $"Catalog: {catalogCoverage.CoveragePercent:F2}% ({catalogCoverage.CoveredUnique}/{catalogCoverage.ExpectedUnique}) Missing: {catalogCoverage.MissingUnique}";
|
|
_lblGregCoreCoverage.Text = $"gregCore implementation: {gregCoreCoverage.CoveragePercent:F2}% ({gregCoreCoverage.CoveredUnique}/{gregCoreCoverage.ExpectedUnique}) Missing: {gregCoreCoverage.MissingUnique} | Plugin-ready: {(pluginReady ? "YES" : "NO")}";
|
|
_lblAssemblyCoverage.Text = $"gregCore assemblies: {assemblyRows.Count}";
|
|
}
|
|
|
|
var details = new StringBuilder();
|
|
if (_language == UiLanguage.German)
|
|
{
|
|
details.AppendLine("Coverage-Übersicht");
|
|
details.AppendLine("------------------------------------------------------------");
|
|
details.AppendLine($"Snapshot Methoden : {snapshot.Methods.Count}");
|
|
details.AppendLine($"Katalog-Abdeckung : {catalogCoverage.CoveragePercent:F2}%");
|
|
details.AppendLine($"gregCore-Umsetzung : {gregCoreCoverage.CoveragePercent:F2}%");
|
|
details.AppendLine($"Plugin-Ready : {(pluginReady ? "JA" : "NEIN")}");
|
|
details.AppendLine($"Fehlende gregCore Signaturen: {gregCoreCoverage.MissingUnique}");
|
|
details.AppendLine();
|
|
details.AppendLine("Top Assemblies mit Lücken:");
|
|
foreach (AssemblyCoverageRow row in assemblyRows.OrderByDescending(x => x.MissingUnique).Take(12))
|
|
details.AppendLine($"- {row.Assembly}: Missing {row.MissingUnique}, Coverage {row.CoveragePercent:F2}%");
|
|
}
|
|
else
|
|
{
|
|
details.AppendLine("Coverage Overview");
|
|
details.AppendLine("------------------------------------------------------------");
|
|
details.AppendLine($"Snapshot methods : {snapshot.Methods.Count}");
|
|
details.AppendLine($"Catalog coverage : {catalogCoverage.CoveragePercent:F2}%");
|
|
details.AppendLine($"gregCore implementation : {gregCoreCoverage.CoveragePercent:F2}%");
|
|
details.AppendLine($"Plugin-ready : {(pluginReady ? "YES" : "NO")}");
|
|
details.AppendLine($"Missing gregCore signatures : {gregCoreCoverage.MissingUnique}");
|
|
details.AppendLine();
|
|
details.AppendLine("Top assemblies with gaps:");
|
|
foreach (AssemblyCoverageRow row in assemblyRows.OrderByDescending(x => x.MissingUnique).Take(12))
|
|
details.AppendLine($"- {row.Assembly}: Missing {row.MissingUnique}, Coverage {row.CoveragePercent:F2}%");
|
|
}
|
|
|
|
_txtCoverageDetails.Text = details.ToString();
|
|
|
|
_gridAssemblyCoverage.DataSource = assemblyRows;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_lblCoverage.Text = _language == UiLanguage.German ? "Katalog: Fehler" : "Catalog: error";
|
|
_lblGregCoreCoverage.Text = _language == UiLanguage.German ? "gregCore Umsetzung: Fehler" : "gregCore implementation: error";
|
|
_lblAssemblyCoverage.Text = _language == UiLanguage.German ? "Assemblies: Fehler" : "Assemblies: error";
|
|
_txtCoverageDetails.Text = ex.Message;
|
|
_gridAssemblyCoverage.DataSource = null;
|
|
AppendLog(ex.Message);
|
|
}
|
|
}
|
|
|
|
private void UpdateCategoryOptions()
|
|
{
|
|
string allLabel = T("all");
|
|
string previous = _cmbCategory.SelectedItem?.ToString() ?? allLabel;
|
|
|
|
List<string> categories = _allHookRows
|
|
.Select(r => r.Category)
|
|
.Where(c => !string.IsNullOrWhiteSpace(c))
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.OrderBy(c => c, StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
_cmbCategory.Items.Clear();
|
|
_cmbCategory.Items.Add(allLabel);
|
|
foreach (string category in categories)
|
|
_cmbCategory.Items.Add(category);
|
|
|
|
int idx = _cmbCategory.Items.IndexOf(previous);
|
|
_cmbCategory.SelectedIndex = idx >= 0 ? idx : 0;
|
|
}
|
|
|
|
private void ApplyHookFilter()
|
|
{
|
|
string selected = _cmbCategory.SelectedItem?.ToString() ?? T("all");
|
|
bool allSelected = string.Equals(selected, T("all"), StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(selected, "Alle", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(selected, "All", StringComparison.OrdinalIgnoreCase);
|
|
|
|
List<HookCatalogRow> filtered = allSelected
|
|
? _allHookRows
|
|
: _allHookRows.Where(r => string.Equals(r.Category, selected, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
_gridHooks.DataSource = filtered;
|
|
}
|
|
|
|
private static string BuildClassNameFromPluginName(string pluginName)
|
|
{
|
|
char[] separators = { '.', '-', ' ', '_', '/' };
|
|
string[] segments = pluginName
|
|
.Split(separators, StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(s => s.Trim())
|
|
.Where(s => s.Length > 0)
|
|
.ToArray();
|
|
|
|
if (segments.Length == 0)
|
|
return "GeneratedHookTemplate";
|
|
|
|
var sb = new StringBuilder();
|
|
foreach (string segment in segments)
|
|
{
|
|
if (segment.Length == 1)
|
|
sb.Append(char.ToUpperInvariant(segment[0]));
|
|
else
|
|
sb.Append(char.ToUpperInvariant(segment[0])).Append(segment[1..]);
|
|
}
|
|
|
|
if (!sb.ToString().EndsWith("Template", StringComparison.Ordinal))
|
|
sb.Append("Template");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private void AppendLog(string text)
|
|
{
|
|
string line = $"[{DateTime.Now:HH:mm:ss}] {text}{Environment.NewLine}";
|
|
_txtLog.AppendText(line);
|
|
}
|
|
|
|
private void ApplyLanguage()
|
|
{
|
|
_lblTitle.Text = "gregExtractor - Hook Sync & Migration";
|
|
_lblLanguage.Text = _language == UiLanguage.German ? "Sprache:" : "Language:";
|
|
|
|
TabControl? tabControl = Controls.OfType<TabControl>().FirstOrDefault();
|
|
if (tabControl != null && tabControl.TabPages.Count >= 5)
|
|
{
|
|
tabControl.TabPages[0].Text = T("tab.workflow");
|
|
tabControl.TabPages[1].Text = T("tab.coverage");
|
|
tabControl.TabPages[2].Text = T("tab.modanalysis");
|
|
tabControl.TabPages[3].Text = T("tab.log");
|
|
tabControl.TabPages[4].Text = T("tab.help");
|
|
}
|
|
|
|
TabControl? coverageTabs = GetAllControls(this).OfType<TabControl>().FirstOrDefault(x => x.Name == "CoverageTabs");
|
|
if (coverageTabs != null && coverageTabs.TabPages.Count >= 3)
|
|
{
|
|
coverageTabs.TabPages[0].Text = T("tab.coverage.overview");
|
|
coverageTabs.TabPages[1].Text = T("tab.coverage.hooks");
|
|
coverageTabs.TabPages[2].Text = T("tab.coverage.assemblies");
|
|
}
|
|
|
|
TabControl? modTabs = GetAllControls(this).OfType<TabControl>().FirstOrDefault(x => x.Name == "ModAnalysisTabs");
|
|
if (modTabs != null && modTabs.TabPages.Count >= 4)
|
|
{
|
|
modTabs.TabPages[0].Text = T("tab.mod.summary");
|
|
modTabs.TabPages[1].Text = T("tab.mod.opportunities");
|
|
modTabs.TabPages[2].Text = T("tab.mod.fileinsights");
|
|
modTabs.TabPages[3].Text = T("tab.mod.hooks");
|
|
}
|
|
|
|
foreach (Control control in GetAllControls(this))
|
|
{
|
|
switch (control)
|
|
{
|
|
case GroupBox group when control.Name == "WorkflowPathsGroup":
|
|
group.Text = T("workflow.paths.group");
|
|
break;
|
|
case GroupBox group when control.Name == "WorkflowSummaryGroup":
|
|
group.Text = T("workflow.summary.group");
|
|
break;
|
|
case Label label when label.Name == "WorkflowHintLabel":
|
|
label.Text = T("workflow.hint");
|
|
break;
|
|
case Label label when label.Name == "CategoryLabel":
|
|
label.Text = T("category");
|
|
break;
|
|
case Label label when label.Name == "ModProjectLabel":
|
|
label.Text = T("mod.project.path");
|
|
break;
|
|
case Label label when label.Name == "PathRepoLabel":
|
|
label.Text = T("path.repo");
|
|
break;
|
|
case Label label when label.Name == "PathSourceLabel":
|
|
label.Text = T("path.source");
|
|
break;
|
|
case Label label when label.Name == "PathMelonLabel":
|
|
label.Text = T("path.melon");
|
|
break;
|
|
case Label label when label.Name == "PathGameLabel":
|
|
label.Text = T("path.game");
|
|
break;
|
|
case Label label when label.Name == "PathTemplateLabel":
|
|
label.Text = T("path.template");
|
|
break;
|
|
case Label label when label.Name == "PathPluginLabel":
|
|
label.Text = T("path.plugin");
|
|
break;
|
|
case Button button when button.Name == "RefreshCoverageButton":
|
|
button.Text = T("btn.refresh");
|
|
break;
|
|
case Button button when button.Name.EndsWith("_Browse", StringComparison.Ordinal):
|
|
button.Text = T("btn.browse");
|
|
break;
|
|
}
|
|
}
|
|
|
|
_btnImportMelon.Text = T("btn.import.melon");
|
|
_btnImportGame.Text = T("btn.import.game");
|
|
_btnGenerateTemplate.Text = T("btn.template.generate");
|
|
_btnOpenTemplateOutput.Text = T("btn.open");
|
|
|
|
_btnScan.Text = T("btn.scan");
|
|
_btnSync.Text = T("btn.sync");
|
|
_btnRegenerate.Text = T("btn.regenerate");
|
|
_btnBuild.Text = T("btn.build");
|
|
|
|
_btnBrowseModProject.Text = T("btn.browse");
|
|
_btnOpenModProject.Text = T("btn.open");
|
|
_btnAnalyzeModProject.Text = T("btn.mod.analyze");
|
|
|
|
_chkWatch.Text = T("chk.watch");
|
|
_chkAutoRegenerate.Text = T("chk.autoregen");
|
|
_chkBuildAfterRegenerate.Text = T("chk.buildafter");
|
|
|
|
_lblStatus.Text = $"{T("status")}: {T("ready")}";
|
|
|
|
_txtHelp.Text = BuildHelpText();
|
|
|
|
UpdateGridHeaders();
|
|
RefreshHookRows(_store.TryLoadSnapshot());
|
|
}
|
|
|
|
private void UpdateGridHeaders()
|
|
{
|
|
_gridHooks.Columns[0].HeaderText = T("col.hookevent");
|
|
_gridHooks.Columns[1].HeaderText = T("col.gregapi");
|
|
_gridHooks.Columns[2].HeaderText = T("col.category");
|
|
_gridHooks.Columns[3].HeaderText = T("col.patchtarget");
|
|
|
|
_gridAssemblyCoverage.Columns[0].HeaderText = T("col.assembly");
|
|
_gridAssemblyCoverage.Columns[1].HeaderText = T("col.coveragepercent");
|
|
_gridAssemblyCoverage.Columns[2].HeaderText = T("col.covered");
|
|
_gridAssemblyCoverage.Columns[3].HeaderText = T("col.expected");
|
|
_gridAssemblyCoverage.Columns[4].HeaderText = T("col.missing");
|
|
_gridAssemblyCoverage.Columns[5].HeaderText = T("col.hooked");
|
|
|
|
_gridMigration.Columns[0].HeaderText = T("col.type");
|
|
_gridMigration.Columns[1].HeaderText = T("col.currentpattern");
|
|
_gridMigration.Columns[2].HeaderText = T("col.suggestedhook");
|
|
_gridMigration.Columns[3].HeaderText = T("col.suggestion");
|
|
|
|
_gridFileInsights.Columns[0].HeaderText = T("col.filepath");
|
|
_gridFileInsights.Columns[1].HeaderText = T("col.fileharmony");
|
|
_gridFileInsights.Columns[2].HeaderText = T("col.filesubs");
|
|
_gridFileInsights.Columns[3].HeaderText = T("col.fileapi");
|
|
_gridFileInsights.Columns[4].HeaderText = T("col.needsmigration");
|
|
_gridFileInsights.Columns[5].HeaderText = T("col.recommendation");
|
|
}
|
|
|
|
private string BuildHelpText()
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
if (_language == UiLanguage.German)
|
|
{
|
|
sb.AppendLine("gregExtractor - Help");
|
|
sb.AppendLine("============================================================");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Was ist 'Melon Generated'?");
|
|
sb.AppendLine("- Das ist der Ordner mit generierten C#-Quellen von Melon/Il2Cpp.");
|
|
sb.AppendLine("- Er kann im Spieleordner liegen (z. B. <Game>\\MelonLoader\\Generated)");
|
|
sb.AppendLine(" oder unter %LocalAppData%\\MelonLoader.");
|
|
sb.AppendLine("- Es ist NICHT nur ein beliebiger Installationspfad, sondern der Output mit Source-Dateien.");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Tabs:");
|
|
sb.AppendLine("1) Workflow:");
|
|
sb.AppendLine(" - Import aus Melon Generated oder direkt aus Game-Ordner.");
|
|
sb.AppendLine(" - Scan/Sync/Regenerate/Build.");
|
|
sb.AppendLine(" - Template erzeugen und direkt per 'Öffnen' den Zielordner öffnen.");
|
|
sb.AppendLine("2) Hook Coverage:");
|
|
sb.AppendLine(" - Katalog-Abdeckung vs. Snapshot.");
|
|
sb.AppendLine(" - gregCore-Umsetzung + Plugin-Ready (JA/NEIN).");
|
|
sb.AppendLine(" - Assembly-Tabelle für Lückenanalyse.");
|
|
sb.AppendLine("3) Modprojekt Analyse:");
|
|
sb.AppendLine(" - Beliebigen Modprojekt-Ordner wählen.");
|
|
sb.AppendLine(" - Prüfen, wie weit das Projekt auf gregCore migriert ist.");
|
|
sb.AppendLine(" - Konkrete Umsetzungsansätze in der Opportunity-Tabelle erhalten.");
|
|
sb.AppendLine("4) Log: Alle Aktionen und Outputs.");
|
|
sb.AppendLine("5) Help: Diese Referenzseite.");
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("gregExtractor - Help");
|
|
sb.AppendLine("============================================================");
|
|
sb.AppendLine();
|
|
sb.AppendLine("What is 'Melon Generated'?");
|
|
sb.AppendLine("- It is the folder containing generated C# sources from Melon/Il2Cpp.");
|
|
sb.AppendLine("- It can be inside the game folder (e.g. <Game>\\MelonLoader\\Generated)");
|
|
sb.AppendLine(" or in %LocalAppData%\\MelonLoader.");
|
|
sb.AppendLine("- It is not just any installation path; it is the source-output location.");
|
|
sb.AppendLine();
|
|
sb.AppendLine("Tabs:");
|
|
sb.AppendLine("1) Workflow:");
|
|
sb.AppendLine(" - Import from Melon Generated or directly from game directory.");
|
|
sb.AppendLine(" - Run scan/sync/regenerate/build.");
|
|
sb.AppendLine(" - Generate template and open target folder with 'Open'.");
|
|
sb.AppendLine("2) Hook Coverage:");
|
|
sb.AppendLine(" - Catalog coverage vs snapshot.");
|
|
sb.AppendLine(" - gregCore implementation + Plugin-ready (YES/NO).");
|
|
sb.AppendLine(" - Assembly table for gap analysis.");
|
|
sb.AppendLine("3) Mod Project Analysis:");
|
|
sb.AppendLine(" - Pick any mod project directory.");
|
|
sb.AppendLine(" - Check how much is migrated to gregCore usage.");
|
|
sb.AppendLine(" - Get concrete migration opportunities and implementation hints.");
|
|
sb.AppendLine("4) Log: all actions and command outputs.");
|
|
sb.AppendLine("5) Help: this reference page.");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static IEnumerable<Control> GetAllControls(Control parent)
|
|
{
|
|
foreach (Control child in parent.Controls)
|
|
{
|
|
yield return child;
|
|
foreach (Control nested in GetAllControls(child))
|
|
yield return nested;
|
|
}
|
|
}
|
|
|
|
private string T(string key)
|
|
{
|
|
return (_language, key) switch
|
|
{
|
|
(UiLanguage.German, "tab.workflow") => "Workflow",
|
|
(UiLanguage.English, "tab.workflow") => "Workflow",
|
|
(UiLanguage.German, "tab.coverage") => "Hook Coverage",
|
|
(UiLanguage.English, "tab.coverage") => "Hook Coverage",
|
|
(UiLanguage.German, "tab.coverage.overview") => "Übersicht",
|
|
(UiLanguage.English, "tab.coverage.overview") => "Overview",
|
|
(UiLanguage.German, "tab.coverage.hooks") => "Hook Tabelle",
|
|
(UiLanguage.English, "tab.coverage.hooks") => "Hook Table",
|
|
(UiLanguage.German, "tab.coverage.assemblies") => "Assembly Tabelle",
|
|
(UiLanguage.English, "tab.coverage.assemblies") => "Assembly Table",
|
|
(UiLanguage.German, "tab.modanalysis") => "Modprojekt Analyse",
|
|
(UiLanguage.English, "tab.modanalysis") => "Mod Project Analysis",
|
|
(UiLanguage.German, "tab.mod.summary") => "Zusammenfassung",
|
|
(UiLanguage.English, "tab.mod.summary") => "Summary",
|
|
(UiLanguage.German, "tab.mod.opportunities") => "Opportunities",
|
|
(UiLanguage.English, "tab.mod.opportunities") => "Opportunities",
|
|
(UiLanguage.German, "tab.mod.fileinsights") => "Datei-Insights",
|
|
(UiLanguage.English, "tab.mod.fileinsights") => "File Insights",
|
|
(UiLanguage.German, "tab.mod.hooks") => "Hook Nutzung",
|
|
(UiLanguage.English, "tab.mod.hooks") => "Hook Usage",
|
|
(UiLanguage.German, "tab.log") => "Log",
|
|
(UiLanguage.English, "tab.log") => "Log",
|
|
(UiLanguage.German, "tab.help") => "Help",
|
|
(UiLanguage.English, "tab.help") => "Help",
|
|
(UiLanguage.German, "workflow.paths.group") => "Import, Pfade und Aktionen",
|
|
(UiLanguage.English, "workflow.paths.group") => "Import, Paths and Actions",
|
|
(UiLanguage.German, "workflow.summary.group") => "Änderungsübersicht",
|
|
(UiLanguage.English, "workflow.summary.group") => "Change Summary",
|
|
(UiLanguage.German, "path.repo") => "Repo:",
|
|
(UiLanguage.English, "path.repo") => "Repo:",
|
|
(UiLanguage.German, "path.source") => "Source:",
|
|
(UiLanguage.English, "path.source") => "Source:",
|
|
(UiLanguage.German, "path.melon") => "Melon Generated:",
|
|
(UiLanguage.English, "path.melon") => "Melon Generated:",
|
|
(UiLanguage.German, "path.game") => "Game:",
|
|
(UiLanguage.English, "path.game") => "Game:",
|
|
(UiLanguage.German, "path.template") => "Template Ziel:",
|
|
(UiLanguage.English, "path.template") => "Template Output:",
|
|
(UiLanguage.German, "path.plugin") => "Plugin Name:",
|
|
(UiLanguage.English, "path.plugin") => "Plugin Name:",
|
|
(UiLanguage.German, "btn.import.melon") => "Melon importieren",
|
|
(UiLanguage.English, "btn.import.melon") => "Import Melon",
|
|
(UiLanguage.German, "btn.import.game") => "Aus Spielordner importieren",
|
|
(UiLanguage.English, "btn.import.game") => "Import From Game",
|
|
(UiLanguage.German, "btn.template.generate") => "Plugin Template bauen (alle Hooks)",
|
|
(UiLanguage.English, "btn.template.generate") => "Generate Plugin Template (all hooks)",
|
|
(UiLanguage.German, "btn.open") => "Öffnen",
|
|
(UiLanguage.English, "btn.open") => "Open",
|
|
(UiLanguage.German, "btn.scan") => "1) Nur Änderungen scannen",
|
|
(UiLanguage.English, "btn.scan") => "1) Scan changes only",
|
|
(UiLanguage.German, "btn.sync") => "2) Scan + bei Änderung syncen",
|
|
(UiLanguage.English, "btn.sync") => "2) Scan + sync if changed",
|
|
(UiLanguage.German, "btn.regenerate") => "Hooks regenerieren",
|
|
(UiLanguage.English, "btn.regenerate") => "Regenerate hooks",
|
|
(UiLanguage.German, "btn.build") => "gregCore builden",
|
|
(UiLanguage.English, "btn.build") => "Build gregCore",
|
|
(UiLanguage.German, "btn.browse") => "Wählen",
|
|
(UiLanguage.English, "btn.browse") => "Browse",
|
|
(UiLanguage.German, "btn.mod.analyze") => "Modprojekt analysieren",
|
|
(UiLanguage.English, "btn.mod.analyze") => "Analyze mod project",
|
|
(UiLanguage.German, "btn.refresh") => "Aktualisieren",
|
|
(UiLanguage.English, "btn.refresh") => "Refresh",
|
|
(UiLanguage.German, "chk.watch") => "Dateien überwachen (continuous mode)",
|
|
(UiLanguage.English, "chk.watch") => "Watch files (continuous mode)",
|
|
(UiLanguage.German, "chk.autoregen") => "Auto-Regenerate bei Änderungen",
|
|
(UiLanguage.English, "chk.autoregen") => "Auto regenerate on changes",
|
|
(UiLanguage.German, "chk.buildafter") => "Nach Regeneration automatisch builden",
|
|
(UiLanguage.English, "chk.buildafter") => "Build automatically after regeneration",
|
|
(UiLanguage.German, "status") => "Status",
|
|
(UiLanguage.English, "status") => "Status",
|
|
(UiLanguage.German, "ready") => "Bereit",
|
|
(UiLanguage.English, "ready") => "Ready",
|
|
(UiLanguage.German, "all") => "Alle",
|
|
(UiLanguage.English, "all") => "All",
|
|
(UiLanguage.German, "workflow.hint") => "Tipp: Import -> Scan/Sync -> Regenerate -> Build -> Coverage/Analyse prüfen.",
|
|
(UiLanguage.English, "workflow.hint") => "Tip: Import -> Scan/Sync -> Regenerate -> Build -> Review coverage/analysis.",
|
|
(UiLanguage.German, "category") => "Kategorie:",
|
|
(UiLanguage.English, "category") => "Category:",
|
|
(UiLanguage.German, "mod.project.path") => "Modprojekt:",
|
|
(UiLanguage.English, "mod.project.path") => "Mod project:",
|
|
(UiLanguage.German, "mod.summary.group") => "Migrationsstatus & Zusammenfassung",
|
|
(UiLanguage.English, "mod.summary.group") => "Migration Status & Summary",
|
|
(UiLanguage.German, "mod.opportunities.group") => "Migration Opportunities (wie umsetzen)",
|
|
(UiLanguage.English, "mod.opportunities.group") => "Migration Opportunities (how to implement)",
|
|
(UiLanguage.German, "col.hookevent") => "IL2CPP Hook Event",
|
|
(UiLanguage.English, "col.hookevent") => "IL2CPP Hook Event",
|
|
(UiLanguage.German, "col.gregapi") => "greg API Call",
|
|
(UiLanguage.English, "col.gregapi") => "greg API Call",
|
|
(UiLanguage.German, "col.category") => "Kategorie",
|
|
(UiLanguage.English, "col.category") => "Category",
|
|
(UiLanguage.German, "col.patchtarget") => "Patch Target",
|
|
(UiLanguage.English, "col.patchtarget") => "Patch Target",
|
|
(UiLanguage.German, "col.assembly") => "Assembly",
|
|
(UiLanguage.English, "col.assembly") => "Assembly",
|
|
(UiLanguage.German, "col.coveragepercent") => "Coverage %",
|
|
(UiLanguage.English, "col.coveragepercent") => "Coverage %",
|
|
(UiLanguage.German, "col.covered") => "Covered",
|
|
(UiLanguage.English, "col.covered") => "Covered",
|
|
(UiLanguage.German, "col.expected") => "Expected",
|
|
(UiLanguage.English, "col.expected") => "Expected",
|
|
(UiLanguage.German, "col.missing") => "Missing",
|
|
(UiLanguage.English, "col.missing") => "Missing",
|
|
(UiLanguage.German, "col.hooked") => "Hooked",
|
|
(UiLanguage.English, "col.hooked") => "Hooked",
|
|
(UiLanguage.German, "col.type") => "Typ",
|
|
(UiLanguage.English, "col.type") => "Type",
|
|
(UiLanguage.German, "col.currentpattern") => "Aktuelles Muster",
|
|
(UiLanguage.English, "col.currentpattern") => "Current Pattern",
|
|
(UiLanguage.German, "col.suggestedhook") => "Vorgeschlagener greg Hook",
|
|
(UiLanguage.English, "col.suggestedhook") => "Suggested greg Hook",
|
|
(UiLanguage.German, "col.suggestion") => "Umsetzungsvorschlag",
|
|
(UiLanguage.English, "col.suggestion") => "Implementation Suggestion",
|
|
(UiLanguage.German, "col.filepath") => "Datei",
|
|
(UiLanguage.English, "col.filepath") => "File",
|
|
(UiLanguage.German, "col.fileharmony") => "Harmony",
|
|
(UiLanguage.English, "col.fileharmony") => "Harmony",
|
|
(UiLanguage.German, "col.filesubs") => "greg Subs",
|
|
(UiLanguage.English, "col.filesubs") => "greg Subs",
|
|
(UiLanguage.German, "col.fileapi") => "greg API",
|
|
(UiLanguage.English, "col.fileapi") => "greg API",
|
|
(UiLanguage.German, "col.needsmigration") => "Migration nötig",
|
|
(UiLanguage.English, "col.needsmigration") => "Needs migration",
|
|
(UiLanguage.German, "col.recommendation") => "Empfehlung",
|
|
(UiLanguage.English, "col.recommendation") => "Recommendation",
|
|
_ => key,
|
|
};
|
|
}
|
|
}
|